
SurfSensecreate_automation工具全解析一次调用完成意图起草 → JSON 生成 → 人工审批 → 持久化的自动化创建链路【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSenseSurfSense 是一个开源 NotebookLM 替代方案其多智能体聊天系统中的主智能体main agent通过create_automation工具让用户用一句自然语言描述想让 SurfSense 自主定时做什么即可在单次工具调用内完成自动化任务的草拟、结构化 JSON 生成、审批卡片确认与落库保存。本文将结合该工具的描述文档与仓库源码从意图规范、返回契约、底层实现到 drafter 子模型的数据结构完整剖析这条人机协同的自动化创建链路。工具定位一个调用三个阶段在 SurfSense 的多智能体架构中主智能体只暴露极少量工具见 主智能体工具注册表除create_automation外仅update_memory其余连接器、MCP 与交付物能力均委托给task子智能体。create_automation是主智能体侧唯一负责创建自动化的入口。按照 description.md 的定义它的职责是Draft and author a new automation. You describe the users intent; a focused drafter inside the tool turns it into the full automation JSON; the user sees a preview on an approval card and chooses approve or reject.整个流程分为三个连贯阶段且全部发生在单次工具调用内意图复述main agent 负责主智能体将用户的原话转述为一段具体的intent字符串JSON 起草工具内部的 drafter 子模型负责一个聚焦的草稿子 LLM 把intent翻译成完整的自动化 JSON审批与保存用户 服务层负责用户在审批卡片上看到结构化预览与原始 JSON选择 approve 或 reject批准后由服务层持久化。这一职责拆分是有意为之的架构决策源码中 create.py 的模块文档明确写道——The main agent only restates the users request as a singleintentstring. The drafting sub-LLM owns the JSON shape; the HITL card is the users review.主智能体只复述请求为一个intent字符串草稿子 LLM 负责 JSON 形状HITL 卡片是用户的审查环节。同样地prompt.py 的头部注释也强调真正的自动化 JSON 结构只存在于 drafter 的 prompt 中主智能体的 prompt 片段description.md/example.md只携带 intent 字符串示例主智能体永远不会看到 schema。调用时机何时触发该工具描述文档给出了明确的触发条件——当用户想让 SurfSense 自主做某件周期性或定时性的事情时任何 recurring重复性或 scheduled定时的诉求例如every morning…每天早上…each Monday…每周一…weekly recap…每周总结…反之一次性任务、即时性操作不应走此工具。这种触发语义在工具源码的 docstring 中得到了印证见 create.py 中create_automation的说明Use this when the user wants SurfSense to do something on its own on a schedule。intent参数规范What / When / Static values 三要素create_automation的唯一参数是intentstring其要求是用一段话具体地复述用户的请求必须覆盖以下三件事要素内容说明What做什么要执行的动作summarize、recap、post、draft 等动作本身When何时做调度 时区若用户提到了时区则一并写入未提及时区时留给 drafter 默认 UTCStatic values静态值自动化运行所需的固定参数folder ids、channel names、project keys、parent page ids 等必须逐项列出并给出值关键约束如果用户没有提供自动化所需的某个静态值必须在intent中明确说出来例如 the Notion parent page id was not specified以便 drafter 在 JSON 中留下占位符而不是编造 ID。这一点在 prompt.py 的 drafter 约定中得到了强化If the user did NOT supply a value the plan needs, putREPLACE_MEinstatic_inputs. Do NOT invent ids, channels, or paths.若用户未提供计划所需的值在static_inputs中写入REPLACE_ME绝不虚构 ID、频道或路径。few-shot 示例example.md 提供了两个完整的意图构造示例示例一静态值齐备user: Every weekday at 9am, summarize new documents in folder 12 and post the summary to Slack channel #daily-digest. → create_automation(intentEvery weekday at 09:00 UTC, summarize documents added to folder_id12 since the last run, then post the summary to Slack channel #daily-digest. Static inputs: folder_id12, slack_channel#daily-digest.) tool returns: {status: saved, automation_id: 42, name: Daily folder 12 digest} (Reply briefly: Saved as automation #42 — runs weekdays at 9am UTC.)注意主智能体将用户模糊的 9am 规范化为 09:00 UTC用户未提时区默认 UTC并把静态值显式列在Static inputs:中。示例二存在缺失值 → 占位符user: Once a week on Mondays at 7am Paris time, draft a Notion page recapping last weeks Jira tickets in project CORE. → create_automation(intentEvery Monday at 07:00 Europe/Paris, read last weeks Jira issues in project CORE, then draft a Notion page recapping them. Static inputs: jira_project_keyCORE. The user did NOT specify which Notion page the recap should sit under — leave notion_parent_page_id as a placeholder.) tool returns: {status: saved, automation_id: 51, name: Weekly CORE Jira recap} (Reply: Saved as automation #51. I left the Notion parent page id as a placeholder — set it on the automation before next Monday.)此例展示了两个要点用户明确给出了时区Paris time →Europe/Paris时区名且主智能体主动声明缺失的notion_parent_page_id以让 drafter 留占位符保存后回复中还要提醒用户在下次运行前补充占位值。不确认原则审批卡片本身就是确认描述文档有一条重要的交互约束值得单独强调Do NOT prompt the user to confirm before calling — the approval card IS the confirmation.也就是说主智能体不得在调用工具前先问用户确认吗。卡片本身承担确认职能它展示结构化预览加原始 JSON只提供 approve/reject 两个选项。用户若在预览后想修改就在聊天里回复修改意见然后主智能体用精炼后的intent再次调用本工具——这就是编辑路径edit path而不是在卡片上加编辑按钮。这一约束在工具源码 docstring 中原文复现见 create.pyThe card supports approve/reject only — if the user wants edits after seeing the draft, they say so in chat and you call this tool again with a refined intent. Do NOT prompt the user to confirm before calling — the card IS the confirmation.返回契约四种状态码及其处置create_automation的返回值是一个结构化 dict描述文档给出了四种状态及主智能体对应的处置方式status含义主智能体处置saved已保存携带automation_id与name简短确认Saved as automation #N — runs when.不要把 JSON 倒给用户rejected用户在卡片上拒绝确认一次Understood, I didnt create it.后停止除非用户提出新请求否则不得重试或推销变体invalid起草/校验失败发生在卡片展示之前携带issues与可选raw阅读 issues用补充的缺失细节精炼intent再次调用error内部错误携带message原样转达消息并提供重试rejected状态的不得重试约束尤其重要源码中在工具 docstring 与返回消息里双重强调Do not retry or suggest alternatives.源码级实现三阶段在代码中如何落地下面深入 create.py 的实现看三个阶段的真实代码路径。工具通过工厂函数create_create_automation_tool(workspace_id, user_id, llm, auth_context)创建workspace_id由聊天会话注入模型无需猜测llm复用主智能体的模型并在调用时打上surfsense:internal/automation-draft标签以便在可观测性链路中识别。阶段一Draft — 子 LLM 起草prompt build_draft_prompt(workspace_idworkspace_id, intentintent) response await llm.ainvoke( [HumanMessage(contentprompt)], config{tags: [surfsense:internal, automation-draft]}, ) raw_text extract_text_content(response.content).strip() draft _extract_json(raw_text)build_draft_prompt渲染 drafter 的完整 system prompt见 prompt.py由四段拼接_HEADER当前 UTC 时间 目标 workspace_id→_SCHEMAJSON 结构 v1 目录→_FEW_SHOTS两个 intent→JSON 完整示例→_FOOTER用户 intent。设计上刻意让_SCHEMA与_FEW_SHOTS保持纯字符串避免其中的 JSON 字面量与 Jinja 引用如{{ inputs.X }}被str.format的大括号转义破坏。模型输出经_extract_json提取它用正则_JSON_FENCE匹配 围栏容忍模型常见的 markdown 代码块包裹再json.loads解析并要求结果为 dict解析失败时返回{status: invalid, issues: [model output was not parseable JSON], raw: raw_text}。阶段二HITL 审批卡片validated_draft AutomationCreate.model_validate(draft) # 先校验 draft[workspace_id] workspace_id # 会话作用域注入 card_params validated_draft.model_dump(modejson, by_aliasTrue) card_params.pop(workspace_id, None) # 用户不可编辑 result request_approval( action_typeautomation_create, tool_namecreate_automation, paramscard_params, context{workspace_id: workspace_id}, tool_call_idruntime.tool_call_id, )起草结果先用AutomationCreatePydantic 模型见 api/automation.py做严格校验校验失败会以invalid状态返回并附带由_format_validation_issues生成的字段路径: 错误消息列表。workspace_id属于会话作用域而非用户可编辑字段因此在卡片参数中剔除。request_approval来自 self-gated 审批模块request.py。其核心机制是 langgraph 的interrupt卡片允许的决策为 approve / reject / edit 三种_SELF_GATED_DECISIONS用户在卡片上的选择会以统一 wire payload 的形式回传到parse_lc_envelope任何非 approve/edit 的意外决策都会 fail-closed 视为拒绝防止畸形前端信封走私副作用。另外工具名若命中 per-session 的trusted_tools白名单或全局DEFAULT_AUTO_APPROVED_TOOLS则会跳过中断直接放行自动批准。阶段三持久化final_payload {**result.params, workspace_id: workspace_id} final_validated AutomationCreate.model_validate(final_payload) # 用户编辑后重新校验 async with async_session_maker() as session: service AutomationService(sessionsession, authauth_context) created await service.create(final_validated) return {status: saved, automation_id: created.id, name: created.name}批准后由于用户在卡片上可能编辑过参数保存前会用AutomationCreate再次校验re-validate随后通过AutomationService.create原子性地创建自动化及其初始触发器见 automation.py 的create方法先做AUTOMATIONS_CREATE权限校验再捕获模型配置快照一个事务内写入Automation记录与其triggers。值得注意的是工具源码明确注释模型选择是在审批卡片上按自动化单独进行的premium/BYOK 选择器并由AutomationService.create在持久化时校验因此工具内部没有 fail-fast 的工作区模型资格门槛——当前聊天/角色模型的配置不再约束自动化的起草或保存。drafter 子模型的 JSON 结构AutomationCreate Schemadrafter 的输出必须严格符合 prompt.py 中_SCHEMA规定的形状该形状与 Pydantic 的AutomationCreate一一对应。其顶层结构如下{ name: 1-200 char identifier, description: one-liner or null, definition: { schema_version: 1.0, name: same as outer name, goal: one sentence, plan: [ { step_id: slug, action: agent_task, params: { query: Jinja string referencing {{ inputs.X }}, auto_approve_all: true } } ], metadata: {tags: [...]} }, triggers: [ { type: schedule, params: {cron: 5-field cron, timezone: IANA tz, default UTC}, static_inputs: {key: value, ...}, enabled: true } ] }各字段与仓库 Schema 的对应关系name1~200 字符对应 api/automation.py 中AutomationCreate.name的min_length1, max_length200约束description可为 null。definition对应 definition/envelope.py 中的AutomationDefinitionextraforbid意味着任何未声明字段都会导致校验失败。plan至少 1 步min_length1。plan[]对应 plan_step.py 的PlanStep支持step_id计划内唯一、action经注册表解析的动作类型、可选when谓词、params、output_as、max_retries与timeout_seconds。triggers[]外层触发器的创建形状对应 api/trigger.py 的TriggerCreatetypeparamsstatic_inputsenabled定义内部的trigger_spec则对应 trigger_spec.py。v1 目录与起草约定_SCHEMA明确当前 v1 目录只有一对动作/触发器组合Actionsagent_task参数为querystring支持 Jinja 模板与auto_approve_allbool。Triggersschedule参数为cron5 字段与timezoneIANA 时区名如UTC、Europe/Paris并携带static_inputs。与之呼应触发器类型枚举trigger_type.py注册了schedule与event两种类型manual在枚举中保留但暂未注册等待 Run now UX 重新设计。prompt 注释中也说明当新类型上线时将把内联的目录行替换为从app.automations.actions/app.automations.triggers渲染时拉取以避免multi_agent_chat的导入环。起草过程遵循几条硬性约定Jinja 变量必须可解析plan中引用的{{ inputs.X }}必须出现在某 trigger 的static_inputs或definition.inputs.schema_.properties中否则执行器在触发时无法解析。静态值归属触发器每次触发都不变的静态值folder ids、channel names、project keys、parent page ids放在static_inputs上而不是放进 plan。缺失值用REPLACE_ME用户未提供且计划需要的值在static_inputs中写REPLACE_ME绝不虚构。cron 为 5 字段分 时 日 月 周时区优先取用户提到的未指定默认UTC。触发时可用的模板变量inputs.*static_inputs 与运行时输入合并、inputs.fired_at、inputs.last_fired_at。_FEW_SHOTS中两个完整示例演示了这些约定示例一将cron: 0 9 * * 1-5、timezone: UTC、static_inputs: {folder_id: 12, slack_channel: #daily-digest}与查询模板...folder {{ inputs.folder_id }} since {{ inputs.last_fired_at or yesterday }}...组合示例二则演示notion_parent_page_id: REPLACE_ME占位符与inputs.fired_at的用法。主智能体侧的组装与注册在主智能体工具注册表registry.py中create_automation是声明顺序第一的工具其工厂_build_create_automation_tool通过延迟导入deferred import拉取create_create_automation_tool并把workspace_id、user_id、auth_context、llm四个依赖注入进去。依赖缺失时build_main_agent_tools会抛出ValueError指明缺失项。工具的展示元数据则独立存在于 shared/tools/catalog.py对应/agent/tools列表端点描述为 Draft an automation from an NL intent; user approves the card; tool saves与描述文档的语义完全一致——这是一个不依赖任何连接器的纯数据模块保持主智能体工具面自洽。可观测性与测试佐证自动化创建链路纳入了 SurfSense 的 OpenTelemetry 观测体系test_otel_span.py 的TestResolveToolName用例验证了create_automation工具调用可被解析为对应 span 的工具名drafter 的 LLM 调用也通过automation-draft标签在 traces 中可识别。AutomationService.create在成功落库后还会上报automation_created分析事件包含automation_id、workspace_id、trigger_count。此外AutomationDefinition.modelsAutomationModels会在创建时快照模型配置chat / image_gen / vision 三个模型 ID0为 Auto、 0为全局、 0为 BYOK使运行与后续聊天/工作区的模型变更解耦——这也是审批卡片上按自动化选模型的设计在数据层的落点。总结一次调用背后的分工哲学回顾整条链路create_automation的设计体现了一种清晰的职责分层用户用自然语言表达想要什么主智能体只做一件事——把用户意图规范化为结构化的intent字符串What / When / Static values并遵守先调用、不追问、卡片即确认的交互纪律drafter 子 LLM独占 JSON 结构知识把intent翻译成严格符合AutomationCreateschema 的 JSONHITL 审批卡片作为人工审查关卡提供结构化预览 原始 JSON只允许 approve/reject服务层二次校验、权限检查、模型快照与事务性持久化。对开发者而言这一模式可以直接借鉴在 Agent 工具设计中将意图表述与领域对象生成分层用聚焦子模型负责 schema 严格的结构化输出再以人工审批卡片兜底高风险副作用并在工具契约中用四种明确的状态码引导智能体的后续行为。它同时验证了 SurfSense 团队小工具面 子智能体委托的架构取向——主智能体保持精简复杂性被隔离在工具内部。如需进一步探索可继续阅读description.md、example.md、create.py、prompt.py以及自动化相关的 schemas/api/automation.py 与 services/automation.py。【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考