From 9dd54b3a3831f247fd7809582fda82a1722b41cb Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Thu, 19 Feb 2026 18:07:00 +0800 Subject: [PATCH] =?UTF-8?q?Phase=205:=20=E7=9F=A5=E8=AF=86=E6=8E=A8?= =?UTF-8?q?=E7=90=86=E4=B8=8E=E9=97=AE=E7=AD=94=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 knowledge_reasoner.py 推理引擎 - 支持因果/对比/时序/关联四种推理类型 - 智能项目总结 API (全面/高管/技术/风险) - 实体关联路径发现功能 - 前端推理面板 UI 和交互 - 更新 API 端点和健康检查 Refs: Phase 5 开发任务 --- README.md | 21 +- STATUS.md | 7 +- .../__pycache__/db_manager.cpython-312.pyc | Bin 30308 -> 36052 bytes .../document_processor.cpython-312.pyc | Bin 8094 -> 8149 bytes .../entity_aligner.cpython-312.pyc | Bin 12047 -> 12102 bytes .../knowledge_reasoner.cpython-312.pyc | Bin 0 -> 19892 bytes backend/__pycache__/main.cpython-312.pyc | Bin 49647 -> 58168 bytes .../__pycache__/oss_uploader.cpython-312.pyc | Bin 0 -> 3004 bytes .../__pycache__/tingwu_client.cpython-312.pyc | Bin 0 -> 8046 bytes backend/knowledge_reasoner.py | 533 ++++++++++++++++++ backend/main.py | 171 +++++- frontend/app.js | 266 +++++++++ frontend/workbench.html | 299 ++++++++++ 13 files changed, 1286 insertions(+), 11 deletions(-) create mode 100644 backend/__pycache__/knowledge_reasoner.cpython-312.pyc create mode 100644 backend/__pycache__/oss_uploader.cpython-312.pyc create mode 100644 backend/__pycache__/tingwu_client.cpython-312.pyc create mode 100644 backend/knowledge_reasoner.py diff --git a/README.md b/README.md index 57863e4..747cbeb 100644 --- a/README.md +++ b/README.md @@ -122,14 +122,21 @@ POST /api/v1/projects/{project_id}/align-entities?threshold=0.85 - [ ] 图谱数据同步 - [ ] 复杂图查询支持 -## Phase 5: 高级功能 - 规划中 📋 +## Phase 5: 高级功能 - 进行中 🚧 -- [ ] 知识推理与问答增强 -- [ ] 实体属性扩展 -- [ ] 时间线视图 -- [ ] 导出功能(PDF/图片) -- [ ] 协作功能(多用户) -- [ ] API 开放平台 +### 开发任务清单 + +1. **知识推理与问答增强** ✅ (已完成) + - 后端推理引擎 `knowledge_reasoner.py` + - 因果/对比/时序/关联推理 + - 智能项目总结 API + - 实体关联路径发现 + - 前端推理面板 UI + +2. **实体属性扩展** - 待开发 +3. **时间线视图** ✅ (已完成) +4. **导出功能** - 待开发 +5. **协作功能** - 待开发 --- diff --git a/STATUS.md b/STATUS.md index 5e33b98..bb2fbd0 100644 --- a/STATUS.md +++ b/STATUS.md @@ -78,7 +78,12 @@ Phase 4: Agent 助手与知识溯源 - **开发中 🚧** - [ ] 支持复杂图查询 ### Phase 5 - 高级功能 (进行中 🚧) -- [ ] 知识推理与问答增强 +- [x] 知识推理与问答增强 ✅ (2026-02-19 完成) + - 后端 API: `/api/v1/projects/{id}/reasoning/query` + - 支持因果/对比/时序/关联推理 + - 智能项目总结 (全面/高管/技术/风险) + - 实体关联路径发现 + - 前端推理面板 UI - [ ] 实体属性扩展 - [x] 时间线视图 ✅ (2026-02-19 完成) - [ ] 导出功能 diff --git a/backend/__pycache__/db_manager.cpython-312.pyc b/backend/__pycache__/db_manager.cpython-312.pyc index 016ecc9a60d70a80cab1d5c19b438f7d67d6986a..b06fe2e941c0cf31bef607fd56aa1be177ce0992 100644 GIT binary patch delta 4006 zcmaJ^Yj6|S72aLFR?@CsmMlMHc?~vLj*%Y>X<`c;V>`qIoMwQgP;7OhUBiMN&T5To zS02$BGGiXC?cNFC#x(YyhDPe^V^xoA< z7RKq0_MCgqJ?Eb9p1b#)t2=+i=OzftZ)nh!08eIw2Yt4jhzv!?A(7OOJ_18vLK&fxZUc3JMH|@ive& zyot919r-fe0bV61p!_f7yRW{;>3A*Ygl{u^1MnqziZ3aK{(DW_=3}~j8x#$aFX`2C zTaIb>`MGUe3Dj%h>$n%-OY+)Yl52&U^@B~mmo9zZcrsMil{UpCR+NGqEAeTPk8(;Y zq(ci+9~Q)jGk$w-fDnyO2bqZkU2p?KokUI&IhC& zrlH1y)oVL|4O})J3X+P%F8MyNuzF!1OnoF~kSqWZ-Id09@E>bn?tAT@XrcO>jv( z2n373Quk(Js1aNX7K8mg#Z5ytab+m%;?oO%LWCC1j)1Wq2E5m^u>~St{gFg}uMp-l zOMhYp6qoEuz^6SI!QH)`#84GPRh1MexXoe`lC&wO#Ea{Zz?>J&TnEKa^dh5Y5UE9^ z8j&|4Nt>60!}~PL17aB*^_eo>P+R;1BI}W3@sEi736iv3^|-u2d0qyQ1}XAk7VVt) z7V2jGp;f2NFqIzQr6B5vr{S=VSVrI@S2{-%B-6BtIO91@t8-+!$}A#s98$9@Y+@M7 zYX$IKM8P|QB`^+0#}6JGqARm7H|+ZGH2$?XvSUOv{4X*PW+Sh2?3STt+gXWyosILL zGe)&$4~HZ~_^=S={ef^S#D?QrU?XGjG4vQm)$S;EzTy1-_gao(Vo}D0<}g19X1BYO zdtqPfge0wjK}c)kF;Vi_#kEN6K?Loom`3DNL|Pz8n?kWDr<{a%THnWtY$Tqh1`hHf zAA|+8KhBH4Kxzfqs0Z`(tvMkw{y zDXQ=mRW?hN&3NnPsQMJ;7~Oe%=ULwzRe9U&${TH%qZ+fLHFH$eZF50xG&$qVAFiIG z*3P@VXA?}GH7!SyV&R!^+F!$QA9vQ3e52(uZjBdrLK;xAR`KC2x+ zkSeN3dCRA$iCC)4d-m%SJ*jeLs%*BrF2}3ZO(mv-DW+n|Kg%?z%JWc~oUQr-64y-A zDNoti$gHP!!JsX5EV!(dmdBWtw#pjlQ%+W5j?zaMy}J7~S@#udrdnnzHl`roJX_I_ z<8bOj6FZ^9clS*1Ip1=ls#$fqpwmESb_zM8kTd8(W-w#XEz&a!e-%wLq-Tz7*3VnL zGo%;P`bvqP;_y!P5|B4mfXZ4YxXJs7VJ)brb%X64H27ds8Lk7<%~fFA{tfl**r*{y z9Kb?EHw|wZ2FVCNf8~K!B{jW_6Q`_Nr~z+wI`I4b{v@ZJ)a-%+90~OU*_I*KA_p*^ zt{&Z;#|odKl|W&-?)GEod*}DS?zN5Y;WD&QJr6TeW-|{%V;gr_R5~F-G=HK zMFf5;l!KeyJ-7s3K2&W`?ZDLYrJzSJ>X8L|1B?kJ;IiOJer}E0+1d59yIw(zOTh@r zxUneDeZFz)m!CEcv-!4}+6b<9JHg+CFTpzJ2HKu_u>DXp*;vmemlwWT5&b{GSWR7t zPj!3euHBto%0)!2T>_^>j15V`5k9C01wP)m>asF(MEt6a6qDF+HWG0_3KcNki$Yz2 z1U2BDBI1`sHX09!LZ1X04i&Gy8)38?K26%fu|jw-r~rXiE%7G&M$#4)7cAmJjEjq- zQ0Oz}RSBgmm@}sEzlj9vfx0lICoZ8`a867qoCO!XPH!nn)~Z%6+sFGO5mp==dIsfe z@b`crxpL^|C~u=$^i*wv^n+HpL4Volb4zzKgRX{frH|I!Q-f?UqaCs2KXj7I2Mb7#aIKa>FVn8 z;SYZWm3$4Lw(rmOe<1yTBeDSz9S&S?F!+f}&%WW%>>a{;ab5ZOxQrowS>-dY?aK&e zB7K>L46_g3N4$nVJUz4q;QiwU h6KslR98C0k2pbMQ>OVp6G2`V67^1RR2)d3G{tqR#r0W0x delta 226 zcmcaIlj+GDM!wU$yj%=Gkd!+ub4}PrzGqB~qMKEiKQS{Nnk>$)#b`I#i9MUqe)2l@ zRz`=(CLG?3j*}ZWW&*{fIG4*hrAVc+q*$furdR_pM=DDiM~Y1gYm{E4t)}y4A+GM^ z%`VMb8QE?DZ78}u`F~3hTND!mLvih7{%G;ZeXV(nev|*S8gekcJONapsXy7O?Gd{! zNbw_v$-3Ef;6 SV3hyJ03ttB2u|MDBL)B+6-S`} diff --git a/backend/__pycache__/document_processor.cpython-312.pyc b/backend/__pycache__/document_processor.cpython-312.pyc index aef023dd66308874db12a3b2fe39e8e857293714..6fe9caa00a150bde460e2307c05ffe762c120f50 100644 GIT binary patch delta 82 zcmbPdf7PD*G%qg~0}y=8nYNKTk=epWzbHSyL|-qzAT=*JC$U_=JijQrxF9h(RllGp kKPxr4q*y;QuQ)S3qa-aSzg#~lF*!RmFGYWIF0-Kw0GJ*fIsgCw delta 27 hcmca=KhK`~G%qg~0}wP@P2I?y$jm6Zc?q+j3;=6Q2O9tY diff --git a/backend/__pycache__/entity_aligner.cpython-312.pyc b/backend/__pycache__/entity_aligner.cpython-312.pyc index 3045fd40b63df63e558d807890b2b9b7f5139081..41f18b2161fdc6ded51ffd7c276820b8e94bd13c 100644 GIT binary patch delta 82 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 H5Agp2&w2!F delta 10902 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 diff --git a/backend/__pycache__/oss_uploader.cpython-312.pyc b/backend/__pycache__/oss_uploader.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e89360137387a48a108f95326833620c01d9e23 GIT binary patch literal 3004 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 Bt|b5f literal 0 HcmV?d00001 diff --git a/backend/knowledge_reasoner.py b/backend/knowledge_reasoner.py new file mode 100644 index 0000000..24c62fe --- /dev/null +++ b/backend/knowledge_reasoner.py @@ -0,0 +1,533 @@ +#!/usr/bin/env python3 +""" +InsightFlow Knowledge Reasoning - Phase 5 +知识推理与问答增强模块 +""" + +import os +import json +import httpx +from typing import List, Dict, Optional, Any +from dataclasses import dataclass +from enum import Enum + +KIMI_API_KEY = os.getenv("KIMI_API_KEY", "") +KIMI_BASE_URL = os.getenv("KIMI_BASE_URL", "https://api.kimi.com/coding") + + +class ReasoningType(Enum): + """推理类型""" + CAUSAL = "causal" # 因果推理 + ASSOCIATIVE = "associative" # 关联推理 + TEMPORAL = "temporal" # 时序推理 + COMPARATIVE = "comparative" # 对比推理 + SUMMARY = "summary" # 总结推理 + + +@dataclass +class ReasoningResult: + """推理结果""" + answer: str + reasoning_type: ReasoningType + confidence: float + evidence: List[Dict] # 支撑证据 + related_entities: List[str] # 相关实体 + gaps: List[str] # 知识缺口 + + +@dataclass +class InferencePath: + """推理路径""" + start_entity: str + end_entity: str + path: List[Dict] # 路径上的节点和关系 + strength: float # 路径强度 + + +class KnowledgeReasoner: + """知识推理引擎""" + + def __init__(self, api_key: str = None, base_url: str = None): + self.api_key = api_key or KIMI_API_KEY + self.base_url = base_url or KIMI_BASE_URL + self.headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + async def _call_llm(self, prompt: str, temperature: float = 0.3) -> str: + """调用 LLM""" + if not self.api_key: + raise ValueError("KIMI_API_KEY not set") + + payload = { + "model": "k2p5", + "messages": [{"role": "user", "content": prompt}], + "temperature": temperature + } + + async with httpx.AsyncClient() as client: + response = await client.post( + f"{self.base_url}/v1/chat/completions", + headers=self.headers, + json=payload, + timeout=120.0 + ) + response.raise_for_status() + result = response.json() + return result["choices"][0]["message"]["content"] + + async def enhanced_qa( + self, + query: str, + project_context: Dict, + graph_data: Dict, + reasoning_depth: str = "medium" + ) -> ReasoningResult: + """ + 增强问答 - 结合图谱推理的问答 + + Args: + query: 用户问题 + project_context: 项目上下文 + graph_data: 知识图谱数据 + reasoning_depth: 推理深度 (shallow/medium/deep) + """ + # 1. 分析问题类型 + analysis = await self._analyze_question(query) + + # 2. 根据问题类型选择推理策略 + if analysis["type"] == "causal": + return await self._causal_reasoning(query, project_context, graph_data) + elif analysis["type"] == "comparative": + return await self._comparative_reasoning(query, project_context, graph_data) + elif analysis["type"] == "temporal": + return await self._temporal_reasoning(query, project_context, graph_data) + else: + return await self._associative_reasoning(query, project_context, graph_data) + + async def _analyze_question(self, query: str) -> Dict: + """分析问题类型和意图""" + prompt = f"""分析以下问题的类型和意图: + +问题:{query} + +请返回 JSON 格式: +{{ + "type": "causal|comparative|temporal|factual|opinion", + "entities": ["提到的实体"], + "intent": "问题意图描述", + "complexity": "simple|medium|complex" +}} + +类型说明: +- causal: 因果类问题(为什么、导致、影响) +- comparative: 对比类问题(区别、比较、优劣) +- temporal: 时序类问题(什么时候、进度、变化) +- factual: 事实类问题(是什么、有哪些) +- opinion: 观点类问题(怎么看、态度、评价)""" + + content = await self._call_llm(prompt, temperature=0.1) + + import re + json_match = re.search(r'\{{.*?\}}', content, re.DOTALL) + if json_match: + try: + return json.loads(json_match.group()) + except: + pass + + return {"type": "factual", "entities": [], "intent": "general", "complexity": "simple"} + + async def _causal_reasoning( + self, + query: str, + project_context: Dict, + graph_data: Dict + ) -> ReasoningResult: + """因果推理 - 分析原因和影响""" + + # 构建因果分析提示 + entities_str = json.dumps(graph_data.get("entities", []), ensure_ascii=False, indent=2) + relations_str = json.dumps(graph_data.get("relations", []), ensure_ascii=False, indent=2) + + prompt = f"""基于以下知识图谱进行因果推理分析: + +## 问题 +{query} + +## 实体 +{entities_str[:2000]} + +## 关系 +{relations_str[:2000]} + +## 项目上下文 +{json.dumps(project_context, ensure_ascii=False, indent=2)[:1500]} + +请进行因果分析,返回 JSON 格式: +{{ + "answer": "详细回答", + "reasoning_chain": ["推理步骤1", "推理步骤2"], + "root_causes": ["根本原因1", "根本原因2"], + "effects": ["影响1", "影响2"], + "confidence": 0.85, + "evidence": ["证据1", "证据2"], + "knowledge_gaps": ["缺失信息1"] +}}""" + + content = await self._call_llm(prompt, temperature=0.3) + + import re + json_match = re.search(r'\{{.*?\}}', content, re.DOTALL) + + if json_match: + try: + data = json.loads(json_match.group()) + return ReasoningResult( + answer=data.get("answer", ""), + reasoning_type=ReasoningType.CAUSAL, + confidence=data.get("confidence", 0.7), + evidence=[{"text": e} for e in data.get("evidence", [])], + related_entities=[], + gaps=data.get("knowledge_gaps", []) + ) + except: + pass + + return ReasoningResult( + answer=content, + reasoning_type=ReasoningType.CAUSAL, + confidence=0.5, + evidence=[], + related_entities=[], + gaps=["无法完成因果推理"] + ) + + async def _comparative_reasoning( + self, + query: str, + project_context: Dict, + graph_data: Dict + ) -> ReasoningResult: + """对比推理 - 比较实体间的异同""" + + prompt = f"""基于以下知识图谱进行对比分析: + +## 问题 +{query} + +## 实体 +{json.dumps(graph_data.get("entities", []), ensure_ascii=False, indent=2)[:2000]} + +## 关系 +{json.dumps(graph_data.get("relations", []), ensure_ascii=False, indent=2)[:1500]} + +请进行对比分析,返回 JSON 格式: +{{ + "answer": "详细对比分析", + "similarities": ["相似点1", "相似点2"], + "differences": ["差异点1", "差异点2"], + "comparison_table": {{"维度": ["实体A值", "实体B值"]}}, + "confidence": 0.85, + "evidence": ["证据1"], + "knowledge_gaps": [] +}}""" + + content = await self._call_llm(prompt, temperature=0.3) + + import re + json_match = re.search(r'\{{.*?\}}', content, re.DOTALL) + + if json_match: + try: + data = json.loads(json_match.group()) + return ReasoningResult( + answer=data.get("answer", ""), + reasoning_type=ReasoningType.COMPARATIVE, + confidence=data.get("confidence", 0.7), + evidence=[{"text": e} for e in data.get("evidence", [])], + related_entities=[], + gaps=data.get("knowledge_gaps", []) + ) + except: + pass + + return ReasoningResult( + answer=content, + reasoning_type=ReasoningType.COMPARATIVE, + confidence=0.5, + evidence=[], + related_entities=[], + gaps=[] + ) + + async def _temporal_reasoning( + self, + query: str, + project_context: Dict, + graph_data: Dict + ) -> ReasoningResult: + """时序推理 - 分析时间线和演变""" + + prompt = f"""基于以下知识图谱进行时序分析: + +## 问题 +{query} + +## 项目时间线 +{json.dumps(project_context.get("timeline", []), ensure_ascii=False, indent=2)[:2000]} + +## 实体提及历史 +{json.dumps(graph_data.get("entities", []), ensure_ascii=False, indent=2)[:1500]} + +请进行时序分析,返回 JSON 格式: +{{ + "answer": "时序分析结果", + "timeline": [{{"date": "时间", "event": "事件", "significance": "重要性"}}], + "trends": ["趋势1", "趋势2"], + "milestones": ["里程碑1"], + "confidence": 0.85, + "evidence": ["证据1"], + "knowledge_gaps": [] +}}""" + + content = await self._call_llm(prompt, temperature=0.3) + + import re + json_match = re.search(r'\{{.*?\}}', content, re.DOTALL) + + if json_match: + try: + data = json.loads(json_match.group()) + return ReasoningResult( + answer=data.get("answer", ""), + reasoning_type=ReasoningType.TEMPORAL, + confidence=data.get("confidence", 0.7), + evidence=[{"text": e} for e in data.get("evidence", [])], + related_entities=[], + gaps=data.get("knowledge_gaps", []) + ) + except: + pass + + return ReasoningResult( + answer=content, + reasoning_type=ReasoningType.TEMPORAL, + confidence=0.5, + evidence=[], + related_entities=[], + gaps=[] + ) + + async def _associative_reasoning( + self, + query: str, + project_context: Dict, + graph_data: Dict + ) -> ReasoningResult: + """关联推理 - 发现实体间的隐含关联""" + + prompt = f"""基于以下知识图谱进行关联分析: + +## 问题 +{query} + +## 实体 +{json.dumps(graph_data.get("entities", [])[:20], ensure_ascii=False, indent=2)} + +## 关系 +{json.dumps(graph_data.get("relations", [])[:30], ensure_ascii=False, indent=2)} + +请进行关联推理,发现隐含联系,返回 JSON 格式: +{{ + "answer": "关联分析结果", + "direct_connections": ["直接关联1"], + "indirect_connections": ["间接关联1"], + "inferred_relations": [{{"source": "A", "target": "B", "relation": "可能关系", "confidence": 0.7}}], + "confidence": 0.85, + "evidence": ["证据1"], + "knowledge_gaps": [] +}}""" + + content = await self._call_llm(prompt, temperature=0.4) + + import re + json_match = re.search(r'\{{.*?\}}', content, re.DOTALL) + + if json_match: + try: + data = json.loads(json_match.group()) + return ReasoningResult( + answer=data.get("answer", ""), + reasoning_type=ReasoningType.ASSOCIATIVE, + confidence=data.get("confidence", 0.7), + evidence=[{"text": e} for e in data.get("evidence", [])], + related_entities=[], + gaps=data.get("knowledge_gaps", []) + ) + except: + pass + + return ReasoningResult( + answer=content, + reasoning_type=ReasoningType.ASSOCIATIVE, + confidence=0.5, + evidence=[], + related_entities=[], + gaps=[] + ) + + def find_inference_paths( + self, + start_entity: str, + end_entity: str, + graph_data: Dict, + max_depth: int = 3 + ) -> List[InferencePath]: + """ + 发现两个实体之间的推理路径 + + 使用 BFS 在关系图中搜索路径 + """ + entities = {e["id"]: e for e in graph_data.get("entities", [])} + relations = graph_data.get("relations", []) + + # 构建邻接表 + adj = {} + for r in relations: + src = r.get("source_id") or r.get("source") + tgt = r.get("target_id") or r.get("target") + if src not in adj: + adj[src] = [] + if tgt not in adj: + adj[tgt] = [] + adj[src].append({"target": tgt, "relation": r.get("type", "related"), "data": r}) + # 无向图也添加反向 + adj[tgt].append({"target": src, "relation": r.get("type", "related"), "data": r, "reverse": True}) + + # BFS 搜索路径 + from collections import deque + paths = [] + queue = deque([(start_entity, [{"entity": start_entity, "relation": None}])]) + visited = {start_entity} + + while queue and len(paths) < 5: + current, path = queue.popleft() + + if current == end_entity and len(path) > 1: + # 找到一条路径 + paths.append(InferencePath( + start_entity=start_entity, + end_entity=end_entity, + path=path, + strength=self._calculate_path_strength(path) + )) + continue + + if len(path) >= max_depth: + continue + + for neighbor in adj.get(current, []): + next_entity = neighbor["target"] + if next_entity not in [p["entity"] for p in path]: # 避免循环 + new_path = path + [{ + "entity": next_entity, + "relation": neighbor["relation"], + "relation_data": neighbor.get("data", {}) + }] + queue.append((next_entity, new_path)) + + # 按强度排序 + paths.sort(key=lambda p: p.strength, reverse=True) + return paths + + def _calculate_path_strength(self, path: List[Dict]) -> float: + """计算路径强度""" + if len(path) < 2: + return 0.0 + + # 路径越短越强 + length_factor = 1.0 / len(path) + + # 关系置信度 + confidence_sum = 0 + confidence_count = 0 + for node in path[1:]: # 跳过第一个节点 + rel_data = node.get("relation_data", {}) + if "confidence" in rel_data: + confidence_sum += rel_data["confidence"] + confidence_count += 1 + + confidence_factor = (confidence_sum / confidence_count) if confidence_count > 0 else 0.5 + + return length_factor * confidence_factor + + async def summarize_project( + self, + project_context: Dict, + graph_data: Dict, + summary_type: str = "comprehensive" + ) -> Dict: + """ + 项目智能总结 + + Args: + summary_type: comprehensive/executive/technical/risk + """ + type_prompts = { + "comprehensive": "全面总结项目的所有方面", + "executive": "高管摘要,关注关键决策和风险", + "technical": "技术总结,关注架构和技术栈", + "risk": "风险分析,关注潜在问题和依赖" + } + + prompt = f"""请对以下项目进行{type_prompts.get(summary_type, "全面总结")}: + +## 项目信息 +{json.dumps(project_context, ensure_ascii=False, indent=2)[:3000]} + +## 知识图谱 +实体数: {len(graph_data.get("entities", []))} +关系数: {len(graph_data.get("relations", []))} + +请返回 JSON 格式: +{{ + "overview": "项目概述", + "key_points": ["要点1", "要点2"], + "key_entities": ["关键实体1"], + "risks": ["风险1"], + "recommendations": ["建议1"], + "confidence": 0.85 +}}""" + + content = await self._call_llm(prompt, temperature=0.3) + + import re + json_match = re.search(r'\{{.*?\}}', content, re.DOTALL) + + if json_match: + try: + return json.loads(json_match.group()) + except: + pass + + return { + "overview": content, + "key_points": [], + "key_entities": [], + "risks": [], + "recommendations": [], + "confidence": 0.5 + } + + +# Singleton instance +_reasoner = None + + +def get_knowledge_reasoner() -> KnowledgeReasoner: + global _reasoner + if _reasoner is None: + _reasoner = KnowledgeReasoner() + return _reasoner \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 18ad1ca..efa664c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -61,6 +61,12 @@ try: except ImportError: LLM_CLIENT_AVAILABLE = False +try: + from knowledge_reasoner import get_knowledge_reasoner, KnowledgeReasoner, ReasoningType + REASONER_AVAILABLE = True +except ImportError: + REASONER_AVAILABLE = False + app = FastAPI(title="InsightFlow", version="0.3.0") app.add_middleware( @@ -983,14 +989,15 @@ async def get_entity_mentions(entity_id: str): async def health_check(): return { "status": "ok", - "version": "0.5.0", - "phase": "Phase 5 - Timeline View", + "version": "0.6.0", + "phase": "Phase 5 - Knowledge Reasoning", "oss_available": OSS_AVAILABLE, "tingwu_available": TINGWU_AVAILABLE, "db_available": DB_AVAILABLE, "doc_processor_available": DOC_PROCESSOR_AVAILABLE, "aligner_available": ALIGNER_AVAILABLE, - "llm_client_available": LLM_CLIENT_AVAILABLE + "llm_client_available": LLM_CLIENT_AVAILABLE, + "reasoner_available": REASONER_AVAILABLE } @@ -1336,6 +1343,164 @@ async def get_entity_timeline(entity_id: str): } +# ==================== Phase 5: 知识推理与问答增强 API ==================== + +class ReasoningQuery(BaseModel): + query: str + reasoning_depth: str = "medium" # shallow/medium/deep + stream: bool = False + + +@app.post("/api/v1/projects/{project_id}/reasoning/query") +async def reasoning_query(project_id: str, query: ReasoningQuery): + """ + 增强问答 - 基于知识推理的智能问答 + + 支持多种推理类型: + - 因果推理:分析原因和影响 + - 对比推理:比较实体间的异同 + - 时序推理:分析时间线和演变 + - 关联推理:发现隐含关联 + """ + if not DB_AVAILABLE or not REASONER_AVAILABLE: + raise HTTPException(status_code=500, detail="Knowledge reasoner not available") + + db = get_db_manager() + reasoner = get_knowledge_reasoner() + + project = db.get_project(project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + # 获取项目上下文 + project_context = db.get_project_summary(project_id) + + # 获取知识图谱数据 + entities = db.list_project_entities(project_id) + relations = db.list_project_relations(project_id) + + graph_data = { + "entities": [{"id": e.id, "name": e.name, "type": e.type, "definition": e.definition} for e in entities], + "relations": relations + } + + # 执行增强问答 + result = await reasoner.enhanced_qa( + query=query.query, + project_context=project_context, + graph_data=graph_data, + reasoning_depth=query.reasoning_depth + ) + + return { + "answer": result.answer, + "reasoning_type": result.reasoning_type.value, + "confidence": result.confidence, + "evidence": result.evidence, + "knowledge_gaps": result.gaps, + "project_id": project_id + } + + +@app.post("/api/v1/projects/{project_id}/reasoning/inference-path") +async def find_inference_path( + project_id: str, + start_entity: str, + end_entity: str +): + """ + 发现两个实体之间的推理路径 + + 在知识图谱中搜索从 start_entity 到 end_entity 的路径 + """ + if not DB_AVAILABLE or not REASONER_AVAILABLE: + raise HTTPException(status_code=500, detail="Knowledge reasoner not available") + + db = get_db_manager() + reasoner = get_knowledge_reasoner() + + project = db.get_project(project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + # 获取知识图谱数据 + entities = db.list_project_entities(project_id) + relations = db.list_project_relations(project_id) + + graph_data = { + "entities": [{"id": e.id, "name": e.name, "type": e.type} for e in entities], + "relations": relations + } + + # 查找推理路径 + paths = reasoner.find_inference_paths(start_entity, end_entity, graph_data) + + return { + "start_entity": start_entity, + "end_entity": end_entity, + "paths": [ + { + "path": path.path, + "strength": path.strength, + "path_description": " -> ".join([p["entity"] for p in path.path]) + } + for path in paths[:5] # 最多返回5条路径 + ], + "total_paths": len(paths) + } + + +class SummaryRequest(BaseModel): + summary_type: str = "comprehensive" # comprehensive/executive/technical/risk + + +@app.post("/api/v1/projects/{project_id}/reasoning/summary") +async def project_summary(project_id: str, req: SummaryRequest): + """ + 项目智能总结 + + 根据类型生成不同侧重点的总结: + - comprehensive: 全面总结 + - executive: 高管摘要 + - technical: 技术总结 + - risk: 风险分析 + """ + if not DB_AVAILABLE or not REASONER_AVAILABLE: + raise HTTPException(status_code=500, detail="Knowledge reasoner not available") + + db = get_db_manager() + reasoner = get_knowledge_reasoner() + + project = db.get_project(project_id) + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + # 获取项目上下文 + project_context = db.get_project_summary(project_id) + + # 获取知识图谱数据 + entities = db.list_project_entities(project_id) + relations = db.list_project_relations(project_id) + + graph_data = { + "entities": [{"id": e.id, "name": e.name, "type": e.type} for e in entities], + "relations": relations + } + + # 生成总结 + summary = await reasoner.summarize_project( + project_context=project_context, + graph_data=graph_data, + summary_type=req.summary_type + ) + + return { + "project_id": project_id, + "summary_type": req.summary_type, + **summary + } + + # Serve frontend - MUST be last to not override API routes app.mount("/", StaticFiles(directory="frontend", html=True), name="frontend") diff --git a/frontend/app.js b/frontend/app.js index ae71ff3..a88b16a 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -924,6 +924,7 @@ window.switchView = function(viewName) { document.getElementById('workbenchView').style.display = 'none'; document.getElementById('knowledgeBaseView').classList.remove('show'); document.getElementById('timelineView').classList.remove('show'); + document.getElementById('reasoningView').classList.remove('active'); // 显示选中的视图 if (viewName === 'workbench') { @@ -937,6 +938,9 @@ window.switchView = function(viewName) { document.getElementById('timelineView').classList.add('show'); document.querySelector('.sidebar-btn:nth-child(3)').classList.add('active'); loadTimeline(); + } else if (viewName === 'reasoning') { + document.getElementById('reasoningView').classList.add('active'); + document.querySelector('.sidebar-btn:nth-child(4)').classList.add('active'); } }; @@ -1109,3 +1113,265 @@ window.deleteGlossaryTerm = async function(termId) { console.error('Delete glossary term failed:', err); } }; + +// ==================== Phase 5: Knowledge Reasoning ==================== + +window.submitReasoningQuery = async function() { + const input = document.getElementById('reasoningInput'); + const depth = document.getElementById('reasoningDepth').value; + const query = input.value.trim(); + + if (!query) return; + + const resultsDiv = document.getElementById('reasoningResults'); + + // 显示加载状态 + resultsDiv.innerHTML = ` +
+
+ +
+

