ego commited on
Commit
1fe2fca
·
1 Parent(s): 4822069

fix: improve graph generation reliability (strip thinking blocks, add retry, robust DOT extraction)

Browse files
Files changed (4) hide show
  1. core/models.py +2 -2
  2. core/retriever.py +2 -2
  3. core/visualizer.py +64 -13
  4. requirements.txt +1 -2
core/models.py CHANGED
@@ -2,7 +2,7 @@ import os
2
  import streamlit as st
3
  from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings
4
 
5
- def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b", enable_thinking: bool = True):
6
  api_key = os.getenv("NV_API_KEY")
7
  if not api_key and "NV_API_KEY" in st.secrets:
8
  api_key = st.secrets["NV_API_KEY"]
@@ -15,7 +15,7 @@ def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b", enable_thinking:
15
  temperature=0,
16
  seed=42,
17
  max_tokens=16384,
18
- extra_body={"chat_template_kwargs": {"enable_thinking": enable_thinking}},
19
  api_key=api_key
20
  )
21
 
 
2
  import streamlit as st
3
  from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings
4
 
5
+ def get_llm(model_name: str = "nvidia/nemotron-3-nano-30b-a3b"):
6
  api_key = os.getenv("NV_API_KEY")
7
  if not api_key and "NV_API_KEY" in st.secrets:
8
  api_key = st.secrets["NV_API_KEY"]
 
15
  temperature=0,
16
  seed=42,
17
  max_tokens=16384,
18
+ extra_body={"chat_template_kwargs": {"enable_thinking":True}},
19
  api_key=api_key
20
  )
21
 
core/retriever.py CHANGED
@@ -1,7 +1,7 @@
1
  import os
2
  from langchain_community.document_loaders import PyPDFLoader
3
  from langchain_text_splitters import RecursiveCharacterTextSplitter
4
- from langchain_chroma import Chroma
5
  from core.models import get_embeddings
6
  import tempfile
7
 
@@ -59,7 +59,7 @@ class Retriever:
59
  self.vector_store = Chroma.from_documents(
60
  documents=splits,
61
  embedding=self.embeddings,
62
- collection_name="knowledge_base",
63
  )
64
  else:
65
  self.vector_store.add_documents(splits)
 
1
  import os
2
  from langchain_community.document_loaders import PyPDFLoader
3
  from langchain_text_splitters import RecursiveCharacterTextSplitter
4
+ from langchain_community.vectorstores import Chroma
5
  from core.models import get_embeddings
6
  import tempfile
7
 
 
59
  self.vector_store = Chroma.from_documents(
60
  documents=splits,
61
  embedding=self.embeddings,
62
+ collection_name="knowledge_base"
63
  )
64
  else:
65
  self.vector_store.add_documents(splits)
core/visualizer.py CHANGED
@@ -3,20 +3,71 @@ from core.models import get_llm
3
  from langchain_core.output_parsers import StrOutputParser
4
  from prompts import GRAPH_PROMPT
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  class KnowledgeGraphGenerator:
7
  def __init__(self):
8
- # 图谱生成需要严格的 DOT 结构输出,必须关闭 thinking 模式,否则 <think> 块会污染结果
9
- self.llm = get_llm(enable_thinking=False)
10
 
11
- def generate_graph(self, text):
 
12
  chain = GRAPH_PROMPT | self.llm | StrOutputParser()
13
-
14
- dot_code = chain.invoke({"text": text})
15
-
16
- # 过滤残留的 <think>...</think> 块
17
- dot_code = re.sub(r"<think>.*?</think>", "", dot_code, flags=re.DOTALL)
18
- dot_code = dot_code.replace("```dot", "").replace("```", "").strip()
19
- if "digraph" not in dot_code:
20
- dot_code = f'digraph G {{ {dot_code} }}'
21
-
22
- return dot_code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  from langchain_core.output_parsers import StrOutputParser
4
  from prompts import GRAPH_PROMPT
5
 
6
+
7
+ def _extract_dot(raw: str) -> str:
8
+ """从模型输出中提取干净的 DOT 代码。
9
+
10
+ 处理以下情况:
11
+ 1. <think>...</think> reasoning 块 (enable_thinking=True 时产生)
12
+ 2. ```dot ... ``` 或 ``` ... ``` Markdown 代码块
13
+ 3. 纯文本中直接包含 digraph {...}
14
+ """
15
+ # 1. 剥离 <think>...</think> 块(贪婪匹配,跨行)
16
+ raw = re.sub(r"<think>.*?</think>", "", raw, flags=re.DOTALL).strip()
17
+
18
+ # 2. 优先提取 Markdown 代码块内容
19
+ md_match = re.search(r"```(?:dot)?\s*(digraph\s+\w+\s*\{.*?\})\s*```", raw, re.DOTALL)
20
+ if md_match:
21
+ return md_match.group(1).strip()
22
+
23
+ # 3. 直接提取 digraph { ... } 块(处理嵌套花括号)
24
+ digraph_match = re.search(r"(digraph\s+\w+\s*\{)", raw)
25
+ if digraph_match:
26
+ start = digraph_match.start()
27
+ depth = 0
28
+ for i, ch in enumerate(raw[start:], start=start):
29
+ if ch == "{":
30
+ depth += 1
31
+ elif ch == "}":
32
+ depth -= 1
33
+ if depth == 0:
34
+ return raw[start : i + 1].strip()
35
+
36
+ # 4. 兜底:清理 Markdown 标记后返回原文
37
+ cleaned = raw.replace("```dot", "").replace("```", "").strip()
38
+ if "digraph" not in cleaned:
39
+ cleaned = f"digraph G {{ {cleaned} }}"
40
+ return cleaned
41
+
42
+
43
  class KnowledgeGraphGenerator:
44
  def __init__(self):
45
+ self.llm = get_llm()
 
46
 
47
+ def generate_graph(self, text: str, max_retries: int = 3) -> str:
48
+ """生成 DOT 格式知识图谱,失败时自动重试。"""
49
  chain = GRAPH_PROMPT | self.llm | StrOutputParser()
50
+
51
+ last_error = None
52
+ for attempt in range(1, max_retries + 1):
53
+ try:
54
+ raw = chain.invoke({"text": text})
55
+ dot_code = _extract_dot(raw)
56
+
57
+ # 基本合法性校验:必须包含 digraph 和至少一条边
58
+ if "digraph" in dot_code and "->" in dot_code:
59
+ return dot_code
60
+
61
+ last_error = f"Attempt {attempt}: DOT output invalid (no edges found)."
62
+ except Exception as e:
63
+ last_error = f"Attempt {attempt}: {e}"
64
+
65
+ # 全部重试失败,返回最小合法占位图
66
+ return (
67
+ 'digraph G {\n'
68
+ ' rankdir=LR;\n'
69
+ ' node [style="filled", fillcolor="#FFEBEE", shape="box"];\n'
70
+ f' "Generation Failed" -> "Please Retry" [label="error"];\n'
71
+ f' "Reason" [label="{last_error[:80]}"];\n'
72
+ '}'
73
+ )
requirements.txt CHANGED
@@ -4,8 +4,7 @@ langchain-community
4
  langchain-nvidia-ai-endpoints
5
  langchain-core
6
  langgraph
7
- chromadb==0.5.23
8
- langchain-chroma
9
  pypdf
10
  graphviz
11
  python-dotenv
 
4
  langchain-nvidia-ai-endpoints
5
  langchain-core
6
  langgraph
7
+ chromadb
 
8
  pypdf
9
  graphviz
10
  python-dotenv