@roll-agent/octopus-agent 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +26 -190
  2. package/SKILL.md +27 -103
  3. package/package.json +12 -18
  4. package/references/env.yaml +1 -22
  5. package/scripts/start-octopus-agent.js +1 -125
  6. package/src/config.js +13 -0
  7. package/src/context.js +5 -0
  8. package/src/mcp-client.js +80 -0
  9. package/src/orchestrator.js +57 -0
  10. package/src/server-core.js +67 -0
  11. package/src/server.js +10 -0
  12. package/src/stdio.js +29 -0
  13. package/octopus_skill/__init__.py +0 -11
  14. package/pyproject.toml +0 -19
  15. package/scripts/refresh_dictionary.py +0 -67
  16. package/scripts/verify_binding.py +0 -47
  17. package/src/octopus_skill/__init__.py +0 -5
  18. package/src/octopus_skill/__main__.py +0 -4
  19. package/src/octopus_skill/_version.py +0 -3
  20. package/src/octopus_skill/agent.py +0 -5739
  21. package/src/octopus_skill/audit_logger.py +0 -29
  22. package/src/octopus_skill/config.py +0 -129
  23. package/src/octopus_skill/context.py +0 -17
  24. package/src/octopus_skill/device_info.py +0 -7
  25. package/src/octopus_skill/exporter.py +0 -66
  26. package/src/octopus_skill/llm_result_renderer.py +0 -101
  27. package/src/octopus_skill/llm_sql_generator.py +0 -197
  28. package/src/octopus_skill/main.py +0 -26
  29. package/src/octopus_skill/mcp_client.py +0 -114
  30. package/src/octopus_skill/octopus_run.py +0 -755
  31. package/src/octopus_skill/prompt_builder.py +0 -522
  32. package/src/octopus_skill/question_analyzer.py +0 -401
  33. package/src/octopus_skill/result_renderer.py +0 -367
  34. package/src/octopus_skill/schema_cache.py +0 -49
  35. package/src/octopus_skill/schema_context_retriever.py +0 -1053
  36. package/src/octopus_skill/sql_generator.py +0 -2866
package/README.md CHANGED
@@ -1,209 +1,45 @@
1
- # 丸子Agent(Octopus Agent
1
+ # Octopus Agent
2
2
 
3
- 本仓库实现 Octopus 对接 Sponge MCP Server 的第一版 MVP。
4
-
5
- ## 能力
6
-
7
- - Schema Cache(表结构缓存):本地保存 Sponge 返回的 schema。
8
- - MCP Client(MCP 客户端):按 `get_schema`、`validate_sql`、`execute_sql` 三步调用。
9
- - MCP Sampling(采样):由 roll-core 代调 LLM 生成和修正 SQL。
10
- - SQL Guard(SQL 保护规则):只允许单条 `SELECT`,必须带 `LIMIT`。
11
- - Audit Log(审计日志):记录 trace、session、SQL hash、执行摘要。
3
+ Octopus Agent 是 Node.js 实现的轻量 NL2SQL(自然语言转 SQL)调用客户端。Agent 每次只调用一次 `query_sponge`(查询 Sponge);所有业务判断、SQL 生成、校验、修复、执行和渲染均由 Sponge MCP Server 完成。
12
4
 
13
5
  ## 本地运行
14
6
 
15
- ```bash
16
- python -m octopus_skill "查询我能看到的品牌列表"
17
- ```
18
-
19
- 运行参数由 Agent 内置;用户本地只需要配置 Sponge MCP Server 地址和 Token。
20
-
21
- ## roll-core 接入
22
-
23
- 本仓库支持纯 Python stdio MCP 子 Agent 接入 roll-core。
24
-
25
- ### npm 包安装
26
-
27
- 发布到 npm 后,可通过 `roll agent install` 安装:
7
+ 要求 Node.js 20 或更高版本。
28
8
 
29
9
  ```bash
30
- roll agent install @roll-agent/octopus-agent
10
+ npm install
11
+ npm test
12
+ npm start
31
13
  ```
32
14
 
