Toggle navigation
Toggle navigation
This project
Loading...
Sign in
万朱浩
/
Venue-Ops
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
马一丁
2025-12-20 13:19:58 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
5a47bec9f4585ece5ab1483df2f35ed84c7e3f17
5a47bec9
1 parent
4c1147bc
Add Comments
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
80 additions
and
11 deletions
ReportEngine/agent.py
ReportEngine/graphrag/__init__.py
ReportEngine/graphrag/forum_parser.py
ReportEngine/graphrag/graph_builder.py
ReportEngine/graphrag/graph_storage.py
ReportEngine/graphrag/prompts.py
ReportEngine/graphrag/query_engine.py
ReportEngine/graphrag/state_parser.py
ReportEngine/nodes/graphrag_query_node.py
app.py
templates/index.html
ReportEngine/agent.py
View file @
5a47bec
...
...
@@ -598,6 +598,7 @@ class ReportAgent:
emit
(
'stage'
,
{
'stage'
:
'storage_ready'
,
'run_dir'
:
str
(
run_dir
)})
# ==================== GraphRAG 初始化 ====================
# 根据配置开关决定是否启用图谱构建/查询(需 .env 设置 GRAPHRAG_ENABLED=True)
graphrag_enabled
=
getattr
(
self
.
config
,
'GRAPHRAG_ENABLED'
,
False
)
knowledge_graph
=
None
graphrag_query_node
=
None
...
...
@@ -607,6 +608,7 @@ class ReportAgent:
emit
(
'stage'
,
{
'stage'
:
'graphrag_building'
,
'message'
:
'正在构建知识图谱'
})
try
:
# 将 state_*.json + forum.log 转为结构化图谱,并立即落盘 graphrag.json
knowledge_graph
=
self
.
_build_knowledge_graph
(
query
,
normalized_reports
,
forum_logs
,
run_dir
)
...
...
@@ -687,6 +689,7 @@ class ReportAgent:
'emphasis'
:
emphasis_value
}
# 先让 GraphRAG 节点多轮查询,再把结果附加到章节上下文
graph_results
=
graphrag_query_node
.
run
(
section_info
,
{
...
...
@@ -699,7 +702,7 @@ class ReportAgent:
)
if
graph_results
and
graph_results
.
get
(
'total_nodes'
,
0
)
>
0
:
# 将图谱结果注入生成上下文
# 将图谱结果注入生成上下文
,后续章节 LLM 自动使用增强提示词
chapter_context
[
'graph_results'
]
=
graph_results
chapter_context
[
'graph_enhancement_prompt'
]
=
format_graph_results_for_prompt
(
graph_results
)
logger
.
info
(
f
"章节 {section.title} GraphRAG 查询完成: {graph_results.get('total_nodes', 0)} 节点"
)
...
...
ReportEngine/graphrag/__init__.py
View file @
5a47bec
...
...
@@ -2,6 +2,11 @@
GraphRAG 知识图谱模块
提供基于结构化数据的知识图谱构建、存储与查询功能。
典型用法:
1) 使用 `StateParser`/`ForumParser` 解析三引擎 state JSON 与 forum.log;
2) 调用 `GraphBuilder.build` 生成纯结构化的图对象;
3) 通过 `GraphStorage.save/load` 持久化或读取图数据;
4) 以 `QueryEngine` 在章节侧执行多轮图查询。
"""
from
.state_parser
import
StateParser
,
ParsedState
,
ParsedSection
,
SearchRecord
...
...
ReportEngine/graphrag/forum_parser.py
View file @
5a47bec
...
...
@@ -2,6 +2,8 @@
Forum 日志解析器
解析 forum.log 文件,提取结构化的讨论记录用于构建知识图谱。
日志与 GraphRAG 的关系:仅将主持人/三引擎发言转为结构化节点,
用于补充 Host 总结或跨引擎观点。
"""
from
dataclasses
import
dataclass
...
...
@@ -41,6 +43,7 @@ class ForumParser:
解析 forum.log,提取结构化的讨论记录。
日志格式: [HH:MM:SS] [SPEAKER] content
SPEAKER 需属于 VALID_SPEAKERS;非规范行会被忽略,确保图谱不被噪音污染。
"""
# 匹配日志行的正则表达式
...
...
ReportEngine/graphrag/graph_builder.py
View file @
5a47bec
...
...
@@ -19,6 +19,10 @@ class GraphBuilder:
基于已有的结构化数据(State JSON、Forum 日志)构建图谱,
无需 LLM 进行实体/关系提取。
ReportAgent 在 _build_knowledge_graph 中调用本构建器,将 load_input_files
提前解析好的 ParsedState / ForumEntry 转为 Graph 对象,再交由 GraphStorage
落盘并供 GraphRAGQueryNode 查询。
节点类型(5种):
- topic: 用户查询主题
- engine: 四个引擎来源 (insight/media/query/host)
...
...
@@ -108,7 +112,7 @@ class GraphBuilder:
if
not
search
.
query
:
continue
# 搜索词去重
# 搜索词去重
(同一段落相同查询仅保留首条,避免图谱冗余)
query_key
=
search
.
query
.
strip
()
.
lower
()
if
query_key
in
seen_queries
:
continue
...
...
ReportEngine/graphrag/graph_storage.py
View file @
5a47bec
...
...
@@ -107,7 +107,12 @@ class Edge:
class
Graph
:
"""知识图谱"""
"""
知识图谱
仅负责存储节点/边与邻接表,不依赖外部数据库,便于在章节侧内存查询。
邻接表 _adjacency 用于 QueryEngine 按深度扩展邻居节点。
"""
def
__init__
(
self
):
self
.
_nodes
:
Dict
[
str
,
Node
]
=
{}
...
...
@@ -290,7 +295,13 @@ class Graph:
class
GraphStorage
:
"""图谱存储管理器"""
"""
图谱存储管理器
将 Graph 对象序列化为 JSON(graphrag.json),路径与 ChapterStorage 输出目录一致,
便于 Web/Report 引擎共享。支持按报告ID查找、列举最新图谱,供 Flask API 或
GraphRAGQueryNode 直接读取。
"""
FILENAME
=
"graphrag.json"
...
...
@@ -394,6 +405,11 @@ class GraphStorage:
Returns:
图谱文件路径,未找到返回 None
工作方式:
1) 优先匹配目录名是否含 report_id(兼容 _/- 差异);
2) 否则读取 graphrag.json 内 task_id/report_id 做兜底匹配;
适配 Agent 运行目录命名不一致的场景。
"""
# 在章节目录中搜索(与 ChapterStorage 保持一致)
chapters_dir
=
self
.
chapters_dir
...
...
@@ -442,6 +458,8 @@ class GraphStorage:
Returns:
最新图谱文件路径,未找到返回 None
根据文件修改时间排序,用于前端“最近一次生成”快速预览。
"""
chapters_dir
=
self
.
chapters_dir
if
not
chapters_dir
.
exists
():
...
...
ReportEngine/graphrag/prompts.py
View file @
5a47bec
...
...
@@ -121,6 +121,9 @@ def format_graph_results_for_prompt(graph_results: dict) -> str:
Returns:
格式化的字符串
供 ReportAgent 在章节生成前注入 `graph_enhancement_prompt`,
将多轮查询结果以结构化文本交给章节 LLM,避免直接传递大 JSON。
"""
if
not
graph_results
:
return
""
...
...
ReportEngine/graphrag/query_engine.py
View file @
5a47bec
...
...
@@ -12,7 +12,15 @@ from .graph_storage import Graph, Node
@dataclass
class
QueryParams
:
"""查询参数"""
"""
查询参数
由 GraphRAGQueryNode 或 Flask API 注入,控制查询范围:
- keywords: 关键词列表,可为空(空时默认返回各引擎 section 摘要);
- node_types: 限定节点类型;None 表示全量;
- engine_filter: 仅保留指定引擎来源;
- depth: 匹配节点向外扩展的层级。
"""
keywords
:
List
[
str
]
=
field
(
default_factory
=
list
)
node_types
:
Optional
[
List
[
str
]]
=
None
# None 表示全部类型
engine_filter
:
Optional
[
List
[
str
]]
=
None
# 限定引擎来源
...
...
ReportEngine/graphrag/state_parser.py
View file @
5a47bec
...
...
@@ -3,6 +3,10 @@ State JSON 解析器
解析 Insight/Media/Query 三引擎的 State JSON 文件,
提取结构化数据用于构建知识图谱。
默认假设 state_* 文件结构与三引擎输出一致:
- 顶层包含 query/report_title/paragraphs;
- 段落内的 research.search_history 记录搜索关键词、URL与摘要。
"""
from
dataclasses
import
dataclass
,
field
...
...
@@ -45,6 +49,8 @@ class StateParser:
State JSON 解析器
解析三引擎的 State JSON,提取用于构建知识图谱的结构化数据。
适用于 load_input_files 阶段:先查找与 MD 同目录的 state_*.json,
若存在则转为 ParsedState 供 GraphBuilder 直接消费。
"""
def
parse
(
self
,
engine_name
:
str
,
state_json
:
Dict
[
str
,
Any
])
->
ParsedState
:
...
...
@@ -84,7 +90,7 @@ class StateParser:
timestamp
=
search
.
get
(
'timestamp'
,
''
)
))
# 获取摘要,优先使用 latest_summary
# 获取摘要,优先使用 latest_summary
;若缺失则回退到段落正文
summary
=
research
.
get
(
'latest_summary'
,
''
)
if
not
summary
:
summary
=
para
.
get
(
'content'
,
''
)
...
...
@@ -124,6 +130,7 @@ class StateParser:
根据 Markdown 报告路径查找对应的 State JSON 文件
State JSON 通常与 MD 文件在同一目录下,命名格式为 state_*.json
用于 GraphRAG:在 load_input_files 时自动匹配最新或同名 state 文件。
Args:
md_path: Markdown 文件路径
...
...
ReportEngine/nodes/graphrag_query_node.py
View file @
5a47bec
...
...
@@ -133,7 +133,7 @@ class GraphRAGQueryNode(BaseNode):
for
round_idx
in
range
(
max_queries
):
self
.
log_info
(
f
"查询轮次 {round_idx + 1}/{max_queries}"
)
# 1. 构建决策提示词
# 1. 构建决策提示词
:将章节目标+图谱概览+查询历史一起交给 LLM
prompt
=
self
.
_build_decision_prompt
(
section
,
context
,
query_engine
,
history
)
...
...
@@ -145,12 +145,12 @@ class GraphRAGQueryNode(BaseNode):
self
.
log_error
(
"LLM 返回无效决策,终止查询"
)
break
# 3. 检查是否停止
# 3. 检查是否停止
:LLM 可主动返回 should_query=false 以节省轮次
if
not
decision
.
get
(
'should_query'
,
False
):
self
.
log_info
(
f
"LLM 决定停止查询: {decision.get('reasoning', '无原因')}"
)
break
# 4. 执行查询
# 4. 执行查询
:按 LLM 给出的参数查询本地图谱
params
=
QueryParams
(
keywords
=
decision
.
get
(
'keywords'
,
[]),
node_types
=
decision
.
get
(
'node_types'
),
...
...
@@ -222,7 +222,12 @@ class GraphRAGQueryNode(BaseNode):
context
:
Dict
[
str
,
Any
],
query_engine
:
QueryEngine
,
history
:
QueryHistory
)
->
Dict
[
str
,
str
]:
"""构建查询决策提示词"""
"""
构建查询决策提示词
将章节目标、模板章节概览、图谱统计、历史查询摘要整合为
system/user prompt,指导 LLM 生成下一轮 QueryParams。
"""
# 获取图谱概览
summary
=
query_engine
.
get_node_summary
()
stats
=
summary
.
get
(
'stats'
,
{})
...
...
@@ -268,7 +273,12 @@ class GraphRAGQueryNode(BaseNode):
}
def
_get_query_decision
(
self
,
prompt
:
Dict
[
str
,
str
])
->
Optional
[
Dict
[
str
,
Any
]]:
"""调用 LLM 获取查询决策"""
"""
调用 LLM 获取查询决策
返回的 JSON 将被转换为 QueryParams;任何解析失败都会终止后续轮次,
避免章节生成被异常输出阻断。
"""
try
:
response
=
self
.
llm_client
.
invoke
(
system_prompt
=
prompt
[
'system'
],
...
...
app.py
View file @
5a47bec
...
...
@@ -1307,6 +1307,8 @@ def shutdown_system():
return
jsonify
({
'success'
:
False
,
'message'
:
f
'系统关闭异常: {exc}'
}),
500
# ==================== GraphRAG API 端点 ====================
# 前端控制台与 /graph-viewer 调用,均依赖 ReportEngine 在章节目录落盘的 graphrag.json。
# 若 GRAPHRAG_ENABLED 关闭,这些接口仅返回“未找到图谱”提示。
@app.route
(
'/api/graph/<report_id>'
)
def
get_graph_data
(
report_id
):
...
...
@@ -1493,6 +1495,7 @@ def query_graph():
if
report_id
:
graph_path
=
storage
.
find_graph_by_report_id
(
report_id
)
else
:
# 未指定报告ID时默认取最近一次生成的图谱,便于快速试用
graph_path
=
storage
.
find_latest_graph
()
if
not
graph_path
or
not
graph_path
.
exists
():
...
...
templates/index.html
View file @
5a47bec
...
...
@@ -2282,6 +2282,7 @@
query
:
8503
};
// ---------------- GraphRAG 开关与配置同步 ----------------
function
syncGraphragFlag
(
config
)
{
if
(
!
config
||
!
Object
.
prototype
.
hasOwnProperty
.
call
(
config
,
'GRAPHRAG_ENABLED'
))
{
return
;
...
...
@@ -2290,6 +2291,7 @@
graphragSettingLoaded
=
true
;
}
// 前端懒加载配置:初次访问或强制刷新时请求 /api/config,决定是否展示图谱面板
async
function
ensureGraphragSetting
(
force
=
false
)
{
if
(
!
force
&&
graphragSettingLoaded
)
{
return
graphragEnabled
;
...
...
@@ -5151,6 +5153,7 @@ function getConsoleContainer() {
}
}
// 入口:在报告界面渲染后初始化图谱面板,若 GraphRAG 关闭则隐藏
async
function
initializeGraphPanel
(
statusData
)
{
const
panel
=
document
.
getElementById
(
'graphPanel'
);
if
(
!
panel
)
return
;
...
...
@@ -5175,6 +5178,7 @@ function getConsoleContainer() {
refreshGraphPanel
(
graphPanelTaskId
,
true
);
}
// 注册图谱面板按钮/筛选/搜索事件,只绑定一次
function
bindGraphPanelEvents
()
{
const
refreshBtn
=
document
.
getElementById
(
'graphRefreshBtn'
);
const
collapseBtn
=
document
.
getElementById
(
'graphCollapseBtn'
);
...
...
@@ -5342,6 +5346,7 @@ function getConsoleContainer() {
});
}
// 将 graphrag.json 转换的节点/边渲染成 mini 版 vis.js 图谱
function
renderGraphPanel
(
graph
,
resetPlaceholder
=
true
)
{
const
panel
=
document
.
getElementById
(
'graphPanel'
);
const
canvasWrapper
=
document
.
getElementById
(
'graphPanelCanvas'
);
...
...
Please
register
or
login
to post a comment