跳到主要内容

外部编程工具接入模式

CountBot 支持两种接入外部编程工具的模式:AI 模式(智能体编排)与直通模式(消息直接转发)。

两种模式对比

维度AI 模式直通模式
消息处理经 AgentLoop,模型可选择调用工具、读取文件、执行 Shell 命令直接转发给外部编程工具
能力范围完整:可读写文件、执行命令、调用技能、搜索记忆受限:仅能调用已启用的编程工具
适用场景需要模型自主决策的复杂任务简单任务,或用户明确指定工具

AI 模式

工作流程

用户消息 → ChannelManager → AgentLoop → 模型推理 → 工具调用 → exec → 外部编程工具

在 AI 模式下:

  1. 消息进入消息队列,由 AgentLoop 处理
  2. 模型通过系统提示词了解编程工具的能力
  3. 模型自主决定何时调用 exec 工具
  4. exec 工具启动外部编程工具的 CLI 进程
  5. 执行结果返回给模型,模型继续推理

编程工具在 AI 模式下的角色

编程工具在 AI 模式下是 AgentLoop 可调用的工具之一。模型可以:

  • 决定调用哪个编程工具
  • 构造调用参数
  • 根据输出结果调整策略

直通模式

工作流程

用户消息 → ChannelManager → 检测到直通模式 → 构建 ExternalAgentRequest → CLI 进程 → 返回输出

在直通模式下:

  1. 消息绕过 AgentLoop
  2. 系统直接构建 ExternalAgentRequest
  3. 调用 CliExternalAgentAdapter.run()
  4. 启动 CLI 进程并等待完成
  5. 将 stdout/stderr 返回给用户

启用直通模式的条件

  • 渠道账号的路由模式设置为 direct
  • 该账号配置了 external_coding_profile
  • 消息到达时,系统检测到路由模式为 direct,直接执行编程工具

会话模式(直通模式特有)

直通模式下,系统需要决定携带多少历史消息给外部编程工具。conversation.py 中的会话模式逻辑:

def resolve_effective_session_mode(profile: ExternalAgentProfile) -> str:
"""返回真正生效的会话模式。"""
mode = normalize_session_mode(profile.session_mode)
if mode == "native" and not profile_supports_native_session(profile):
return "history"
return mode

def profile_supports_native_session(profile: ExternalAgentProfile) -> bool:
"""判断当前 profile 是否可稳定使用原生会话。"""
command_name = Path(profile.command or "").name.lower()
profile_name = profile.name.strip().lower()
return command_name == "claude" or profile_name == "claude"

三种会话模式:

模式行为源码实现
stateless只发当前任务,不携带历史build_history_prompt() 直接返回当前任务
history携带最近 N 条历史消息build_history_prompt() 拼接历史到 prompt
native使用工具自身的会话能力cli.py:_build_argv() 为 claude 注入 --session-id

native 模式的实际行为:

只有当 profile 的 command 或 name 为 claude 时,native 模式才会真正生效:

  • 源码 cli.py:_build_argv() 检查 command_name == "claude"profile.name == "claude"
  • 如果是 claude 且模式为 native,CLI 参数中自动注入 --session-id <session_id>
  • 如果不是 claude,即使配置为 native,也会在 resolve_effective_session_mode() 中回退到 history

默认工具配置:

Profile默认 session_mode说明
claudenative支持 Claude Code 的内置会话
codexhistory回退到历史模式
opencodehistory回退到历史模式

配置外部编程工具

步骤

  1. 在设置页新增 profile

    • 名称:如 claudecodexopencode
    • 类型:当前仅支持 cli
    • 命令:如 claudecodex
    • 参数:支持 {prompt}{working_dir} 等占位符
  2. 配置会话模式

    • stateless:不携带历史
    • history:携带最近 N 条消息(默认 10,范围 1-50)
    • native:仅 claude 支持
  3. 配置环境变量

    • inherit_env:从父进程继承的变量名,如 ANTHROPIC_API_KEY
    • env:自定义环境变量
  4. 启用或禁用

    • 默认新建的 profile 为禁用状态
    • 需要在设置页启用