33
- npm package(npm 包)直接包含 Python 源码;`roll agent install` 不会再从 PyPI 下载 `octopus-skill`。
34
- 安装机器上需要有 Python 3.11+。Agent 启动时会自动尝试 `python3`、`python`、`py -3.11`、`py -3`。
35
- 如果 Python 命令不在这些名称里,可以配置 `OCTOPUS_PYTHON_COMMAND`(Python 命令),例如:
36
-
37
- ```powershell
38
- roll config set agents.env.octopus-agent.OCTOPUS_PYTHON_COMMAND "py -3.11"
39
- ```
40
-
41
- Windows 用户只要安装 Python 3.11+,通常不需要额外创建 `python3` 别名。
42
-
43
- ### 首次使用引导(安装后必做)
44
-
45
- 业务查询前先完成这两步,可减少首次 `query_sponge` 冷启动耗时,并尽早发现配置问题:
46
-
47
- ```bash
48
- # 1) 检查 token、schema 缓存、日志路径
49
- roll run octopus-agent diagnostic_status --json
50
-
51
- # 2) 预热本地 schema 缓存(Windows 请用 --input-file,见下文)
52
- roll run octopus-agent refresh_schema --json \
53
- --input-json '{"traceId":"trace_schema_10001"}'
54
- ```
55
-
56
- 确认 `diagnostic_status` 里 `spongeMcp.accessTokenConfigured=true`,且 `refresh_schema` 成功后,再发起业务查询。
57
-
58
- ### 本地源码注册
15
+ ## Roll Agent 接入
59
16
 
60
17
  ```bash
61
18
  roll agent add .
62
- roll run octopus-agent diagnostic_status --json
63
- roll run octopus-agent refresh_schema --json \
64
- --input-json '{"traceId":"trace_schema_10001"}'
65
- roll run octopus-agent query_sponge --json \
66
- --input-json '{"question":"查询我能看到的品牌列表","originalQuestion":"查询我能看到的品牌列表"}'
19
+ roll run octopus-agent query --json \
20
+ --input-json '{"question":"查询本月各门店销售额"}'
67
21
  ```
68
22
 
69
- `refresh_schema` 必填 `traceId`。Agent 会自动读取本地缓存里的 `schemaVersion` 并传给 Sponge。
70
- `query_sponge` 必填 `question` 和 `originalQuestion`;不接收用户或设备标识;Sponge MCP Server 会根据 `Authorization` token 解析用户权限。
71
- Agent 只生成业务 SQL,不生成 `:scope.project_ids`、`:scope.brand_ids` 等权限占位符。
72
- 权限范围由 Sponge MCP Server 在 `execute_sql` 阶段统一补全,并写入 `mcp_sql_records.scope_bindings`。
73
-
74
- #### Windows 调用注意(CMD / PowerShell)
23
+ `query`(自然语言查询)入参:
75
24
 
76
- Windows CMD / PowerShell 会改写命令行里的引号,常见结果是把 JSON 变成 `{'question':...}`,从而报:
77
-
78
- ```text
79
- --input-json 不是合法 JSON: Expected property name or '}' in JSON at position 1
80
- ```
81
-
82
- 推荐优先用 `--input-file`,避免 shell 引号问题:
83
-
84
- ```powershell
85
- # 1) 先写无 BOM 的 UTF-8 JSON(Windows PowerShell 5.1 用 .NET 写,避免 BOM)
86
- $path = Join-Path $env:TEMP "octopus-query.json"
87
- $json = '{"question":"奥乐齐有哪些在招的岗位","originalQuestion":"奥乐齐有哪些在招的岗位"}'
88
- [System.IO.File]::WriteAllText($path, $json, (New-Object System.Text.UTF8Encoding $false))
89
-
90
- # 2) 再调用
91
- roll run octopus-agent query_sponge --input-file $path --json
92
- ```
25
+ | 字段 | 必填 | 中文说明 |
26
+ |-|-|-|
27
+ | `question` | 是 | 用户原始问题,不得改写 |
28
+ | `resultFormat` | | 结果格式,支持 text/json/yaml/md/markdown/html,默认 markdown(Markdown 格式) |
93
29
 
94
- 若必须内联 JSON:
95
-
96
- ```powershell
97
- # PowerShell:用双引号包住 JSON,内部双引号写成 \"
98
- roll run octopus-agent query_sponge --json --input-json "{\"question\":\"查询我能看到的品牌列表\",\"originalQuestion\":\"查询我能看到的品牌列表\"}"
99
- ```
100
-
101
- ```bat
102
- REM CMD:外层用双引号,内部双引号写成 \"
103
- roll run octopus-agent query_sponge --json --input-json "{\"question\":\"查询我能看到的品牌列表\",\"originalQuestion\":\"查询我能看到的品牌列表\"}"
104
- ```
105
-
106
- macOS / Linux / Git Bash 仍可继续用单引号包 JSON:
107
-
108
- ```bash
109
- roll run octopus-agent query_sponge --json \
110
- --input-json '{"question":"查询我能看到的品牌列表","originalQuestion":"查询我能看到的品牌列表"}'
111
- ```
30
+ ## 环境变量
112
31
 
113
- #### `MCP error -32001: Request timed out`
32
+ | 环境变量 | 必填 | 中文说明 | 默认值 |
33
+ |-|-|-|-|
34
+ | `SPONGE_MCP_BASE_URL` | 是 | Sponge MCP Server 地址 | `https://sponge-mcp.duliday.com` |
35
+ | `SPONGE_MCP_ACCESS_TOKEN` | 是 | 身份透传 Token(令牌) | 无 |
114
36
 
