""" 豆包 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 说明「均为新接口,对现有调用方无破坏」即可 - 语气简洁,可用 ... 标注风险项 ## 变更文件 {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)