正在进行知识推理...

+
+ `; + + try { + const res = await fetch(`${API_BASE}/projects/${currentProject.id}/reasoning/query`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, reasoning_depth: depth }) + }); + + if (!res.ok) throw new Error('Reasoning failed'); + + const data = await res.json(); + renderReasoningResult(data); + + } catch (err) { + console.error('Reasoning query failed:', err); + resultsDiv.innerHTML = ` +
+

推理失败,请稍后重试

+
+ `; + } +}; + +function renderReasoningResult(data) { + const resultsDiv = document.getElementById('reasoningResults'); + + const typeLabels = { + 'causal': '🔍 因果推理', + 'comparative': '⚖️ 对比推理', + 'temporal': '⏱️ 时序推理', + 'associative': '🔗 关联推理', + 'summary': '📝 总结推理' + }; + + const typeLabel = typeLabels[data.reasoning_type] || '🤔 智能分析'; + const confidencePercent = Math.round(data.confidence * 100); + + let evidenceHtml = ''; + if (data.evidence && data.evidence.length > 0) { + evidenceHtml = ` +
+

📋 支撑证据

+ ${data.evidence.map(e => `
${e.text || e}
`).join('')} +
+ `; + } + + let gapsHtml = ''; + if (data.knowledge_gaps && data.knowledge_gaps.length > 0) { + gapsHtml = ` +
+