115
- 这是 **roll-core 调用子 Agent MCP `tools/call` 默认 60 秒超时**(`@modelcontextprotocol/sdk` `DEFAULT_REQUEST_TIMEOUT_MSEC`),不是 Sponge HTTP 客户端自己的 30 秒超时。
116
- `query_sponge` 会多次走 MCP Sampling(需求分析 / 实体抽取 / SQL 生成)再调 Sponge,整链路经常超过 60 秒,Windows 上网络或首次冷启动更明显。
37
+ Agent 内置单次请求超时 58 秒、query(查询)总超时 59 秒、网络重试 2 次,无需额外配置。每个新问题生成新的 `requestId`(请求编号),仅网络重试复用该编号。总超时低于当前 Roll 60 秒固定限制。
117
38
 
118
- 可先做这些排查:
39
+ ## 安全边界
119
40
 
120
- 1. 先跑 `roll run octopus-agent diagnostic_status --json`,确认 `spongeMcp.accessTokenConfigured=true`、schema 缓存存在。
121
- 2. 先跑一次 `refresh_schema`,避免查询时再拉大 schema
122
- 3. 确认本机到 `SPONGE_MCP_BASE_URL` LLM provider 网络通畅。
123
- 4. 若仍稳定在约 60 秒失败,需要 **升级 / 修复 roll-core**:让 `roll run` 对长耗时 tool(如 `query_sponge`)传入更大的 `callTool` `timeout`(当前 roll v0.15.0 未暴露该参数)。仅改本 Agent 的 `SPONGE_MCP` HTTP timeout 无法消除 `-32001`。
124
-
125
- 指定输出格式:
126
-
127
- ```bash
128
- roll run octopus-agent query_sponge --json \
129
- --input-json '{"question":"查询我能看到的品牌列表","originalQuestion":"查询我能看到的品牌列表","resultFormat":"markdown"}'
130
- ```
131
-
132
- 支持 `text`、`json`、`yaml`、`md`、`markdown`、`html`。不传时会从问题里自动识别。
133
-
134
- 调试 SQL:
135
-
136
- ```bash
137
- roll run octopus-agent query_sponge --json \
138
- --input-json '{"question":"查询我能看到的品牌列表","originalQuestion":"查询我能看到的品牌列表","includeSql":true}'
139
- ```
140
-
141
- `includeSql=true` 时会返回 `generatedSql`、`normalizedSql`、`repairedSql` 和 `validation`。
142
- 所有模式都会返回 `needsClarification`。当 `needsClarification=true` 时,上层 Agent 必须原样展示 `answer` 并等待用户确认,不得自行选择候选项或再次调用 `query_sponge`。
143
- 默认 `requirePlanConfirmation=true`,`query_sponge` 会先返回查询计划:匹配到的表、字段、筛选条件和执行步骤。用户回复“确认”后,再继续生成、校验和执行 SQL;用户提出修改时,会按修改后的口径重新整理查询计划并再次确认。调试或内部直通场景可以显式传 `requirePlanConfirmation=false`。
144
-
145
- 普通业务返回使用单 `content` 通道,透传约束改由结构化字段承载:
146
-
147
- - `content[0].text`:纯净业务答案,裸客户端可以直接展示。
148
- - `structuredContent.answer`:同样保存纯净业务答案,作为结构化兜底。
149
- - `structuredContent.responseMode=verbatim` 和 `structuredContent.finalAnswerOnly=true`:提示上层 Agent 应原样透传答案。约束只放在结构化字段,`content` 数组不再附带元指令文本,避免泄露给用户。
150
-
151
- ### 查询链路规范
152
-
153
- 一次完整查询链路先展示查询计划,再由 `validate_sql` 和 `execute_sql` 组成。`validate_sql` 和 `execute_sql` 两步必须使用同一个 `traceId`(链路 ID)。同一个用户问题、同一次分析上下文必须复用同一个 `sessionId`(会话 ID)。
154
-
155
- 查询计划包含:
156
-
157
- - 查询表:来自 `get_schema` 的 schema 中文语义匹配结果。
158
- - 查询字段:确认后必须尽量出现在 SQL 的 `SELECT` 中。
159
- - 筛选条件:确认后必须尽量出现在 SQL 的 `WHERE` 中;名称类使用 `LIKE`,状态类使用 `schema.enums` 的枚举值。
160
- - 执行步骤:从上层调用、意图判断、口径确认、SQL 校验执行到结果渲染。
161
-
162
- 最终答案顶部会先展示“查询字段”和“筛选条件”,再展示查询结果。
163
-
164
- 如果执行成功但结果异常,例如 `rowCount=0`、关键字段大量为空、结果明显不符合预期,Agent 不应自动改口径再跑第二次完整链路。应先说明异常点、可能原因、建议口径和影响范围,等待用户确认后,再用新的 `traceId`、原 `sessionId` 发起新链路。
165
-
166
- 如果只是 SQL 校验失败,可以修正 SQL 后重新校验;只要涉及业务口径变化,必须先确认。
167
-
168
- `package.json` 中声明了 roll-core 启动方式:
169
-
170
- ```json
171
- {
172
- "rollAgent": {
173
- "runtime": {"ownership": "on-demand", "transport": "stdio"},
174
- "start": {
175
- "command": "node",
176
- "args": ["scripts/start-octopus-agent.js"]
177
- }
178
- }
179
- }
180
- ```
181
-
182
- 环境变量可放到 `roll.config.yaml`:
183
-
184
- ```yaml
185
- agents:
186
- env:
187
- octopus-agent:
188
- SPONGE_MCP_BASE_URL: "https://sponge-mcp.duliday.com"
189
- SPONGE_MCP_ACCESS_TOKEN: "xxxx"
190
- ```
191
-
192
- `SPONGE_MCP_BASE_URL` 只填域名根地址,不要带 `/mcp/admin/health`;Agent 会调用 `/mcp/tools/...`。
193
-
194
- LLM 配置读取 roll-core 现有 `llm` 配置;Agent 不读取模型 API Key。
195
- `query_sponge` 会通过 MCP Sampling 请求 roll-core 代调模型。
196
-
197
- ## 本地持久化
198
-
199
- - `data/sponge-schema-cache.json`:schema 缓存。
200
- - `data/sponge-clarification-cache.json`:短期确认缓存,保存品牌/项目候选和查询计划确认上下文,默认 10 分钟过期。
201
- - `logs/octopus-sponge-audit.log`:审计日志。
202
-
203
- 不新建数据库,不保存真实项目 ID、品牌 ID、数据库账号密码。
204
-
205
- ## 自测
206
-
207
- ```bash
208
- PYTHONPATH=src python -m unittest discover -s tests
209
- ```
41
+ - Agent 只调用一次 `query_sponge`(查询 Sponge),不调用内部原子能力。
42
+ - Agent 不接收或传入任意 SQL
43
+ - Agent 不读取 Schema,不调用 LLM,不硬编码业务判断。
44
+ - 认证 Token 仅用于身份透传,数据权限由 Sponge MCP Server 校验。
45
+ - 不新建数据库,不持久化 Schema、SQL 或查询结果。
package/SKILL.md CHANGED
@@ -1,128 +1,52 @@
1
1
  ---
