Skip to content

plugin-agent — 默认对话编排器

包名: @aalis/plugin-agent
源码: packages/plugin-agent/src/index.ts

概述

默认的 AgentService 实现,负责编排完整的对话流程:组装提示词 → 加载历史 → 收集工具 → 调用 LLM → 工具循环 → 发送回复。

插件声明

typescript
meta.name = '@aalis/plugin-agent'
meta.provides = ['agent']
meta.inject = { optional: ['llm', 'memory', 'persona', 'message-archive', 'platform', 'media', 'storage'] }

配置

字段类型默认值说明
defaultLLMllm-ref默认对话模型:全局默认 LLM。apply() 时调用 ctx.preferService("llm", provider/model) 锁定 ServiceContainer 偏好。会话 / 平台 profile 未覆盖时生效。
systemPrompttextarea''行为准则提示词:定义 Agent 的行为准则。当人设插件存在时,身份描述由人设提供,此处仅作为行为指令追加。
memoryTokenBudgetnumber4096长期记忆预留 Token:为长期记忆注入的 system 消息预留的 token 额度,截断时不会删除这些消息
historyLimitnumber50历史消息条数:从记忆中加载的最近对话历史条数
maxToolIterationsnumber30最大工具迭代:工具调用循环的最大迭代次数
promptBuildTimeoutMsnumber10000提示词贡献构建超时 (ms):单个 agent:prompt 贡献 build 的等待上限。挂死的构建(如网络检索卡住)超时后本轮缺席、其余照常,避免拖住每次 LLM 调用。0 表示不设限。
toolResultMaxRationumber0.15工具结果最大比例:单条工具结果占上下文窗口的最大比例 (0~1),超出则截断。例如 0.15 表示 15%
trimThresholdRationumber1裁剪触发比例:裁剪预算 = 上下文长度 × 该比例 − 最大输出 token − 512 安全余量(下限 1024)。本次调用估算输入 token 超过该预算才会对消息列表做内存裁剪(不影响 DB)。默认 1.0 表示用满扣除输出预留后的可用窗口;调低可提前裁剪。压缩触发请在“@aalis/plugin-memory-summary”中配置。

指令

本插件注册以下斜杠指令(由 commands 服务统一解析):

指令说明
/model [关键词]列出 / 搜索可用对话模型(分页,-p <n> 翻页)
/persona [关键词]列出 / 搜索可用人设(分页,-p <n> 翻页)
/session查看当前对话生效的模型 / 人设 / thinking / 名称,及各自来源与解析链
/session.set设定会话级覆盖(持久化,重启不丢)
/session.reset复位会话级覆盖:默认清模型 + 人设 + thinking,-m/-p/-t 单独清对应项(显示名不在此列)

/session.set 的选项:

选项说明
-mprovider/model模型引用,即 LLM entry 的 contextId;用 /model 列出可选值
-p人设卡名不含后缀
-ton / offthinking 开关
-n显示名会话显示名称
/session.set -m @aalis/plugin-llm-openai:main/gpt-4o -p catgirl
/session.set -p strict-reviewer
/session.set -t off
/session.set -n 深夜助手

全局默认模型由本插件的 defaultLLM 配置项决定;会话级设置优先于它。

核心流程

  1. agent:input:before: 消息预处理 / 拦截;中间件不调用 next() 则以下步骤(含 LLM 调用)均不执行
  2. 构建系统提示词: persona 提示词 + 配置的 systemPrompt(无人设时仅 systemPrompt),末尾固定追加输入约定块
  3. 加载历史: 从 memory 服务获取最近 historyLimit 条消息,跳过不完整的工具调用组与控制类消息
  4. 收集工具: 从 tools 服务获取工具定义:无分组的通用工具恒在,带分组的只取会话生效配置 enabledToolGroups 列出的分组('*' 为全部,未配置则一个都不取)
  5. 执行 Hook 管道:
    • 组装 agent:prompt 贡献 — 记忆 / 摘要 / 技能 / 档案等提示词块按锚位物化进消息列表,单个贡献 build 超过 promptBuildTimeoutMs 时本轮缺席
    • agent:llm:before — 拦截、修改消息列表或工具列表(如工具搜索过滤、媒体规范化)
    • trimMessages() — 按 token 预算裁剪上下文
    • chatStream() — 流式调用 LLM
    • agent:llm:after — 处理 LLM 响应
    • 工具调用循环(最多 maxToolIterations 次):
      • agent:tool:before → 执行工具 → agent:tool:after
    • agent:reply:before — 后处理回复内容
    • agent:turn:after — 消息处理完成通知
  6. 保存: 用户消息在回合开始时经 message-archive 归档;每轮工具调用组(assistant + tool)与最终助手回复经 message-archive 的 saveMessage 写入;空回复不保存
  7. 发送: 经 gateway 服务的 dispatchOutbound 分发(经过出站中间件链);gateway 缺失时回退为直接发出 outbound:message 事件

