Agent Scope Java 2.x 系列【19】Harness:系统提示词
文章目录
- 1. 前言
- 2. 总体流程
- 3. 基础提示词
- 3.1 设置方式
- 3.2 存储位置
- 4. Transformer 链实现
- 4.1 源码入口
- 4.2 反射检测
- 4.3 串行执行
- 4.4 系统提示词中间件
- 4.4.1 WorkspaceContextMiddleware
- 4.4.2 TaskReminderMiddleware
- 4.4.3 PlanModeMiddleware
- 4.4.4 HarnessSkillMiddleware
- 4.4.5 SubagentsMiddleware(通过 onReasoning)
- 4.5 最终 System Prompt 结构
- 5. 生命周期要点
分析
AgentScope Harness中System Prompt的组装、注入和生命周期管理机制。
1. 前言
系统提示词(System Prompt)是给大模型底层预设的固定指令,优先级高于用户每一轮提问,相当于提前给AI定好身份、规则、能力边界、输出规范,全程生效。
在构建HarnessAgent时,指定了一个简单的系统提示词:
HarnessAgentagent=HarnessAgent.builder().name("harness-demo").description("HarnessAgent Demo").sysPrompt("你是一个中文 AI 助手").model(model).workspace(workspacePath).maxIters(5).build();在进行对话时,可以看到当前AI回复的内容,包含了更强大的能力,说明AgentScope框架内部对系统提示词进行了增强处理:
2. 总体流程
System Prompt通过Transformer 链逐层组装,最终注入到LLM推理的SYSTEM消息中。
HarnessAgent.builder().sysPrompt("你是 AI 助手") │ ▼ ReActAgent.sysPrompt ← 存储基础提示词 │ ▼ seedSystemMsg() 被调用(每次推理前) │ ▼ applySystemPromptMiddlewares(base, ctx) ← Transformer 链逐层变换 │ ├─→ custom MW1.onSystemPrompt() ├─→ custom MW2.onSystemPrompt() ├─→ WorkspaceContextMiddleware ← Session / Workspace / AGENTS / MEMORY / knowledge ├─→ TaskReminderMiddleware ← todo_write 使用说明 ├─→ PlanModeMiddleware ← Plan Mode 横幅 └─→ HarnessSkillMiddleware ← 技能列表 (<available_skills>) │ ▼ SystemMessage → 注入 reasoning 的消息列表首位关键设计:
seedSystemMsg()在每轮推理前被调用,所以修改AGENTS.md或MEMORY.md立即生效Transformer链通过反射检测——只有真正重写了onSystemPrompt的中间件才参与变换,避免不必要的block()调用
3. 基础提示词
3.1 设置方式
HarnessAgent.builder().sysPrompt("你是一个有用的 AI 助手,可以用中文回答问题。").build();3.2 存储位置
sysPrompt存储在ReActAgent实例中:
// ReActAgent.javaprivatefinalStringsysPrompt;// BuilderpublicBuildersysPrompt(StringsysPrompt){this.sysPrompt=sysPrompt;returnthis;}4. Transformer 链实现
4.1 源码入口
在Agent每轮调用前,都会通知所有钩子函数:智能体即将启动,其中会调用seedSystemMsg方法:
/** * 获取初始系统消息,用于在钩子执行前填充至{@link PreCallEvent}。 * * <p>默认实现返回{@code null}。子类(例如{@code ReActAgent})会重写该方法, * 根据自身配置的{@code sysPrompt}构建系统消息。 * * @param callScope 调用入口捕获的单次调用作用域(可为{@code null}); * 子类可通过该参数获取本次调用的{@link RuntimeContext},用于系统提示词中间件, * 无需读取共享实例字段 * @return 初始填充用系统消息,无则返回{@code null} */@OverrideprotectedMsgseedSystemMsg(ObjectcallExectution){RuntimeContextrc=callExectutioninstanceofCallExecutionce?ce.rc:getRuntimeContext();Stringbase=sysPrompt!=null?sysPrompt.trim():"";Stringprompt=applySystemPromptMiddlewares(base,rc);if(prompt==null||prompt.isEmpty()){returnnull;}returnSystemMessage.builder().name("system").content(TextBlock.builder().text(prompt).build()).build();}seedSystemMsg方法中会调用私有的applySystemPromptMiddlewares方法,按顺序执行所有系统提示词中间件,对原始系统提示词(sysPrompt)做拦截、修改、增强;若无自定义中间件逻辑,直接返回原提示词,避免不必要的异步阻塞。
// ReActAgent.java:536-569privateStringapplySystemPromptMiddlewares(Stringprompt,RuntimeContextctx){if(middlewares.isEmpty()){returnprompt;}// 通过反射检测哪些 middleware 真正重写了 onSystemPromptbooleanhasOverride=false;for(MiddlewareBasemw:middlewares){if(mw.getClass().getMethod("onSystemPrompt",Agent.class,RuntimeContext.class,String.class).getDeclaringClass()!=MiddlewareBase.class){hasOverride=true;break;}}if(!hasOverride){returnprompt;// 短路:没有任何 middleware 重写,直接返回}// 从左到右串行接力Mono<String>result=Mono.just(prompt);for(MiddlewareBasemw:middlewares){result=result.flatMap(p->mw.onSystemPrompt(this,ctx,p));}returnresult.block();}applySystemPromptMiddlewares方法的入参是【基础提示词】、【运行时上下文】:
4.2 反射检测
通过反射检测哪些middleware真正重写了onSystemPrompt方法,没有任何middleware重写,直接返回:
// 注释翻译:仅当至少一个中间件重写了onSystemPrompt方法时,才构建响应式异步链路// 基类默认实现是直接入参原样返回;该判断是为了避免无意义的block()阻塞调用// block()在非阻塞调度器(如Reactor并行调度器)中会抛出异常booleanhasOverride=false;// 遍历全部中间件for(MiddlewareBasemw:middlewares){try{// 反射获取 onSystemPrompt(Agent, RuntimeContext, String) 方法// getDeclaringClass():获取该方法实际定义的类// 如果 != MiddlewareBase,说明子类重写了该方法,存在自定义处理逻辑if(mw.getClass().getMethod("onSystemPrompt",Agent.class,RuntimeContext.class,String.class).getDeclaringClass()!=MiddlewareBase.class){hasOverride=true;break;}}catch(NoSuchMethodExceptionignored){// 极端异常:找不到该方法,视为存在自定义逻辑hasOverride=true;break;}}关键设计:
MiddlewareBase是中间件基类,自带默认onSystemPrompt,逻辑为直接返回入参prompt,无任何修改;- 用反射判断:中间件子类是否重写了该方法;
- 如果所有中间件都用基类默认实现 →
hasOverride=false,直接返回原始prompt,不走异步流程; - 目的:规避
block()阻塞,Reactor异步环境下随意调用block()会报错、破坏非阻塞性能。
默认配置下,自动装载了以下中间件:
4.3 串行执行
当存在重写了onSystemPrompt方法的中间件时,会构建异步处理链路,串行执行中间件:
// 封装原始prompt为响应式Mono流Mono<String>result=Mono.just(prompt);// 循环串联所有中间件的onSystemPrompt,串行执行// flatMap:异步链式调用,上一个中间件输出作为下一个输入for(MiddlewareBasemw:middlewares){result=result.flatMap(p->mw.onSystemPrompt(this,ctx,p));}// 阻塞等待全部中间件异步处理完成,拿到最终字符串返回returnresult.block();}Middleware列表按注册顺序排列,onSystemPrompt从左到右串行接力:
原始 prompt → mw[0].onSystemPrompt → mw[1].onSystemPrompt → ... → 最终 prompt自定义 Middleware 跑在最前面,因为HarnessAgent先注册用户Middleware,再注册内置Middleware。
4.4 系统提示词中间件
所有中间件中,有5个重写了onSystemPrompt方法
WorkspaceContextMiddlewareDynamicSkillMiddlewareHarnessSkillMiddlewarePlanModeMiddlewareTaskReminderMiddleware
在自动默认装载的8个,只有2个按照执行顺序排列:
WorkspaceContextMiddlewareHarnessSkillMiddleware
4.4.1 WorkspaceContextMiddleware
核心作用:自动向原始系统提示词尾部拼接工作区上下文片段(工作空间路径、Agent配置、记忆、知识库、会话信息等),同时做Token截断控长,避免超出模型上下文窗口。
重写onSystemPrompt钩子方法:
@OverridepublicMono<String>onSystemPrompt(Agentagent,RuntimeContextctx,StringcurrentPrompt){// 兜底空上下文:传入ctx为空则使用空白运行时上下文RuntimeContextrc=ctx!=null?ctx:RuntimeContext.empty();// 组装工作区完整上下文段落Stringsection=buildWorkspaceSection(rc);// 无上下文内容,直接返回原始提示词,不做修改if(section.isEmpty()){returnMono.just(currentPrompt);}// 原始提示词兜底为空串Stringbase=currentPrompt!=null?currentPrompt:"";// 换行分隔符:原始文本末尾已有换行则不加,否则补换行,排版整洁Stringseparator=base.isEmpty()||base.endsWith("\n")?"":"\n";// 原始提示词 + 分隔换行 + 工作区上下文段落,返回异步MonoreturnMono.just(base+separator+section);}核心上下文构建逻辑:
- 读取各类静态资源文件内容
Token预算计算 & 记忆内容截断(关键控长逻辑)- 拼装所有模块,生成完整附加段落
privateStringbuildWorkspaceSection(RuntimeContextrc){// 读取工作区 agents.md、memory.md、knowledge.md 内容并去除首尾空白StringagentsContent=workspaceManager.readAgentsMd(rc).strip();StringmemoryContent=workspaceManager.readMemoryMd(rc).strip();StringknowledgeContent=workspaceManager.readKnowledgeMd(rc).strip();// 获取当前工作空间根路径Pathworkspace=workspaceManager.getWorkspace();// 构建会话专属上下文片段StringsessionContext=buildSessionContextSection(workspace,rc);// 封装知识库块文本StringknowledgeBlock=buildKnowledgeBlock(rc,knowledgeContent,workspace);// 扩展附加上下文(自定义全局变量、权限、用户信息等)StringadditionalBlock=buildAdditionalContextBlock(rc);// 计算固定占用Token:会话信息、Agent配置、知识库、附加上下文intfixedTokens=estimateTokens(sessionContext)+estimateTokens(agentsContent)+estimateTokens(knowledgeBlock)+estimateTokens(additionalBlock);// 记忆文件单独Token消耗intmemoryTokens=estimateTokens(memoryContent);// 剩余可用Token额度 = 最大上下文Token上限 - 固定内容占用量intavailable=maxContextTokens-fixedTokens;// 剩余额度充足,但记忆文本超量 → 截断记忆内容至可用Token上限if(available>0&&memoryTokens>available){memoryContent=truncateToTokenBudget(memoryContent,available);// 工作空间描述段落:路径、文件系统信息StringworkspaceParagraph=buildWorkspaceParagraph(workspace,workspaceManager.getFilesystem());// 整合四大块加载资源:Agent配置、裁剪后记忆、知识库、附加上下文StringloadedContext=buildLoadedContextSection(agentsContent,memoryContent,knowledgeBlock,additionalBlock,rc);// 最终拼接:会话上下文 + 固定引导模板 + 工作空间说明 + 所有加载资源returnassembleSection(sessionContext,GUIDANCE_TEMPLATE,workspaceParagraph,loadedContext);}组装结构:
## AgentStateStore Context This is the HarnessAgent. Today's date is <日期>. My operating system is: <OS> The workspace directory is: <路径> The project's temporary directory is: <临时目录> ## Domain Knowledge / Memory Recall / Memory Persistence 引导段 (内置模板,教模型如何使用记忆系统和知识库) ## Workspace (按 filesystem 模式分支:本机 / 沙箱 / 远端) ## Workspace Files (Injected) The following files were loaded from your workspace: <loaded_context> <agents_context> (AGENTS.md 全文) </agents_context> <memory_context> (MEMORY.md,超出 maxContextTokens 则截断) </memory_context> <domain_knowledge_context> (KNOWLEDGE.md 全文 + knowledge/ 下所有文件路径清单) </domain_knowledge_context> <soul_md>(additionalContextFile 注入的额外文件)</soul_md> </loaded_context>关键参数:maxContextTokens默认8000,控制MEMORY.md的注入预算。
4.4.2 TaskReminderMiddleware
仅在Builder未禁用Task功能时注册。
onSystemPrompt:注入todo_write工具的静态使用说明:
## Task List You have a `todo_write` tool that maintains a structured task list for this session. Use it for multi-step work: capture the plan as todos, keep exactly one task `in_progress`, and update the whole list as you make progress...onReasoning:每轮推理前注入当前Todo列表的<system-reminder>块。
4.4.3 PlanModeMiddleware
PLAN 模式:注入只读设计阶段的提示横幅:
<system-reminder> PLAN MODE is active (read-only). Plan file: plans/PLAN.md Investigate the problem and draft a plan, but do NOT modify files... </system-reminder>BUILD 模式(刚从PLAN切出):注入执行提示:
<system-reminder>You have switched from PLAN to BUILD mode; the read-only restriction is lifted. An approved plan exists at plans/PLAN.md... </system-reminder>4.4.4 HarnessSkillMiddleware
注入当前可见技能列表,渲染为<available_skills>块:
<available_skills> - code-reviewer: 代码评审专家 - pdf-extractor: Extract text from PDF documents </available_skills>只列name+description,Agent判断相关后通过load_skill_through_path拉取完整指令。
技能来源(低优先级 → 高优先级,重名覆盖):
projectGlobalSkillsDir(Path)— 项目全局skillRepository(...)— 市场后端(Git/Nacos/MySQL/classpath)workspace/skills/— 工作区共用<userId>/skills/— 用户隔离
4.4.5 SubagentsMiddleware(通过 onReasoning)
不走onSystemPromptTransformer链,而是通过onReasoning直接修改SYSTEM消息内容——将子Agent使用指南prepend到消息列表首条SYSTEM消息中:
## Subagents You have access to subagent tools for spawning and coordinating isolated subagents. ### Agent Tools - agent_spawn / agent_send / agent_list ### Available agent ids - reviewer: 代码评审专家 - researcher: 技术调研助手 ### When to use subagents / When NOT to use...4.5 最终 System Prompt 结构
经过所有Transformer链处理后,一次典型推理的最终SYSTEM消息结构如下:
[HarnessAgent.builder().sysPrompt(...)] ← 基础提示词 [自定义 Middleware 注入] ← 如果有 ## AgentStateStore Context ← WorkspaceContextMiddleware Today's date is ... My operating system is ... ## Domain Knowledge ← WorkspaceContextMiddleware ## Memory Recall ## Memory Persistence ## Workspace ← WorkspaceContextMiddleware Project: ... Workspace: ... ## Workspace Files (Injected) ← WorkspaceContextMiddleware <loaded_context> <agents_context>AGENTS.md 全文</agents_context> <memory_context>MEMORY.md 全文(受预算截断)</memory_context> <domain_knowledge_context>KNOWLEDGE.md + 路径清单</domain_knowledge_context> </loaded_context> ## Task List ← TaskReminderMiddleware (todo_write 使用说明) <system-reminder>PLAN MODE is active...</system-reminder> ← PlanModeMiddleware(如开启) <available_skills> ← HarnessSkillMiddleware - skill-a: ... - skill-b: ... </available_skills>5. 生命周期要点
每轮推理前重新组装:修改
AGENTS.md、MEMORY.md、技能仓库后,下一次call()立即生效,不需要重启Agent。Transformer 链是同步阻塞的:
applySystemPromptMiddlewares()最后调用result.block(),这意味着所有onSystemPrompt中的异步操作必须在返回前完成。实际内置Middleware的onSystemPrompt都是同步返回Mono.just()。自定义 Middleware 跑在最前面:
HarnessAgent先注册用户.middleware()的实例,再注册内置Middleware。用户的onSystemPrompt可以看到最原始的sysPrompt。SubagentsMiddleware 的特殊性:它不走
onSystemPromptTransformer链,而是通过onReasoning直接操作消息列表中的SYSTEM消息。这是因为子Agent信息需要在每轮推理前动态刷新(文件系统可能随时新增子Agent声明),而onSystemPrompt链只在seedSystemMsg时执行一次。