2
2
  name: octopus-agent
3
- description: 丸子Agent(Octopus Agent),把自然语言问题转换为只读 SQL,并通过 Sponge MCP Server 校验和执行。
3
+ description: 丸子Agent(Octopus Agent),单次调用 Sponge MCP Server 完成自然语言数据查询。
4
4
  metadata:
5
5
  roll-env-file: references/env.yaml
6
6
  ---
7
7
 
8
8
  # 丸子Agent(Octopus Agent)
9
9
 
10
- ## 职责
11
-
12
- 本 Agent 只负责海绵数据查询:
13
-
14
- - 读取和刷新 Schema Cache(表结构缓存)。
15
- - 基于用户问题和 schema context,通过 MCP Sampling(采样)请求 roll-core 代调 LLM 生成只读 SQL。
16
- - 按 `get_schema`、`validate_sql`、`execute_sql` 三步调用 Sponge MCP Server。
17
- - 记录 Audit Log(审计日志)。
18
-
19
- ## Tools
20
-
21
- | Tool | 中文解释 | 用途 |
22
- |-|-|-|
23
- | `diagnostic_status` | 诊断状态 | 检查配置、缓存和日志路径 |
24
- | `refresh_schema` | 刷新表结构 | 人工刷新本地 schema 缓存 |
25
- | `query_sponge` | 查询海绵数据 | 自然语言查询入口 |
26
-
27
- `refresh_schema` 必填 `traceId`;Agent 会自动读取本地缓存里的 `schemaVersion` 并传给 Sponge。
28
-
29
- `query_sponge` 支持 `resultFormat`:`text`、`json`、`yaml`、`md`、`markdown`、`html`。不传时按用户问题自动识别。
30
- `query_sponge` 不接收用户或设备标识;权限由 Sponge MCP Server 根据 `Authorization` token 解析。
31
- Agent 只生成业务 SQL,不生成 `:scope.project_ids`、`:scope.brand_ids` 等权限占位符。
32
- 调试时可传 `includeSql=true` 返回 `generatedSql`、`normalizedSql`、`repairedSql` 和 `validation`;默认不返回 SQL。
33
- `query_sponge` 不需要用户确认查询计划。工具内部会按 schema 中文语义识别表、字段、筛选条件、枚举和实体,把 `search_entities` 的真实命中项固化进 queryPlan,然后直接生成、校验并执行 SQL,不会在任何节点停下来等待用户确认。
34
- `finalAnswerOnly` 默认是 `true`,并返回 `structuredContent.responseMode=verbatim`;上层 Agent 必须把 `content[0].text` 或 `structuredContent.answer` 原样作为最终用户可见答复,不要只放在可折叠工具步骤里,也不要改写成摘要。用户可见答复统一为三段式:`1. 查询实体`、`2. 筛选条件` 和 `3. 查询结果`。
35
- 普通业务返回只带一个 `content` 项:`content[0].text` 是三段式用户可见答复。是否原样透传由 `structuredContent.responseMode=verbatim` 和 `finalAnswerOnly=true` 这两个结构化字段承载,不再在 `content` 里附带任何元指令文本,避免元指令泄露给用户。
36
- `search_entities` 只用于实体别称和模糊召回;品牌、项目等多个实体同时命中时不追问用户,queryPlan 会用同组 OR 条件承载,并在结果中展示各实体名称字段方便用户自行缩小范围。
37
- 状态枚举不单独追问。用户明确提出状态条件时,把状态条件写入 SQL 的 WHERE;用户未提出状态条件或 schema 缺少枚举时,首次查询保持宽泛,但查询结果必须展示相关状态字段的中文含义。
38
- 当 schema 完全匹配不到相关表时,`query_sponge` 会返回 `needsClarification=true`、`clarificationType=schema_no_match`。此时必须立即停止,原样展示 `structuredContent.answer` 给用户;这是"暂不支持该查询"的说明,不是查询计划确认,用户补充明确需求后重新发起查询即可。业务指标口径缺失等其它情况会直接返回普通 `answer`(`needsClarification=false`),上层原样展示即可。
39
-
40
- ## 上层调用规则
41
-
42
- 1. 用户只是说使用、启动、检查、看看丸子Agent,或确认 Agent 是否可用时,只调用 `diagnostic_status`,返回状态后停止;不得主动调用 `query_sponge`,不得编造示例业务问题。
43
- 2. **首次安装或新环境**:先调用 `diagnostic_status`,再调用 `refresh_schema` 预热 schema 缓存;确认 token 与缓存正常后,再接受业务查询。
44
- 3. 查询前如需排查环境,先调用 `diagnostic_status`。
45
- 4. schema 过期或缺失时,调用 `refresh_schema`。
46
- 5. 用户提出明确业务数据查询时,调用 `query_sponge`。
47
- 6. 不要求上层提供真实项目 ID 或品牌 ID;权限由 Sponge MCP Server 处理。
48
- 7. 权限范围由 Sponge MCP Server 在 `execute_sql` 阶段统一补全并记录。
49
- 8. 不允许绕过本 Agent 直接连业务数据库。
50
- 9. 运行参数由 Agent 内置;本地只通过环境变量配置 Sponge 地址和 Token。
51
- 10. LLM 由 roll-core 通过 MCP Sampling 代调,本 Agent 不读取模型 API Key。
52
- 11. 对“匹配效果”“分析报告”“找出报名多但入职少/在招多但报名少/候选人多但岗位少”等报告类问题,只能调用一次 `query_sponge` 处理完整原始问题,不要拆成多个品牌/项目/城市子查询。
53
- 12. `query_sponge` 返回后,最终回复正文必须完整复制工具返回的 `answer`,不要只把报告放在执行命令、思考过程、工具详情或可折叠步骤里。
54
- 13. 如果 `structuredContent.needsClarification=true`(`clarificationType=schema_no_match`),最终回复只能展示 `structuredContent.answer`;这是"暂不支持该查询"的说明,用户补充明确需求后重新发起查询即可。
55
- 14. 品牌、项目等实体多候选由 queryPlan 的同组 OR 条件处理;上层不要要求用户先选择品牌或项目。
56
- 15. 如果 `answer` 已包含分析报告,最终回复顺序必须保持:完整分析报告 -> 总结关键发现;不要自行改写成摘要替代报告。
10
+ ## Tool
57
11
 
