@roll-agent/octopus-agent
v0.4.1
Published
Octopus Agent:编排 Sponge MCP Server 的 NL2SQL 查询链路
Readme
Octopus Agent
Octopus Agent 是 Node.js 实现的轻量 NL2SQL(自然语言转 SQL)调用客户端。正常查询调用 query_sponge(查询 Sponge),SQL 修复由 Sponge 主流程内部完成;查询成功后根据 Sponge 返回的能力契约,可继续调用 analyze_query_result(分析查询结果)或 export_query_csv(导出全部数据)。丸子不负责图表生成。
询问能力、没有可识别的查询对象或查询动作、闲聊、无关问题或要求增删改业务数据时,由 LLM(大语言模型)调用 get_query_guidance(获取查询引导);程序不使用字符串或正则匹配用户原话。查询引导只是动态示例,不是能力白名单;明确业务查询即使重复提出、未出现在引导中或仅缺少筛选条件具体值,也必须调用 query,由 Sponge 判断是否追问。用户要求使用、启动或检查丸子 Agent 时,仅通过 GET /mcp/health 执行健康检查,不尝试简单业务查询。
上一轮 query 返回 clarifying(等待补充条件)或成功结果展示『猜你想查』后,用户选择编号或追加其他要求时,下一次 query.question(查询问题)仍逐字传入用户本轮原话,例如 2,并分析;禁止在 Octopus 中替换为选项正文,选项解析和历史继承由 Sponge 统一处理。
委托查询使用同一个 query,显式传入 delegation(委托信息)。输入、返回、失败及原行追溯见 只读委托契约。下文普通查询的逐字展示与结束规则不适用于委托。
本地运行
要求 Node.js 22.6 或更高版本。query 使用 @roll-agent/sdk 的 defineTool(定义工具)和 Zod(输入校验)声明入参;现有 MCP 响应适配层继续负责标准答案和结构化内容。
npm install
npm test
npm startRoll Agent 接入
roll agent add .
roll run octopus-agent query --json \
--input-json '{"question":"查询本月各门店销售额","isNewConversation":true}'上层平台必须明确传递 isNewConversation(是否新建查询上下文)。当前聊天首次查询传 true;正常追问传 false,并从当前聊天已保存的工具结果中恢复上一次的 structuredContent.contextId(上下文编号)。确实无法恢复时,先提示用户重述完整问题;用户重述后,在同一聊天内传 true 新建查询上下文,再保存新返回的编号。仅补充条件时不得新建上下文,也不能从同一访问令牌的其他聊天推测当前聊天。
query(自然语言查询)入参:
| 字段 | 必填 | 中文说明 |
|-|-|-|
| question | 是 | 用户原始问题,不得改写 |
| isNewConversation | 是 | 是否新建查询上下文;当前聊天首次查询或上下文丢失后重述完整问题传 true,正常追问传 false |
| contextId | 条件必填 | 上一次返回的上下文 ID;普通查询且 isNewConversation=false 时必须传入 |
| resultFormat | 否 | 结果格式,支持 text/json/yaml/md/markdown/html,默认 markdown(Markdown 格式) |
同一上层对话的后续调用示例:
roll run octopus-agent query --json \
--input-json '{"question":"其中肯德基有多少个岗位","isNewConversation":false,"contextId":"ctx_a1b2"}'isNewConversation=true 时禁止同时传入 contextId,避免新上下文误接旧查询。上下文丢失后重述完整问题的调用示例:
roll run octopus-agent query --json \
--input-json '{"question":"查询本月上海门店销售额","isNewConversation":true}'上层模型调用时使用 --json(完整 JSON),展示 content[0].text(最终文本),并随当前聊天保存 structuredContent.contextId(上下文编号)、最近一次成功查询的 queryId(查询编号)和 availableActions(可执行动作)。后续查询只回传当前聊天的 contextId;导出和专项分析只使用最近一次成功查询的 queryId 及对应动作。新建查询上下文后,用新结果覆盖当前聊天的默认续接目标。
export_query_csv(导出全部数据)接收必填的 queryId(查询编号)和可选的 analysisRequest(分析要求)。仅当上一条结果的 structuredContent.availableActions(可用动作)包含 export_query_csv 时,用户追问“导出 CSV”“导出全部”“把刚才结果导出”等纯导出诉求才直接沿用该查询编号。同一上层对话存在多次成功查询时,新查询覆盖此前的默认续接目标;用户未明确指定更早对象或查询编号时,只能使用时间上最后一次成功 query 返回的 queryId。用户要求“导出并分析”时只调用本工具,将本轮原话逐字传入 analysisRequest,禁止随后调用只分析页面快照的 analyze_query_result;只要求导出时省略。不再次调用 query,也不改写上一条问题。Sponge MCP Server 基于实际导出的最多 1000 条数据生成分析,并将同一份分析统一写入 answer(答案)和 CSV,octopus-agent 原样展示,不自行生成或补充分析。用户同时修改筛选条件时才发起新的 query。
纯图表要求不属于丸子能力,由独立 Chart Agent(图表智能体)处理。用户同时要求查询和生成图表时,Roll 先通过 Octopus 完成查询并导出 CSV,再将 CSV 临时地址和用户原始制图要求交给 Chart Agent;Octopus 不识别图型、不生成 HTML,也不为制图重新查询。
analyze_query_result(分析查询结果)接收必填的 queryId 和 analysisRequest(分析要求)。仅当上一条结果的 availableActions 包含 analyze_query_result,且用户只要求分析、没有同时要求导出时调用;查询当轮待分析时沿用 Sponge 返回的分析要求,用户后续追问分析时逐字传入本轮要求,不重新查询。
get_query_guidance(获取查询引导)接收必填的 showQueryOnlyNotice(是否展示仅支持查询提示)和可选的 systemType(系统类型),支持 sponge(2.0,默认)和 supplier(供应商),沿用调用方当前系统上下文。能力询问传 false;闲聊、无关问题、没有可识别的查询对象或查询动作、增删改请求传 true。仅缺少筛选条件具体值时调用 query。Tool(工具)从 Sponge 的 uiResult(界面结果)直接返回公开 structuredContent(结构化正文),只包含 title(标题,可省略)、datasets(数据集)、blocks(展示块)、actions(导出动作);旧快照、角色、机器委托和终止标记不混入公开对象。content[0].text(首段文字)由同一对象生成;showAll(展示全部)仅保留为兼容旧调用的布尔参数,无需填写;当前始终完整展示全部内容。
查询引导通过 outputSchema(输出结构定义)和 _meta["roll/appOutput"](应用输出声明)声明 hm.ui-result(通用界面结果)第一版,remoteReadable=true(愿意远程分享)。读取仍取决于 Runtime(执行服务)、Relay(中继)及 Companion(伴随服务)的版本与授权;本工程不会替部署环境修改授权。契约源及合成样例见 references(交付参考)。纯引导只使用原协议已有的 markdown(格式化文本)展示块,datasets(数据集)和 actions(导出动作)均为空。能力说明、推荐问句和沟通最佳实践完整展示,用户可复制示例后提问。缺失或非法 uiResult 时返回协议错误,不能把旧四字段伪装成新契约正文。
get_supplier_home(获取供应商首页卡片)供未来 Web(网页)端首屏调用,无入参。structuredContent.cards(结构化卡片列表)按 type=metric(数据卡片)或 type=question(问题卡片)区分;数据卡片带 value(统计值)和 unit(单位),两类卡片都带点击时交给 query 的完整 question(问题原文)。统计由 Sponge 按访问令牌唯一对应的供应商实时计算,跨供应商或管理员令牌会被拒绝。首版只展示有明确口径的“尚未产生上岗结果的工单”和“昨日实际上岗工单”,暂不展示无法可靠归属到单个供应商的人员缺口。四个快捷问题使用当前供应商名称,避免复用管理视角的动态引导问题。
问题反馈
拿到用户反馈原文后直接调用 submit_query_feedback(提交查询反馈),不询问是否提交,不要求用户确认内容、选择分类或补齐详情,也不重新执行查询。分类由模型按语义选择,不确定时使用 other(其他);问题描述逐字保留用户原文,反馈由后台人工审核。缺少可选查询编号或原始提问时直接省略,不追问、不编造。多人令牌仅使用当前上下文已知且已授权的用户编号,不猜测身份。传入 category(问题类型,支持 wrong_result/结果有误、query_failed/查询失败、unclear/结果难懂、slow/查询慢、other/其他)和 description(用户描述)。优先附被反馈查询的 queryId(查询编号);查询中断而没有编号时可附 question(原始提问)。服务端按访问令牌核对查询归属。成功后返回 feedbackId(反馈编号)和 pending(待处理)状态;失败时不得显示已受理。
用户询问进度时直接调用 get_query_feedback(查询反馈进度),不要求用户提供编号。无编号返回此人的全部反馈,按最近处理时间和提交时间倒序;需要看某一条时,Agent 从列表取内部 feedbackId 再查详情,逐条展示状态与各自的处理说明。本轮直接请求某条详情但还没有列表时,先向 Agent 工具传 detailRequested=true(本轮需要详情),由 Agent 列出候选并继续查询唯一匹配的历史;该标志不传给 Sponge。多人令牌由可信调用方传入已授权 userId(用户编号)。管理员在 Sponge 的 /mcp/admin/trace-view#feedback 页面查看并更新反馈。
query 的结构化结果包含 resultKind(结果类型)、availableActions、pendingActions(待执行动作)及可能存在的 exportResult(自动导出结果)。pendingActions 非空时 terminal=false(当前调用链未结束),上层按顺序调用对应工具;全部完成后再统一展示每次工具返回的 content[0].text。resultKind=report(报表)且动作列表为空时,不显示查询编号,也不允许套用普通查询的导出或分析动作。
health(健康检查)无入参,返回 Sponge MCP Server 及业务库、权限库、审计库的 UP(正常)或 DOWN(异常)状态。
环境变量
| 环境变量 | 必填 | 中文说明 | 默认值 |
|-|-|-|-|
| SPONGE_MCP_BASE_URL | 是 | Sponge MCP Server 地址 | https://sponge-mcp.duliday.com |
| SPONGE_MCP_ACCESS_TOKEN | 是 | 身份透传 Token(令牌) | 无 |
Agent 内置单次请求超时 58 秒、query(查询)总超时 59 秒、网络重试 2 次,无需额外配置。每个新问题生成新的 requestId(请求编号),仅网络重试复用该编号。总超时低于当前 Roll 的 60 秒固定限制。
agentDurationSeconds(Agent 总耗时)保存在 structuredContent(结构化内容)供排查,不加入用户答案。计算范围为 octopus-agent 收到 query 请求到组装可交给平台渲染的答案,包含上下文处理、网络传输和服务端处理;不包含平台收到结果后的 UI(用户界面)绘制耗时。
isNewConversation=true 时 octopus-agent 直接生成全局唯一 contextId(上下文 ID),不读取历史。普通查询在 isNewConversation=false 时必须传入当前会话保存的 contextId;缺少时不查询其他会话,也不发起业务查询。MCP Server 在生成 SQL 时根据最终 ID 独立加载最近 5 轮;禁止 Server 自行切换 ID。requestId(请求编号)仍由 octopus-agent 每次请求重新生成。委托查询未提供 contextId 时使用独立新上下文。
Sponge Server 返回的 steps(阶段数据)仅用于内部审计,不再透传给上层 Agent;完整步骤通过现有 PostgreSQL 审计记录查看。
查询成功后,octopus-agent 直接使用 Sponge MCP Server 返回的 answer(答案),不校验是否包含数据分析,不调用 Roll Core Sampling(采样调用),也不使用程序重新生成分析。最终正文放在 result.content[0].text,不追加查询编号或耗时;queryId(查询编号)和 agentDurationSeconds 保存在 result.structuredContent 供后续动作和排查使用。result.content[1] 只要求上层 Agent 逐字复制并在需要时继续执行 pendingActions,不要求外层模型分析或重建表格。查询返回的 resultAnalysis(结果分析信息)和 steps(阶段数据)不再透传;contextId、contextAction(上下文动作)和 contextReason(判断原因)保留在结构化内容中。
接口名称:export_query_csv(导出全部数据)
接口说明:重新校验并执行原查询 SQL,最多导出 1000 条;传入 analysisRequest 时基于实际导出数据生成分析,并把同一份分析写入页面答案和 CSV。
入参:
| 字段 | 必填 | 中文说明 |
|-|-|-|
| queryId | 是 | 原查询编号,必须来自上一条可导出的查询结果 |
| analysisRequest | 否 | 用户本轮逐字提出的分析要求;只导出时省略 |
回参:
| 字段 | 中文说明 |
|-|-|
| queryId | 原查询编号 |
| rowCount | 实际导出条数 |
| url | CSV 下载地址 |
| expiresAt | 下载地址过期时间 |
| answer | 下载信息,以及按需生成的完整导出数据分析 |
| resultAnalysis | 按需返回的分析文本、状态和导出统计摘要 |
curl -X POST 'http://localhost:3001/mcp/tools/export_query_csv' \
-H 'Authorization: Bearer <访问令牌>' \
-H 'Content-Type: application/json' \
-d '{"queryId":"qry_a1b2","analysisRequest":"分析多次上岗人员和门店分布"}'{
"queryId": "qry_a1b2",
"rowCount": 1000,
"url": "http://localhost:3001/mcp/exports/example",
"expiresAt": "2026-08-26T10:00:00.000Z",
"answer": "## 导出结果\n已生成 CSV,共 1000 条。\n\n## 数据分析\n以下结论基于本次实际导出的 1000 条数据。",
"resultAnalysis": {
"status": "llm",
"text": "以下结论基于本次实际导出的 1000 条数据。",
"summary": {
"rowCount": 1000,
"exportLimit": 1000,
"exportLimitReached": true
}
}
}MCP Server 内部接口
接口名称:get_supplier_home(获取供应商首页卡片)
接口说明:按访问令牌对应的单一供应商返回初步数据统计和可点击问题;不接受前端传入供应商编号。统计时间使用 Asia/Shanghai(上海时区),昨日为当地自然日。
入参:无业务字段,请求体为 {}。
回参:
| 字段 | 中文说明 |
|-|-|
| generatedAt | 本次统计生成时间 |
| cards[].type | 卡片类型:metric(数据)或 question(问题) |
| cards[].title | 卡片标题 |
| cards[].value | 数据卡片的统计值;问题卡片不返回 |
| cards[].unit | 数据卡片的单位;问题卡片不返回 |
| cards[].question | 点击卡片后提交给查询工具的完整问题原文 |
curl -X POST 'http://localhost:3001/mcp/tools/get_supplier_home' \
-H 'Authorization: Bearer <供应商访问令牌>' \
-H 'Content-Type: application/json' \
-d '{}'{
"generatedAt": "2026-09-29T01:30:00.000Z",
"cards": [
{
"type": "metric",
"title": "昨日实际上岗工单",
"value": 15,
"unit": "单",
"question": "查询甲供应商昨天实际开始上岗的工单明细"
},
{
"type": "question",
"title": "本月交付情况",
"question": "查询甲供应商本月按报名时间统计的报名工单数、面试通过数和上岗成功数"
}
]
}示例数字仅演示格式,不代表真实业务数据。
接口名称:get_query_guidance(获取查询引导)
接口说明:读取 Sponge 当前系统的引导内容;“猜你想查”依次使用当前访问令牌专属配置、本系统公共配置、系统默认内容,在 trace-view(追踪管理页)的“文案配置 → 猜你想查”维护。每组一至三条,按配置顺序整组返回,不按业务用户编号或角色选择,也不补齐三条。访问令牌由现有鉴权识别,无需新增入参。HTTP(超文本传输协议)内部结果保留旧字段并增加界面投影,recommendedQuestions(推荐问题)与界面推荐同源。Agent(智能体)公开出口只使用 uiResult(界面结果)。
入参:
| 字段 | 必填 | 中文说明 |
|-|-|-|
| systemType | 否 | 系统类型:sponge(2.0,默认)、supplier(供应商) |
回参:
| 字段 | 中文说明 |
|-|-|
| snapshot | 快照时间范围、生成时间及下次刷新时间 |
| weeklyHotQuestions | 本周热门问题 |
| recommendedQuestions | 推荐问题 |
| blueprintGroups | 可复用查询蓝图分组 |
| uiResult | 严格契约的界面结果;Agent 对外直接作为结构化正文 |
curl -X POST 'http://localhost:3001/mcp/tools/get_query_guidance' \
-H 'Authorization: Bearer <访问令牌>' \
-H 'Content-Type: application/json' \
-d '{"systemType":"supplier"}'{
"snapshot": {
"timezone": "Asia/Shanghai",
"weekStart": "2026-08-24T00:00:00+08:00",
"weekEndExclusive": "2026-08-31T00:00:00+08:00",
"generatedAt": "2026-08-25T09:00:00+08:00",
"nextRefreshAt": "2026-08-25T12:30:00+08:00"
},
"weeklyHotQuestions": [],
"recommendedQuestions": [],
"blueprintGroups": [],
"uiResult": {
"title": "丸子 · 供应商使用引导",
"datasets": [],
"blocks": [{
"id": "intro",
"type": "markdown",
"content": "我可以帮你查询供应商业务数据,并分析已查询的结果。"
}],
"actions": []
}
}接口名称:analyze_query_result(分析查询结果)
接口说明:基于已成功查询的服务端留存结果执行专项分析,不重新生成或执行 SQL。
入参:
| 字段 | 必填 | 中文说明 |
|-|-|-|
| queryId | 是 | 原查询编号,必须来自上一条结果 |
| analysisRequest | 否 | 分析要求;不传时复用原查询已保存的明确要求,但最终必须存在非空要求 |
回参:
| 字段 | 中文说明 |
|-|-|
| queryId | 原查询编号 |
| status | 分析状态 |
| analysisRequest | 本次分析要求 |
| answer | 已渲染的分析正文 |
| analysis | 分析内容 |
| analysisStatus | 模型分析状态 |
| cached | 是否复用已有分析 |
curl -X POST 'http://localhost:3001/mcp/tools/analyze_query_result' \
-H 'Authorization: Bearer <访问令牌>' \
-H 'Content-Type: application/json' \
-d '{"queryId":"qry_a1b2","analysisRequest":"比较供应商高低"}'{
"queryId": "qry_a1b2",
"status": "succeeded",
"analysisRequest": "比较供应商高低",
"answer": "## 数据分析\n甲供应商高于乙供应商。",
"analysis": "甲供应商高于乙供应商。",
"analysisStatus": "llm",
"cached": false
}安全边界
- 正常查询只调用一次
query_sponge;SQL 生成、校验和最多两次自动修复都由 Sponge 主流程完成。 - 上一条查询的
availableActions包含export_query_csv时,纯导出追问才沿用queryId调用该工具;禁止重新调用query_sponge。导出仍由 Sponge MCP Server 去掉展示上限、应用导出上限并重新执行安全与权限校验。 - 上一条查询的
availableActions包含analyze_query_result时,才可沿用queryId做专项分析;查询当轮待分析时沿用服务端要求,后续追问时逐字传入用户本轮要求,禁止自行改写或重跑查询。 - 查询已自动导出全部数据时,保留 Sponge 返回的
exportResult,直接展示下载与分析正文。 - 导出成功时,Sponge MCP Server 返回的
answer已包含 CSV 下载信息和完整导出数据分析;octopus-agent 与上层 Agent 只能原样展示。 - 查询返回
failed(失败)时原样展示服务端错误并结束,禁止追问或自行补充查询条件。 - 查询返回
clarifying(等待补充条件)时原样展示服务端追问;用户下一轮补充条件后使用原contextId继续调用query。 - 查询成功或失败都使用
renderMode=verbatim_markdown(原样 Markdown)或renderMode=verbatim(原样文本)。octopus-agent 不校验或补充 Sponge MCP Server 返回的answer;上层 Agent 只能逐字展示content[0].text。terminal=false时按pendingActions顺序继续调用,不重新查询;terminal=true且nextAction=stop时结束调用链。 - 使用或检查 Agent 时只调用
health,禁止通过query做连接测试。 - 非明确查询场景调用
get_query_guidance;能力询问直接展示动态引导,其他场景先说明目前只支持查询。 - 未经用户明确确认,不调用
export_query_csv。 - Agent 不接收或传入任意 SQL。
- Agent 不读取 Schema,不生成 SQL,也不生成数据分析;用户语义路由由上层 LLM 负责,不使用正则或硬编码业务判断。
- 认证 Token 仅用于身份透传,数据权限由 Sponge MCP Server 校验。
- 不新建数据库。MCP Server 使用现有
mcp_query_run持久化完整问答;当前会话的contextId由上层平台随会话记录保存并在续问时回传。