⚠️ 知识缺口

+
    + ${data.knowledge_gaps.map(g => `
  • ${g}
  • `).join('')} +
+
+ `; + } + + resultsDiv.innerHTML = ` +
+
+
+ ${typeLabel} +
+
+ 置信度: ${confidencePercent}% +
+
+
+ ${data.answer.replace(/\n/g, '
')} +
+ ${evidenceHtml} + ${gapsHtml} +
+ `; +} + +window.clearReasoningResult = function() { + document.getElementById('reasoningResults').innerHTML = ''; + document.getElementById('reasoningInput').value = ''; + document.getElementById('inferencePathsSection').style.display = 'none'; +}; + +window.generateSummary = async function(summaryType) { + const resultsDiv = document.getElementById('reasoningResults'); + + // 显示加载状态 + resultsDiv.innerHTML = ` +
+
+ +
+

正在生成项目总结...

+
+ `; + + try { + const res = await fetch(`${API_BASE}/projects/${currentProject.id}/reasoning/summary`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ summary_type: summaryType }) + }); + + if (!res.ok) throw new Error('Summary failed'); + + const data = await res.json(); + renderSummaryResult(data); + + } catch (err) { + console.error('Summary generation failed:', err); + resultsDiv.innerHTML = ` +
+

总结生成失败,请稍后重试

+
+ `; + } +}; + +function renderSummaryResult(data) { + const resultsDiv = document.getElementById('reasoningResults'); + + const typeLabels = { + 'comprehensive': '📋 全面总结', + 'executive': '💼 高管摘要', + 'technical': '⚙️ 技术总结', + 'risk': '⚠️ 风险分析' + }; + + const typeLabel = typeLabels[data.summary_type] || '📝 项目总结'; + + let keyPointsHtml = ''; + if (data.key_points && data.key_points.length > 0) { + keyPointsHtml = ` +
+

📌 关键要点

+
    + ${data.key_points.map(p => `
  • ${p}
  • `).join('')} +
+
+ `; + } + + let risksHtml = ''; + if (data.risks && data.risks.length > 0) { + risksHtml = ` +
+

⚠️ 风险与问题

+
    + ${data.risks.map(r => `
  • ${r}
  • `).join('')} +
+
+ `; + } + + let recommendationsHtml = ''; + if (data.recommendations && data.recommendations.length > 0) { + recommendationsHtml = ` +
+

💡 建议

+ ${data.recommendations.map(r => `
${r}
`).join('')} +
+ `; + } + + resultsDiv.innerHTML = ` +
+
+
+ ${typeLabel} +
+
+ 置信度: ${Math.round(data.confidence * 100)}% +
+
+
+ ${data.overview ? data.overview.replace(/\n/g, '
') : ''} +
+ ${keyPointsHtml} + ${risksHtml} + ${recommendationsHtml} +
+ `; +} + +window.findInferencePath = async function(startEntity, endEntity) { + const pathsSection = document.getElementById('inferencePathsSection'); + const pathsList = document.getElementById('inferencePathsList'); + + pathsSection.style.display = 'block'; + pathsList.innerHTML = '

正在搜索关联路径...

'; + + try { + const res = await fetch( + `${API_BASE}/projects/${currentProject.id}/reasoning/inference-path?start_entity=${encodeURIComponent(startEntity)}&end_entity=${encodeURIComponent(endEntity)}` + ); + + if (!res.ok) throw new Error('Path finding failed'); + + const data = await res.json(); + renderInferencePaths(data); + + } catch (err) { + console.error('Path finding failed:', err); + pathsList.innerHTML = '

路径搜索失败

'; + } +}; + +function renderInferencePaths(data) { + const pathsList = document.getElementById('inferencePathsList'); + + if (!data.paths || data.paths.length === 0) { + pathsList.innerHTML = '

未找到关联路径

'; + return; + } + + pathsList.innerHTML = data.paths.map((path, idx) => { + const strengthPercent = Math.round(path.strength * 100); + const pathHtml = path.path.map((node, i) => { + if (i === 0) { + return `${node.entity}`; + } + return ` + → ${node.relation} → + ${node.entity} + `; + }).join(''); + + return ` +
+
+ 路径 ${idx + 1} + 关联强度: ${strengthPercent}% +
+
+ ${pathHtml} +
+
+ `; + }).join(''); +} diff --git a/frontend/workbench.html b/frontend/workbench.html index d8af73a..c93396f 100644 --- a/frontend/workbench.html +++ b/frontend/workbench.html @@ -1020,6 +1020,237 @@ .timeline-legend-dot.mention { background: #7b2cbf; } .timeline-legend-dot.relation { background: #00d4ff; } + /* Phase 5: Reasoning Panel */ + .reasoning-panel { + display: none; + flex-direction: column; + width: 100%; + height: 100%; + padding: 24px; + overflow-y: auto; + } + .reasoning-panel.active { + display: flex; + } + .reasoning-header { + margin-bottom: 24px; + } + .reasoning-header h2 { + font-size: 1.5rem; + margin-bottom: 8px; + } + .reasoning-types { + display: flex; + gap: 12px; + margin-bottom: 24px; + flex-wrap: wrap; + } + .reasoning-type-btn { + padding: 10px 20px; + background: #141414; + border: 1px solid #222; + border-radius: 8px; + color: #888; + cursor: pointer; + transition: all 0.2s; + font-size: 0.9rem; + } + .reasoning-type-btn:hover, .reasoning-type-btn.active { + border-color: #00d4ff; + color: #00d4ff; + background: #00d4ff11; + } + .reasoning-query-box { + background: #141414; + border: 1px solid #222; + border-radius: 12px; + padding: 20px; + margin-bottom: 24px; + } + .reasoning-input { + width: 100%; + background: transparent; + border: none; + color: #e0e0e0; + font-size: 1rem; + resize: none; + outline: none; + min-height: 60px; + font-family: inherit; + } + .reasoning-input::placeholder { + color: #666; + } + .reasoning-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid #222; + } + .reasoning-options { + display: flex; + gap: 16px; + align-items: center; + } + .reasoning-option { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.85rem; + color: #888; + } + .reasoning-option select { + background: #1a1a1a; + border: 1px solid #333; + color: #e0e0e0; + padding: 4px 8px; + border-radius: 4px; + } + .reasoning-result { + background: #141414; + border: 1px solid #222; + border-radius: 12px; + padding: 24px; + margin-bottom: 16px; + } + .reasoning-result-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px solid #222; + } + .reasoning-result-type { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.9rem; + color: #00d4ff; + } + .reasoning-confidence { + padding: 4px 12px; + background: #00d4ff22; + border-radius: 20px; + font-size: 0.8rem; + color: #00d4ff; + } + .reasoning-answer { + line-height: 1.8; + color: #e0e0e0; + font-size: 1rem; + margin-bottom: 20px; + } + .reasoning-evidence { + background: #0a0a0a; + border-radius: 8px; + padding: 16px; + margin-top: 16px; + } + .reasoning-evidence h4 { + font-size: 0.85rem; + color: #888; + margin-bottom: 12px; + } + .evidence-item { + padding: 10px 12px; + background: #141414; + border-radius: 6px; + margin-bottom: 8px; + font-size: 0.85rem; + color: #aaa; + border-left: 3px solid #00d4ff; + } + .reasoning-gaps { + margin-top: 16px; + padding: 12px; + background: #ff6b6b11; + border: 1px solid #ff6b6b33; + border-radius: 8px; + } + .reasoning-gaps h4 { + font-size: 0.85rem; + color: #ff6b6b; + margin-bottom: 8px; + } + .reasoning-gaps ul { + margin: 0; + padding-left: 20px; + font-size: 0.85rem; + color: #aaa; + } + .reasoning-summary-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 16px; + margin-bottom: 24px; + } + .summary-card { + background: #141414; + border: 1px solid #222; + border-radius: 12px; + padding: 20px; + cursor: pointer; + transition: all 0.2s; + } + .summary-card:hover { + border-color: #00d4ff; + background: #1a1a1a; + } + .summary-card h4 { + color: #00d4ff; + font-size: 0.9rem; + margin-bottom: 8px; + } + .summary-card p { + color: #888; + font-size: 0.85rem; + } + .inference-paths { + background: #141414; + border: 1px solid #222; + border-radius: 12px; + padding: 20px; + margin-bottom: 16px; + } + .inference-path { + padding: 16px; + background: #0a0a0a; + border-radius: 8px; + margin-bottom: 12px; + } + .inference-path-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + .path-strength { + padding: 4px 10px; + background: #7b2cbf33; + border-radius: 20px; + font-size: 0.8rem; + color: #7b2cbf; + } + .path-visual { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + font-size: 0.9rem; + } + .path-node { + padding: 6px 12px; + background: #1a1a1a; + border-radius: 6px; + color: #e0e0e0; + } + .path-arrow { + color: #00d4ff; + } + /* Typing indicator */ .typing-indicator { display: flex; @@ -1058,6 +1289,7 @@ + @@ -1175,6 +1407,9 @@ + + + + + +
+
+

🧠 智能推理与问答

+

基于知识图谱的深度推理分析

+
+ +
+
+

📋 全面总结

+

生成项目的完整概览和关键洞察

+
+
+

💼 高管摘要

+

关注关键决策、风险和行动项

+
+
+

⚙️ 技术总结

+

分析技术架构、依赖和实现细节

+
+
+

⚠️ 风险分析

+

识别潜在风险、依赖和缓解建议

+
+
+ +
+ + +
+
+
+ + +
+
+ +
+ + +
+
+
+ +
+ + +