LiSiyi13146413708/Baseline_Agent
0
1# -*- coding: utf-8 -*-2"""3LLM 分析模块:调用 DeepSeek API 动态解析论文结构并返回 Roadmap。4"""5 6import re7import json8from typing import Optional9 10from secure_api import load_api_key11from schemas import Roadmap12from pydantic import ValidationError13from fallback import build_fallback_roadmap14 15 16LLM_SYSTEM_PROMPT = (17 "你是一名科研架构师。请阅读论文,设计一个结构化的技术路线图。请将研究过程划分为 3-5 个主要阶段(如:研究背景与问题、理论框架构建、核心模型设计、实验验证与应用等)。"18 "为了满足‘四列层级布局’要求,你需要生成以下四类节点:\n"19 "1. 'stage_label': 阶段名称(第1列)。\n"20 "2. 'task': 核心任务(第2列),动词+核心对象(如‘构建双螺旋耦合模型’)。\n"21 "3. 'sub_content': 细分内容(第3列),列出具体指标、变量或执行点(如‘输入:R&D经费’)。\n"22 "4. 'method': 方法与工具(第4列),该阶段涉及的方法。\n"23 "输出必须严格符合 JSON Schema:\n"24 "{\n"25 " \"title\": string,\n"26 " \"clusters\": [ { \"id\": string, \"label\": string } ],\n"27 " \"nodes\": [ { \"id\": string, \"label\": string, \"type\": \"stage_label\"|\"task\"|\"sub_content\"|\"method\", \"parent_cluster\": string } ],\n"28 " \"edges\": [ { \"source\": string, \"target\": string, \"label\"?: string } ]\n"29 "}\n"30 "要求:\n"31 "1. 每个 cluster 代表图中的一行(横向区域)。\n"32 "2. 每个 cluster 内必须有一个 'stage_label' 类型的节点(放在左侧)。\n"33 "3. 核心逻辑流向主要体现在 'task' 之间(垂直向下),'task' 指向其对应的 'sub_content'(水平向右)。\n"34 "4. 'sub_content' 内容要具体(拒绝空泛),提取具体指标或变量名。\n"35 "5. 节点文字要精简(不超过 15 个字,允许换行),避免长句。\n"36 "6. 不要输出任何非 JSON 的附加说明。"37)38 39 40def _extract_json_block(text: str) -> Optional[str]:41 # 从回答中提取 JSON 代码块或最外层 JSON42 code_block = re.search(r"```\s*json\s*([\s\S]*?)```", text, flags=re.IGNORECASE)43 if code_block:44 return code_block.group(1).strip()45 start = text.find("{")46 end = text.rfind("}")47 if start != -1 and end != -1 and end > start:48 return text[start : end + 1]49 return None50 51 52def analyze_structure(text: str, model: str = "deepseek-chat", api_key_env: str = "DEEPSEEK_API_KEY", key_file: Optional[str] = None, pass_env: str = "DEEPSEEK_KEY_PASSPHRASE", env_file: Optional[str] = None, key_name: str = "DEEPSEEK_API_KEY") -> Roadmap:53 # 加载 API Key(优先 .env,其次加密文件,最后环境变量)54 api_key = load_api_key(api_key_env=api_key_env, encrypted_file=key_file, pass_env=pass_env, env_file=env_file, key_name=key_name)55 56 content = None57 # 优先使用 OpenAI SDK 兼容模式(设置 base_url 指向 DeepSeek)58 try:59 from openai import OpenAI60 61 client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com/v1")62 resp = client.chat.completions.create(63 model=model,64 messages=[65 {"role": "system", "content": LLM_SYSTEM_PROMPT},66 {"role": "user", "content": text},67 ],68 temperature=0.2,69 )70 content = resp.choices[0].message.content71 except Exception:72 # 回退到 requests 直连 DeepSeek REST API73 try:74 import requests75 76 headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}77 payload = {78 "model": model,79 "messages": [80 {"role": "system", "content": LLM_SYSTEM_PROMPT},81 {"role": "user", "content": text},82 ],83 "temperature": 0.2,84 }85 r = requests.post("https://api.deepseek.com/v1/chat/completions", headers=headers, json=payload, timeout=60)86 r.raise_for_status()87 data_resp = r.json()88 content = data_resp["choices"][0]["message"]["content"]89 except Exception as e:90 # 无法获取内容,降级91 return build_fallback_roadmap(text, error=str(e))92 93 raw_json = _extract_json_block(content) or content94 try:95 data = json.loads(raw_json)96 except Exception:97 cleaned = raw_json.replace("'", '"')98 cleaned = re.sub(r",\s*}\s*$", "}", cleaned)99 data = json.loads(cleaned)100 101 try:102 roadmap = Roadmap(**data)103 roadmap.ensure_consistency()104 return roadmap105 except (ValidationError, ValueError) as e:106 return build_fallback_roadmap(text, error=str(e))107 