Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
# InsightFlow:企业知识与数据问答 Agent

[![CI](https://github.com/hachiwar/InsightFlow/actions/workflows/ci.yml/badge.svg)](https://github.com/hachiwar/InsightFlow/actions/workflows/ci.yml)
[![GitHub Pages](https://github.com/hachiwar/InsightFlow/actions/workflows/pages.yml/badge.svg)](https://github.com/hachiwar/InsightFlow/actions/workflows/pages.yml)

[在线演示](https://hachiwar.github.io/InsightFlow/) · [架构设计](docs/architecture.md) · [国内服务器上线指南](docs/InsightFlow国内服务器上线指南.md)

InsightFlow 是一套可运行的企业 Agent 工程:MindAgent 负责会话记忆、知识库 RAG、意图识别和多 Agent 编排;DataAgent 负责把自然语言数据问题转换为经过治理、执行、校验和解释的 SQL。项目提供 Java / Python 后端、浏览器内 SQLite 范例、Docker Compose 部署以及自动化测试。
InsightFlow 是一套面向企业知识问答与结构化数据分析的 Agent 工程。MindAgent 负责会话记忆、知识库 RAG、意图识别和多 Agent 编排;DataAgent 负责把自然语言数据问题转换为经过治理、执行、校验和解释的 SQL。仓库包含 Java 与 Python 后端、浏览器内 SQLite 交互范例、Docker Compose 部署、接口文档和自动化测试。

## 核心能力

| 领域 | 能力 |
|---|---|
| 统一入口 | MindAgent 对外提供 `/chat`,在知识问答、技术支持、账单账户和数据查询之间完成意图路由 |
| 对话与知识 | Redis 工作记忆、历史摘要、用户画像、查询改写、BM25 与向量混合检索、Rerank |
| 数据推理 | 字段级 Schema 召回、SchemaGraph、CoT 四元组规划、局部 Schema SQL 生成和有限纠错 |
| 执行治理 | 单语句与只读校验、危险函数拦截、表白名单、查询超时、结果上限和 SQLite 只读连接 |
| 结果证据 | 问题导向解释、行列一致性校验、SQL 指纹、策略结果、执行耗时和错误审计 |
| 工程交付 | Caddy、Docker Compose、双 API Key、内部网络、健康检查、冒烟测试和 GitHub Actions |

## 技术栈

| 层级 | 技术 |
|---|---|
| MindAgent | Java 21、Spring Boot、Spring AI、Redis、Micrometer、OpenAPI |
| DataAgent | Python、SQLite、HTTP API、Schema 检索、Text-to-SQL、SQL 治理 |
| 在线范例 | React、Vite、sql.js、WebAssembly |
| 部署与质量 | Caddy、Docker Compose、GitHub Actions、Java/Python/Node.js 自动化测试 |

## 为什么不是预存 SQL
## 动态数据问答场景

报表脚本适合固定口径;业务问答经常同时包含动态时间、模糊概念、多表关系和追问上下文。例如:

Expand Down Expand Up @@ -57,7 +80,7 @@ DataAgent(Python)

[GitHub Pages 范例站](https://hachiwar.github.io/InsightFlow/) 使用 React、sql.js 和 WebAssembly 在浏览器内真实执行 5 张样例表,展示完整数据链路。内置 3 个复杂多表场景,无需 API Key。

页面也支持临时填写 OpenAI Chat Completions 兼容端点。启用后,模型负责动态 SQL、错误修复和结果解释;执行仍由浏览器只读沙盒完成。API Key 只保存在当前页面内存,刷新即清除。问题、相关 Schema、SQL 和结果会发送到用户填写的模型端点,因此只应使用临时限额 Key和公开样例数据。
页面也支持临时填写 OpenAI Chat Completions 兼容端点。启用后,模型负责动态 SQL、错误修复和结果解释;执行仍由浏览器只读沙盒完成。API Key 只保存在当前页面内存,刷新即清除。问题、相关 Schema、SQL 和结果会发送到用户填写的模型端点,因此只应使用临时限额 Key 和公开样例数据。

本地运行:

Expand Down Expand Up @@ -178,7 +201,7 @@ MindAgent 优先使用 `answer` 组织对话回复,同时保留 SQL 和校验
- 容器使用只读文件系统、非特权模式和内部网络;
- 返回行数、请求体和执行时间均有限制;
- 审计记录不保存明文 SQL,只保存指纹、策略、耗时和错误;
- `.env`、数据库、日志、PDF 和本地面试材料均被 Git 忽略。
- `.env`、数据库、运行日志、PDF 和本地个人文档均被 Git 忽略。

项目的工程范围是只读企业问答与 SQLite 演示数据,不提供数据库写操作,也不连接真实银行账户。接入企业数据时,应为每个数据源配置专用只读账号与最小化表白名单。

Expand Down Expand Up @@ -218,7 +241,7 @@ InsightFlow/
└── .env.example # 无密钥配置模板
```

## 项目验证路径
## 复现与检查

1. 查看 MindAgent / DataAgent 的职责边界;
2. 在在线站点运行“盈利下降”或“存款未消费”场景,逐步查看关键词、Schema、计划、SQL、结果和解释;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ def get_trade_relations_meta() -> list[dict]:
"""
Demo 表关系元数据。

当前 SQLite 可以通过外键提取出关系。
这里保留结构,后续可以写入 Milvus 的表关系 Collection。
SQLite 通过外键提取关系;同一结构也可作为 Milvus 表关系 Collection 的写入对象。
"""
return [
{
Expand Down
4 changes: 2 additions & 2 deletions dataagent/code/dataagent_agent/dataagent_agent/env_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def test_schema_loading_and_retrieval() -> bool:
print("\n✅ 最小字段检索流程正常")
return True

print("\n⚠️ 检索流程跑通,但没有命中预期字段。可以后续优化分词和元数据。")
print("\n⚠️ 检索流程可用,但本次查询未命中预期字段;请检查分词与元数据配置。")
return True

except Exception as exc:
Expand All @@ -339,7 +339,7 @@ def test_sentence_transformers_import(deps: dict) -> bool:

print("✅ sentence-transformers 可以 import")
print("说明:这里不强制下载模型,避免无网络环境卡住。")
print("后续你可以手动测试:")
print("如需验证模型下载,可运行:")
print('python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer(\'BAAI/bge-small-zh-v1.5\')"')
return True

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ def embed_texts(self, texts: List[str]) -> np.ndarray:

vectors = np.array(embeddings, dtype=np.float32)

# 归一化,后续可以直接用点积计算 cosine similarity。
# 归一化后可直接用点积计算 cosine similarity。
norms = np.linalg.norm(vectors, axis=1, keepdims=True)
vectors = vectors / np.maximum(norms, 1e-8)

return vectors
return vectors
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,12 @@ class SchemaRetriever:
"""
字段级 Schema 检索器。

当前 Demo 实现:
SchemaRetriever 提供:
- 字段级文档构建
- BM25关键词召回
- 简单业务别名 boost

后续扩展:
- 加 VectorIndex
- 加 Reranker
- 加 LLM Query 关键词提取
HybridSchemaRetrievalService 在此基础上组合向量召回、Rerank 和关键词提取。
"""

def __init__(
Expand Down Expand Up @@ -74,7 +71,7 @@ def _business_boost(self, query: str, doc: FieldDocument) -> float:
- Query 直接包含字段名、字段别名、表别名时加分。
- Query token 与字段描述 token 重合时加分。

这部分后续可以替换为 rerank 模型。
该规则分数与上层 Rerank 结果共同参与混合排序。
"""
query_lower = query.lower()
query_tokens = set(tokenize(query))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class CotStep:
"""
CoT 四元组中的单个步骤。

数据库只用于后续执行路由;
数据库只用于执行路由;
SQL 生成 Prompt 中只放处理对象、操作指令和输出目标。
"""

Expand Down Expand Up @@ -124,7 +124,7 @@ def to_execution_request(self) -> Dict[str, str]:
"""
转换为数据库执行请求。

后续可以通过 MCP 路由到对应 database 的执行 API。
返回对象由 MCPRouter 路由到对应 database 的执行器。
"""
return {
"database": self.database,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@ class LocalSchemaStore:
"""
本地 Schema 存储。

这里先用内存对象保存完整 Schema。
后续可以替换为:
- Milvus 字段索引
- Milvus 表关系索引
- SchemaGraph 对象
- 元数据服务 API
使用内存对象保存当前查询的完整 Schema,并支持从 SchemaGraph 构建。
上游元数据可以来自 Milvus 字段索引、表关系索引或元数据服务 API。

SQL 生成阶段不会直接使用完整 Schema,而是根据 CoT 四元组中的处理对象
裁剪出当前步骤需要的局部 Schema。
Expand Down Expand Up @@ -90,9 +86,8 @@ def from_schema_graph(cls, schema_graph: object) -> "LocalSchemaStore":
从 SchemaGraph 构建 SchemaStore。

SchemaGraph 来自 Schema 检索阶段。
SQL 生成阶段会先把这个 SchemaGraph 存下来,
后续根据 CoT 四元组中的处理对象、操作指令和输出目标,
再从中裁剪当前步骤需要的局部 Schema。
SQL 生成阶段保存 SchemaGraph,并根据 CoT 四元组中的处理对象、
操作指令和输出目标裁剪当前步骤需要的局部 Schema。
"""
tables: Dict[str, TableSchema] = {}

Expand Down
5 changes: 2 additions & 3 deletions demo-site/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,14 @@ function DataLab() {
return <section className="page-section data-lab" id="data-lab">
<SectionHeading title="数据实验室" description="样例数据库在浏览器内执行;问题、规划、SQL、结果和解释都可检查。你也可以临时接入 OpenAI Chat Completions 兼容模型。" />
<div className="stage-line" aria-label="执行阶段">{STAGES.map((stage, index) => { const number = index + 1; const state = number < activeStage ? "done" : number === activeStage ? "active" : ""; return <div className={state} key={stage}><span>{number < activeStage ? "✓" : number}</span><small>{stage}</small></div>; })}</div>
{error ? <div className="error-banner" role="alert"><strong>本次分析未完成</strong><span>{error}</span></div> : null}
{error ? <div className="error-banner" role="alert"><strong>分析失败</strong><span>{error}</span></div> : null}
<div className="lab-grid">
<aside className="question-column"><div className="column-title"><span>QUERY</span><small>数据截至 {SNAPSHOT_DATE}</small></div><label htmlFor="question">业务问题</label><textarea id="question" value={question} maxLength="500" onChange={(event) => setQuestion(event.target.value)} /><div className="question-list">{SAMPLE_QUESTIONS.map((item, index) => <button type="button" onClick={() => setQuestion(item)} key={item}><span>0{index + 1}</span>{item}</button>)}</div><button className="run-button" type="button" disabled={!database || running || !question.trim()} onClick={() => analyze()}>{running ? "正在推理…" : database ? "运行完整链路" : "正在加载样例库…"}</button>
<details className="model-settings"><summary>自定义大模型 <span>OpenAI 兼容接口</span></summary><label className="check-row"><input type="checkbox" checked={modelEnabled} onChange={(event) => setModelEnabled(event.target.checked)} />启用动态 SQL 与结果解释</label><label>服务商<select value={providerId} onChange={chooseProvider}>{MODEL_PROVIDERS.map((provider) => <option value={provider.id} key={provider.id}>{provider.name}</option>)}</select></label><label>Chat Completions 地址<input type="url" autoComplete="off" value={endpoint} onChange={(event) => setEndpoint(event.target.value)} placeholder="https://example.com/v1/chat/completions" /></label><label>模型名称<input value={model} autoComplete="off" onChange={(event) => setModel(event.target.value)} /></label><label>临时 API Key<input type="password" value={apiKey} autoComplete="off" onChange={(event) => setApiKey(event.target.value)} /></label><p>Key 仅保存在页面内存。启用后问题、相关 Schema、SQL 与结果会发送到你填写的端点;请使用临时限额 Key。</p></details>
<details className="model-settings"><summary><span>自定义大模型</span><span className="model-summary-action"><b aria-hidden="true">+</b> OpenAI 兼容接口</span></summary><label className="check-row"><input type="checkbox" checked={modelEnabled} onChange={(event) => setModelEnabled(event.target.checked)} />启用动态 SQL 与结果解释</label><label>服务商<select value={providerId} onChange={chooseProvider}>{MODEL_PROVIDERS.map((provider) => <option value={provider.id} key={provider.id}>{provider.name}</option>)}</select></label><label>Chat Completions 地址<input type="url" autoComplete="off" value={endpoint} onChange={(event) => setEndpoint(event.target.value)} placeholder="https://example.com/v1/chat/completions" /></label><label>模型名称<input value={model} autoComplete="off" onChange={(event) => setModel(event.target.value)} /></label><label>临时 API Key<input type="password" value={apiKey} autoComplete="off" onChange={(event) => setApiKey(event.target.value)} /></label><p>Key 仅保存在页面内存。启用后问题、相关 Schema、SQL 与结果会发送到你填写的端点;请使用临时限额 Key。</p></details>
</aside>
<div className="trace-column"><div className="trace-summary"><article><span>KEYWORDS</span><div className="token-list">{previewKeywords.map((word) => <code key={word}>{word}</code>)}</div></article><article><span>SCHEMA RECALL</span><div className="token-list">{pipeline?.tables?.map((table) => <code key={table.name}>{table.name}</code>) ?? <small>运行后显示召回表</small>}</div></article></div><article className="plan-panel"><div className="column-title"><span>QUERY PLAN</span>{pipeline ? <small>{pipeline.repaired ? "自动纠错" : pipeline.mode === "model" ? "模型生成" : "本地可复现"}</small> : null}</div><h3>{pipeline?.title ?? "等待业务问题进入规划器"}</h3>{pipeline ? <ol>{pipeline.plan.map((step) => <li key={step}>{step}</li>)}</ol> : <p>运行后展示时间窗口、聚合口径、表连接和输出字段。</p>}</article><article className="sql-panel"><div className="column-title"><span>GENERATED SQL</span><div><button type="button" onClick={() => navigator.clipboard.writeText(sql)} disabled={!sql}>复制</button><button type="button" onClick={rerunSql} disabled={!sql || running}>重新执行</button></div></div><textarea aria-label="生成的 SQL,可编辑" spellCheck="false" value={sql} onChange={(event) => setSql(event.target.value)} placeholder="生成的只读 SQL 将显示在这里" /><p className="safety-note"><b>READ ONLY</b> 单条 SELECT / WITH · 写操作拦截 · 最多纠错 2 次</p></article></div>
<aside className="data-column"><section><div className="column-title"><span>DEMO SCHEMA</span><small>{database ? "SQLite 已就绪" : "加载中"}</small></div><SchemaBrowser selectedNames={selectedNames} /></section><section className="result-section"><div className="column-title"><span>RESULT</span><small>{result ? `${result.rowCount} 行` : "等待执行"}</small></div><ResultTable result={result} />{explanation ? <div className="explanation"><span>{explanation.mode}</span><p>{explanation.text}</p></div> : null}</section></aside>
</div>
<div className="agent-rationale"><strong>为什么需要 Agent</strong><span>理解“最近”“盈利”“未消费”等动态口径</span><span>按问题选择表与连接路径</span><span>组合聚合、窗口函数和多步条件</span><span>保留 SQL、结果与解释供复核</span></div>
</section>;
}

Expand Down
Loading
Loading