上下文裁剪算法

估算 token 超过预算(上下文长度 × trimThresholdRatio − 最大输出 token − 512,下限 1024)时,trimMessages() 按下表各阶段依次裁剪,任一阶段后回到预算内即停止。

保护规则

  • 首条系统消息(主提示词)— 永不删除
  • 最新用户消息(当前任务上下文)— 永不删除
  • 最后一组工具调用(assistant + tool 成组)— 永不删除
  • 首条之后的注入系统消息(贡献块、易变上下文等)合计按 memoryTokenBudget 预留;超出时先在阶段 1 按比例缩减,阶段 5 作为最后手段删除

裁剪阶段

阶段操作说明
1缩减注入的系统消息首条与末条之间的 system 消息合计超过 memoryTokenBudget 时按比例截短,每条最少保留 200 字符
2截断过长工具输出>1500 字符 → 保留前 500 字符
2.5精简思考内容超过 200 字符的 reasoningContent 从旧到新截断:旧条保留 200 字符,最新一条保留 400 字符(只截断不删除)
3摘要旧工具调用组除最后一组外,压缩为 [历史工具调用] 工具名 → 结果前 100 字符 形式的单条 assistant 消息(仅在确实节省 token 时)
4删除最旧非系统消息跳过受保护消息,assistant + tool 成组删除
5删除注入的系统消息最后手段

压缩后延续提示

阶段 4 之后仍超预算时进入阶段 5;此路径结束时若消息总数比裁剪前少 6 条及以上,在最后一条用户消息之后注入一条系统提示:

[系统提示] 由于上下文长度限制,部分历史消息已被压缩或移除。请基于当前可见的上下文和最新用户请求继续完成任务,不要因为看不到之前的细节而停止工作。如果你之前有正在执行的多步骤任务或计划,请查看对话摘要和 todo-list 工具确认当前进度,然后继续未完成的步骤。

该提示防止模型因丢失上下文而放弃正在进行的任务。

扩展点

其他插件可通过 agent:* 中间件钩子拦截或修改各阶段,通过 agent:prompt 贡献点向提示词注入内容,也可以用 registerPreprocessor 注册输入预处理器,都无需修改 Agent 代码。详见 events.md

Token 预算追踪与日志

首轮与每次工具迭代的 LLM 调用前(裁剪之后),Agent 估算 prompt 消耗并发出 token:usage 事件;收到 token:request(plugin-webui-server 在客户端订阅会话而无缓存用量、或手动压缩完成后发出)时,也会按当前会话生成一次快照:

ts
'token:usage': [{
  sessionId, platform, contextWindow, maxTokens, tokenBudget,
  used, usageRatio,
  breakdown: {
    system, persona, memorySummary, memoryVector, skills, platform,
    subtask, systemOther, history, toolResults, toolDefs, reservedForReply,
    injectors, // Record<string, number>:systemOther 按注入者标签细分的明细
  }
}]

消费者:

Agent 自身额外维护一个节流日志记录器

  • 状态:sessionId → { count, lastRatioBucket }
  • 触发条件(满足任一即打印):
    • usageRatio 所处区间(< 0.5 / 0.5–0.7 / 0.7–0.85 / ≥ 0.85)与上次不同时(含回落与会话首次)
    • 每 10 轮强制打印一次
  • 标签:OK / INFO / WARN / CRITICAL,与 prompt_budget_info 工具阈值对齐

日志样例:

[token-usage:WARN] <sessionId> 23104/32000 (72.2%) sys=6100(persona=2300 mem=1800 skills=900 subtask=0 other=1100) hist=8901 tools=5400+2703def reserve=4096

CLI 与文件日志中也能看到预算消耗,无需打开 WebUI。