58
- ## 最终展示 few-shot
12
+ `query`(自然语言查询)是唯一业务入口:
59
13
 
60
- 上层 Agent 必须把 `query_sponge` 返回的 `content[0].text` 或 `structuredContent.answer` 原样展示给用户。不得总结、改写、删除表格、删除实体和筛选条件。
14
+ - `question`:必填,本次用于分析的问题。
15
+ - `resultFormat`:选填,结果格式,默认 markdown(Markdown 格式)。
61
16
 
62
- ### 示例 1:保留实体和筛选条件
17
+ ## 调用边界
63
18
 
64
- 用户:查询近一年门店的创建时间,根据每日创建时间计数门店个数,列出所有的门店数量和对应项目
19
+ - 用户询问“介绍丸子”“丸子有什么功能”“怎么使用丸子”“丸子是否可用”等介绍、能力说明或使用方法时,只直接介绍,不调用 `query`(查询)。
20
+ - 介绍场景禁止编造、发起或执行任何示例查询,也不要声称已经执行过查询。
21
+ - 只有用户提出明确的业务数据查询问题时,才调用 `query`。
22
+ - 用户没有给出明确查询对象和查询诉求时,先说明能力,不自行补充示例问题。
65
23
 
66
- 工具返回:
67
-
68
- ```text
69
- 1. 查询实体:
70
- 门店;项目
71
-
72
- 2. 筛选条件:
73
- 创建时间 >= 2025-07-07;创建时间 < 2026-07-08;按创建日期、项目统计门店数量
74
-
75
- 3. 查询结果:
76
- 返回 500 行。
77
- limited: true
78
-
79
- |序号|创建日期|项目名称|门店数量|
80
- |---|---|---|---|
81
- |1|2026-07-07|示例项目|12|
82
- ```
83
-
84
- 最终回复必须原样展示以上两段式内容;不要改写成“我已查询到近一年数据”,也不要删除第一段。
85
-
86
- ### 示例 2:“所有/全部”不覆盖时间范围
87
-
88
- 用户:查询近一年门店数据,列出所有项目
24
+ ## 职责
89
25
 
