148 lines
4.7 KiB
Python
148 lines
4.7 KiB
Python
"""
|
||
豆包 LLM 接口参数变更审核模块。
|
||
LLM 仅输出简短的兼容性提示,详细变更由 AST + Markdown 通知展示。
|
||
"""
|
||
|
||
import json
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
import requests
|
||
|
||
from comparator import EndpointChangeReport
|
||
|
||
# 写入 prompt,不在通知中展示
|
||
FRAMEWORK_IGNORE_HINT = """
|
||
以下参数类型/名称属于 Spring MVC 框架自动注入,不是 API 调用方入参,审核时必须忽略,不要在结果中提及:
|
||
HttpServletRequest、HttpServletResponse、HttpSession、ServletRequest、ServletResponse、
|
||
WebRequest、NativeWebRequest、Model、ModelMap、RedirectAttributes、BindingResult、
|
||
Authentication、Principal 等。
|
||
"""
|
||
|
||
|
||
def is_llm_enabled(config: Dict[str, Any]) -> bool:
|
||
"""判断大模型总开关是否开启。"""
|
||
return config.get("llm", {}).get("enabled", True)
|
||
|
||
|
||
def call_doubao_api(
|
||
api_key: str,
|
||
prompt: str,
|
||
config: Dict[str, Any],
|
||
) -> Optional[str]:
|
||
"""调用豆包 API。"""
|
||
if not api_key or api_key == "YOUR_DOUBAO_API_KEY":
|
||
print("[警告] 未配置豆包 API Key,跳过 LLM 审核。")
|
||
return None
|
||
|
||
llm_cfg = config.get("llm", {})
|
||
model = llm_cfg.get("model") or llm_cfg.get("endpoint_id", "")
|
||
if not model:
|
||
print("[警告] 未配置 llm.model,跳过 LLM 审核。")
|
||
return None
|
||
|
||
api_url = llm_cfg.get(
|
||
"api_url", "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
|
||
)
|
||
timeout = llm_cfg.get("timeout")
|
||
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {api_key}",
|
||
}
|
||
payload = {
|
||
"model": model,
|
||
"messages": [
|
||
{
|
||
"role": "system",
|
||
"content": (
|
||
"你是 Java Spring Boot API 变更分析专家。"
|
||
"你只负责输出简短的兼容性风险提示,不重复罗列接口参数明细。"
|
||
+ FRAMEWORK_IGNORE_HINT
|
||
),
|
||
},
|
||
{"role": "user", "content": prompt},
|
||
],
|
||
"temperature": 0.1,
|
||
}
|
||
|
||
try:
|
||
kwargs = {"headers": headers, "json": payload}
|
||
if timeout is not None:
|
||
kwargs["timeout"] = timeout
|
||
resp = requests.post(api_url, **kwargs)
|
||
resp.raise_for_status()
|
||
data = resp.json()
|
||
if "choices" in data and data["choices"]:
|
||
return data["choices"][0]["message"]["content"]
|
||
return None
|
||
except requests.RequestException as exc:
|
||
print(f"[错误] 豆包 API 调用失败: {exc}")
|
||
return None
|
||
|
||
|
||
def build_parameter_change_prompt(
|
||
reports: List[EndpointChangeReport],
|
||
changed_files: List[str],
|
||
git_diff: str = "",
|
||
) -> str:
|
||
"""
|
||
构造 LLM 提示词:只要求输出兼容性摘要,不要求重复参数列表。
|
||
"""
|
||
ast_report = []
|
||
for r in reports:
|
||
ast_report.append(
|
||
{
|
||
"uri": f"{r.http_method} {r.uri}",
|
||
"is_new": r.is_new_endpoint,
|
||
"is_removed": r.is_removed_endpoint,
|
||
"changes": [
|
||
{
|
||
"type": c.change_type.value,
|
||
"name": c.param_name,
|
||
"java_type": c.param_type,
|
||
"required": c.required,
|
||
}
|
||
for c in r.parameter_changes
|
||
],
|
||
}
|
||
)
|
||
|
||
diff_block = git_diff.strip()[:6000] if git_diff.strip() else "(无)"
|
||
|
||
return f"""请根据以下 Controller 接口参数变更,**仅输出「兼容性提示」**,要求:
|
||
|
||
{FRAMEWORK_IGNORE_HINT}
|
||
|
||
## 输出格式(严格遵守)
|
||
- 只输出 3~6 行 Markdown,不要输出「整体说明」「接口变更详情」等标题
|
||
- 不要逐条重复 URI 和参数列表(通知里已有)
|
||
- 不要提及「排除框架注入」相关字样
|
||
- 重点说明:是否有破坏性变更、哪些必填参数调用方必须传入
|
||
- 全新 Controller 说明「均为新接口,对现有调用方无破坏」即可
|
||
- 语气简洁,可用 <font color="warning">...</font> 标注风险项
|
||
|
||
## 变更文件
|
||
{json.dumps(changed_files, ensure_ascii=False)}
|
||
|
||
## AST 变更摘要
|
||
{json.dumps(ast_report, ensure_ascii=False, indent=2)}
|
||
|
||
## Git Diff
|
||
{diff_block}
|
||
"""
|
||
|
||
|
||
def review_parameter_changes(
|
||
reports: List[EndpointChangeReport],
|
||
config: Dict[str, Any],
|
||
changed_files: List[str],
|
||
git_diff: str = "",
|
||
) -> Optional[str]:
|
||
"""LLM 审核,返回简短兼容性提示。"""
|
||
if not is_llm_enabled(config) or not reports:
|
||
return None
|
||
|
||
llm_cfg = config.get("llm", {})
|
||
prompt = build_parameter_change_prompt(reports, changed_files, git_diff)
|
||
return call_doubao_api(llm_cfg.get("api_key", ""), prompt, config)
|