配置文件

配置存储在 workspace/external_coding_tools.json

{
"version": 1,
"profiles": [
{
"name": "claude",
"type": "cli",
"enabled": true,
"command": "claude",
"args": ["-p", "{prompt}"],
"working_dir": "",
"session_mode": "native",
"history_message_count": 10,
"inherit_env": ["ANTHROPIC_API_KEY"],
"timeout": 900
}
]
}

路由配置

渠道级别的编程工具路由

在渠道配置中,可以为每个渠道账号设置:

  • external_coding_profile:绑定的编程工具 profile 名称
  • routing_modeaidirect

路由逻辑

routing.py 中的路由逻辑:

async def route_message(
session_manager,
channel_name,
account_id,
chat_id,
message_content,
agent_loop,
external_coding_agent,
settings_store,
...
):
"""
根据渠道账号的路由设置,决定消息走向。

如果 routing_mode == "direct" 且 external_coding_profile 有值:
-> 调用 external_coding_agent.run(),直接执行编程工具
否则:
-> 调用 agent_loop.process_message(),走 AgentLoop
"""

路由优先级

  1. 渠道账号配置优先:如果账号配置了 routing_mode: "direct",则走直通模式
  2. 否则走 AI 模式:默认路由模式为 ai
  3. 模型可自主调用工具:在 AI 模式下,模型可以调用 exec 工具执行编程工具

CLI 执行细节

cli.py:CliExternalAgentAdapter 的执行流程:

async def run(self, profile, request, max_output_length, default_timeout):
# 1. 解析命令路径
command = self._resolve_command(profile.command)

# 2. 构建模板变量(prompt, working_dir, session_id 等)
variables = self._build_template_variables(request)

# 3. 构建 CLI 参数
argv = self._build_argv(command, profile, request, variables)
# native 模式下,claude 会注入 --session-id

# 4. 构建环境变量
env = self._build_env(profile, variables)
# 继承 PATH、HOME 等基础变量 + inherit_env + profile.env

# 5. 启动子进程
process = await asyncio.create_subprocess_exec(
*argv,
cwd=str(request.working_dir),
env=env,
stdin=PIPE if stdin_bytes else None,
stdout=PIPE,
stderr=PIPE,
)

# 6. 等待完成或超时
stdout, stderr = await asyncio.wait_for(process.communicate(stdin_bytes), timeout=timeout)

# 7. 返回结果
return ExternalAgentResult(
exit_code=process.returncode,
stdout=stdout_text,
stderr=stderr_text,
...
)

超时处理

  • Profile 级别 timeout > Request 级别 timeout > 默认 timeout(900s)
  • 超时后调用 process.kill() 终止子进程

输出截断

  • 最大输出长度由 max_output_length 配置控制
  • 超出部分会被截断并标注 ... (truncated N chars)

常见问题

直通模式 vs AI 模式该选哪个?

  • 直通模式:任务简单、用户明确知道要用什么工具、不需要模型自主决策
  • AI 模式:任务复杂、需要模型自主决定何时调用工具、需要读写文件或执行多步操作

为什么我的 native 模式没有生效?

检查 profile 的 command 或 name 是否为 claude。源码中只有 claude 才支持原生会话:

def profile_supports_native_session(profile):
command_name = Path(profile.command or "").name.lower()
profile_name = profile.name.strip().lower()
return command_name == "claude" or profile_name == "claude"

如果 command 不是 claude,即使配置了 native,也会回退到 history 模式。

如何在 AI 模式下调用编程工具?

在 AI 模式下,模型通过 exec 工具调用编程工具。确保:

  1. profile 已启用
  2. 系统提示词中包含编程工具的使用说明
  3. 模型知道可以调用 exec 工具

相关文件

  • backend/modules/external_agents/routing.py — 消息路由逻辑
  • backend/modules/external_agents/external_coding_agent.py — 编程工具调用入口
  • backend/modules/external_agents/adapters/cli.py — CLI 适配器实现
  • backend/modules/external_agents/conversation.py — 会话模式逻辑
  • backend/modules/external_agents/registry.py — Profile 管理

下一步