90
- 正确理解:
26
+ - 每个新问题生成新的 `requestId`(请求编号)。
27
+ - 原样传入 `question`(问题),只调用一次 `query_sponge`(查询 Sponge)。
28
+ - 成功时原样返回 `answer`(答案),失败时返回服务端错误。
29
+ - 仅网络重试复用原 `requestId`,并控制总超时。
91
30
 
92
- - `近一年` 是时间范围,必须保留。
93
- - `所有` 是返回范围或导出诉求,不是取消时间范围。
94
- - 如果生成 CSV,也必须在两段式答复中保留实体和筛选条件后再展示文件路径。
31
+ ## 最终展示规则
95
32
 
96
- ### 示例 3:工具返回表格时不得摘要替代
33
+ 上层 Agent 的最终答复必须完全等于 `content[0].text`。该内容带有 `audience: user`(面向用户)和最高优先级注解,禁止总结、删减、改写或重排。必须保留原始换行、Markdown 表格和以下顺序:
97
34
 
98
- 错误回复:
35
+ 1. 查询实体
36
+ 2. 筛选条件
37
+ 3. 业务逻辑
38
+ 4. 查询结果
99
39
 
100
- ```text
101
- 我查到了门店数量最多的项目,结果如下……
102
- ```
40
+ 不得先展示查询结果,也不得在四段内容前后添加分析性结论。
103
41
 
104
- 正确回复:完整复制工具返回的 `content[0].text`,包括:
42
+ Agent 不调用 LLM(大语言模型),不读取 Schema(表结构),不生成、修改或解析 SQL,不根据自然语言错误做业务判断。
105
43
 
106
- - `1. 查询实体`
107
- - `2. 筛选条件`
108
- - `3. 查询结果`
109
- - 完整表格或 CSV 文件路径说明
44
+ ## 查询链路
110
45
 
111
- ## 查询链路规则
46
+ `query → query_sponge → answer`(查询 → 查询 Sponge → 答案)
112
47
 
113
- 1. 一次完整查询链路直接生成查询计划并执行 `validate_sql` 和 `execute_sql`,不需要用户确认查询计划;后两步必须使用同一个 `traceId`(链路 ID)。
114
- 2. 同一个用户问题、同一次分析上下文必须复用同一个 `sessionId`(会话 ID)。
115
- 3. 口径修正仍属于同一问题,继续复用原 `sessionId`,但使用新的 `traceId` 发起新链路。
116
- 4. 如果 `execute_sql` 成功但结果异常,例如 `rowCount=0`、关键字段大量为空、结果明显不符合问题预期,不自动改口径重跑。
117
- 5. 出现结果异常时,先向用户说明异常点、可能原因、建议口径和影响范围,等待用户确认后再用新口径查询。
118
- 6. 如果只是 SQL 校验失败,可以修正 SQL 后重新校验;涉及业务口径变化时必须先确认。
119
- 7. `originalQuestion` 只传**当前这一轮**用户原话,不要把同一会话里上一轮问题拼进来;`sessionId` 仅用于 MCP 审计,**不会**自动继承上一轮的项目/品牌/门店筛选。
120
- 8. 用户从「查岗位列表」转为「查操作日志 / 算历史在招数」等新问题时,仍复用 `sessionId` 即可,但 `originalQuestion` 必须是新问题本身,例如「你得查岗位上下架操作日志,给我算一下6月21日有多少在招岗位」。
121
- 9. 品牌/项目等多实体命中不追问用户;Agent 会在查询计划中生成同组 OR 条件并直接执行,结果中展示各实体名称字段方便用户自行缩小范围。
48
+ Agent 不追问、不生成或修复 SQL(结构化查询语言),也不调用服务端内部原子能力。
122
49
 
123
50
  ## 持久化
124
51
 
125
- - `data/sponge-schema-cache.json`:Schema Cache(表结构缓存)。
126
- - `logs/octopus-sponge-audit.log`:Audit Log(审计日志)。
127
-
128
- 不新建数据库,不保存真实项目 ID、品牌 ID、数据库账号密码。
52
+ 不新建数据库,不缓存 Schema、SQL 或查询结果。链路状态仅保存在单次调用内存中。
package/package.json CHANGED
@@ -1,27 +1,23 @@
1
1
  {
2
2
  "name": "@roll-agent/octopus-agent",
3
- "version": "0.1.3",
4
- "description": "丸子Agent(Octopus Agent",
3
+ "version": "0.2.0",
4
+ "description": "Octopus Agent:编排 Sponge MCP Server 的 NL2SQL 查询链路",
5
+ "type": "module",
5
6
  "license": "UNLICENSED",
6
- "keywords": [
7
- "roll-agent",
8
- "mcp",
9
- "nl2sql"
10
- ],
11
- "publishConfig": {
12
- "access": "public"
13
- },
14
7
  "files": [
15
- "octopus_skill",
16
8
  "src",
17
9
  "scripts",
18
10
  "references",
19
11
  "SKILL.md",
20
- "pyproject.toml",
21
- "!**/__pycache__/**",
22
- "!**/*.pyc",
23
- "!**/*.egg-info/**"
12
+ "README.md"
24
13
  ],
14
+ "scripts": {
15
+ "start": "node scripts/start-octopus-agent.js",
16
+ "test": "node --test"
17
+ },
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
25
21
  "rollAgent": {
26
22
  "runtime": {
27
23
  "ownership": "on-demand",
@@ -29,9 +25,7 @@
29
25
  },
30
26
  "start": {
31
27
  "command": "node",
32
- "args": [
33
- "scripts/start-octopus-agent.js"
34
- ]
28
+ "args": ["scripts/start-octopus-agent.js"]
35
29
  }
36
30
  }
37
31
  }
@@ -5,25 +5,4 @@ env:
5
5
  example: https://sponge-mcp.duliday.com
6
6
  SPONGE_MCP_ACCESS_TOKEN:
7
7
  required: true
8
- purpose: Sponge MCP Server 固定访问 Token
9
- OCTOPUS_BINDING_SHADOW:
10
- required: false
11
- purpose: >-
12
- 实体识别(binding)总开关。false(默认)=识别结果注入 LLM prompt 影响 SQL;
13
- true=仅计算并写审计日志(entity_binding_shadow),不改变 SQL 生成(用于回滚观察)。
14
- example: "false"
15
- OCTOPUS_BINDING_ENABLED:
16
- required: false
17
- purpose: >-
18
- 哪些实体类型的识别结果允许注入 prompt,逗号分隔,可选值 brand,location,project。
19
- 默认 brand,location,project(全开)。仅在 OCTOPUS_BINDING_SHADOW=false 时生效;
20
- 显式设为空字符串可关闭全部类型。
21
- example: brand,location,project
22
- OCTOPUS_BINDING_MIN_SCORE:
23
- required: false
24
- purpose: 实体模糊匹配的最低分数阈值(0~1),低于该分数不视为命中。默认 0.6。
25
- example: "0.6"
26
- OCTOPUS_SAMPLING_MAX_TOKENS:
27
- required: false
28
- purpose: MCP sampling/createMessage 请求的 maxTokens(LLM 输出 token 上限)。默认 4096。
29
- example: "4096"
8
+ purpose: 身份透传 Token(令牌)
@@ -1,126 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
-
4
- const { spawn, spawnSync } = require("node:child_process");
5
- const path = require("node:path");
6
-
7
- const MIN_MAJOR = 3;
8
- const MIN_MINOR = 11;
9
- const VERSION_CHECK = [
10
- "import sys",
11
- `raise SystemExit(0 if sys.version_info >= (${MIN_MAJOR}, ${MIN_MINOR}) else 1)`,
12
- ].join("; ");
13
-
14
- function splitCommand(value) {
15
- const parts = [];
16
- let current = "";
17
- let quote = null;
18
-
19
- for (let index = 0; index < value.length; index += 1) {
20
- const char = value[index];
21
- if ((char === '"' || char === "'") && quote === null) {
22
- quote = char;
23
- continue;
24
- }
25
- if (char === quote) {
26
- quote = null;
27
- continue;
28
- }
29
- if (/\s/.test(char) && quote === null) {
30
- if (current) {
31
- parts.push(current);
32
- current = "";
33
- }
34
- continue;
35
- }
36
- current += char;
37
- }
38
-
39
- if (current) {
40
- parts.push(current);
41
- }
42
- return parts;
43
- }
44
-
45
- function configuredCandidate() {
46
- const value = process.env.OCTOPUS_PYTHON_COMMAND;
47
- if (!value) {
48
- return null;
49
- }
50
-
51
- const [command, ...args] = splitCommand(value);
52
- if (!command) {
53
- return null;
54
- }
55
- return { command, args, source: "OCTOPUS_PYTHON_COMMAND" };
56
- }
57
-
58
- function pythonCandidates() {
59
- return [
60
- configuredCandidate(),
61
- { command: "python3", args: [], source: "python3" },
62
- { command: "python", args: [], source: "python" },
63
- { command: "py", args: ["-3.11"], source: "py -3.11" },
64
- { command: "py", args: ["-3"], source: "py -3" },
65
- ].filter(Boolean);
66
- }
67
-
68
- function isUsablePython(candidate) {
69
- const result = spawnSync(candidate.command, [...candidate.args, "-c", VERSION_CHECK], {
70
- encoding: "utf8",
71
- stdio: "ignore",
72
- });
73
- return result.status === 0;
74
- }
75
-
76
- function findPython() {
77
- return pythonCandidates().find(isUsablePython);
78
- }
79
-
80
- const python = findPython();
81
- if (!python) {
82
- console.error(
83
- [
84
- `Octopus Agent requires Python ${MIN_MAJOR}.${MIN_MINOR}+.`,
85
- "Install Python 3.11+ and make one of these commands available:",
86
- "python3, python, py -3.11, py -3",
87
- "Or set OCTOPUS_PYTHON_COMMAND to the Python command.",
88
- ].join("\n")
89
- );
90
- process.exit(1);
91
- }
92
-
93
- const packageRoot = path.resolve(__dirname, "..");
94
- const PYTHON_BOOTSTRAP = [
95
- "import runpy",
96
- "import sys",
97
- "from pathlib import Path",
98
- "package_root = Path(sys.argv[1]).resolve()",
99
- "sys.path.insert(0, str(package_root / 'src'))",
100
- "sys.path.insert(0, str(package_root))",
101
- "sys.argv = ['octopus_skill.octopus_run', *sys.argv[2:]]",
102
- "runpy.run_module('octopus_skill.octopus_run', run_name='__main__', alter_sys=True)",
103
- ].join("; ");
104
-
105
- const child = spawn(
106
- python.command,
107
- [...python.args, "-c", PYTHON_BOOTSTRAP, packageRoot, ...process.argv.slice(2)],
108
- {
109
- cwd: packageRoot,
110
- stdio: "inherit",
111
- windowsHide: true,
112
- }
113
- );
114
-
115
- child.on("error", (error) => {
116
- console.error(`Failed to start Python via ${python.source}: ${error.message}`);
117
- process.exit(1);
118
- });
119
-
120
- child.on("exit", (code, signal) => {
121
- if (signal) {
122
- process.kill(process.pid, signal);
123
- return;
124
- }
125
- process.exit(code ?? 0);
126
- });
2
+ import "../src/server.js";
package/src/config.js ADDED
@@ -0,0 +1,13 @@
1
+ const REQUEST_TIMEOUT_MS = 58_000;
2
+ const TOTAL_TIMEOUT_MS = 59_000;
3
+ const NETWORK_RETRIES = 2;
4
+
5
+ export function loadConfig(env = process.env) {
6
+ return {
7
+ baseUrl: (env.SPONGE_MCP_BASE_URL || "https://sponge-mcp.duliday.com").replace(/\/+$/, ""),
8
+ accessToken: (env.SPONGE_MCP_ACCESS_TOKEN || "").trim(),
9
+ requestTimeoutMs: REQUEST_TIMEOUT_MS,
10
+ totalTimeoutMs: TOTAL_TIMEOUT_MS,
11
+ networkRetries: NETWORK_RETRIES,
12
+ };
13
+ }
package/src/context.js ADDED
@@ -0,0 +1,5 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ export function createRequestId() {
4
+ return `req_${randomUUID()}`;
5
+ }