openJiuwen ChatAgent 实战指南:基于 LLMCall 与 ContextEngine 的最小对话智能体及 Prompt 调优集成
openJiuwen ChatAgent 实战指南:基于 LLMCall 与 ContextEngine 的最小对话智能体及 Prompt 调优集成
导读
ChatAgent 是 openJiuwen agent-core 在 openjiuwen.dev_tools.tune 模块中提供的最简对话式智能体:它只依赖 LLM 调用(LLMCall)与上下文引擎(ContextEngine)两个核心组件,即可完成一次完整的“模型调用 + 会话管理 + 工具绑定”闭环。本文以官方 API 文档 docs/en/2.Development Guide/API Docs/openjiuwen.dev_tools/tune/chat_agent.md 为骨架,结合源码与单元测试,完整讲解 create_chat_agent_config、create_chat_agent、ChatAgentConfig、ChatAgent 的用法,并深入剖析 invoke / stream 的会话管理机制以及 get_llm_calls() 如何与 JointOptimizer、InstructionOptimizer 等提示词优化器对接,让读者既能快速跑通一个可对话的智能体,也能将其接入 Prompt 自动调优流水线。
一、ChatAgent 在 openJiuwen 调优工具链中的定位
openjiuwen.dev_tools.tune 是 openJiuwen 提供的“提示词调优”工具链模块,包含数据加载、评估器、优化器与训练器四类能力,其对外导出清单见 openjiuwen/dev_tools/tune/init.py:
CaseLoader/Case/EvaluatedCase:数据集与评估结果载体;InstructionOptimizer/ExampleOptimizer/JointOptimizer:提示词指令/示例优化器;DefaultEvaluator:默认评估器;Trainer:训练编排器;ChatAgent及其工厂函数:作为被调优的对象——即以“对话智能体”的形式承载待优化的 LLM 调用。
ChatAgent 是其中最简单的智能体形态,源码位于 openjiuwen/dev_tools/tune/chat_agent/chat_agent.py,其核心注释即点明定位:“ChatAgent - simplest llm chat single_agent”(最简单的 LLM 对话单智能体)。它继承自 openjiuwen.core.single_agent.legacy 中的 LegacyBaseAgent,内部封装了 LLMCall 与 ContextEngine,支持 invoke(一次性返回)与 stream(流式返回)两种调用方式,并可配合 evaluator 与 optimizer 完成提示词自动调优。
二、API 总览与调用关系
ChatAgent 模块对外暴露 4 个核心成员(见 chat_agent/init.py):
| 成员 | 类型 | 作用 |
|---|---|---|
create_chat_agent_config |
函数 | 依据 agent 元信息与 LLM 调用配置,构造 ChatAgentConfig 配置对象 |
create_chat_agent |
函数 | 依据配置创建 ChatAgent 实例,并绑定可选工具列表 |
ChatAgentConfig |
类(Pydantic 模型) | 继承 AgentConfig,仅新增 model 字段 |
ChatAgent |
类 | 最简对话智能体,提供 invoke / stream / get_llm_calls |
完整调用链可概括为:
create_chat_agent_config(agent_id, agent_version, description, model)
└──> ChatAgentConfig(id, version, description, model)
└──> create_chat_agent(config, tools) ──> ChatAgent
├── invoke(inputs, session) → Dict{output, tool_calls}
├── stream(inputs, session) → AsyncIterator[Dict]
└── get_llm_calls() → Dict{"llm_call": LLMCall}
1. 工厂函数 create_chat_agent_config
create_chat_agent_config(agent_id: str, agent_version: str, description: str, model: LLMCallConfig) -> ChatAgentConfig
该函数将四个参数原样封装为 ChatAgentConfig 实例,源码见 chat_agent.py。参数含义:
- agent_id (str):智能体唯一标识,会写入
AgentCard,并在工具获取、会话管理等场景作为区分键; - agent_version (str):智能体版本号;
- description (str):智能体描述信息;
- model (LLMCallConfig):LLM 调用配置,包含 model、model_client、system_prompt、user_prompt 等字段,其定义位于
openjiuwen.core.single_agent.legacy(源码见 openjiuwen/core/single_agent/legacy/config.py)。
2. 工厂函数 create_chat_agent
create_chat_agent(agent_config: ChatAgentConfig, tools: List[Tool] = None) -> ChatAgent
根据配置实例化 ChatAgent,并将 tools 列表绑定到智能体上(agent.add_tools(tools or [])),默认值为 None。绑定后的工具会以 ToolInfo 的形式在每次 LLM 调用时传入模型(详见后文 invoke 实现)。
三、配置模型:ChatAgentConfig 与 LLMCallConfig
1. ChatAgentConfig
class ChatAgentConfig(AgentConfig):
model: LLMCallConfig = Field(...)
源码位于 openjiuwen/dev_tools/tune/chat_agent/chat_config.py,它继承自 AgentConfig,仅在基类基础上新增必填的 model 字段。AgentConfig 基类(openjiuwen/core/single_agent/legacy/config.py)还提供了以下可选字段:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id |
str | "" |
智能体 ID(与工厂函数 agent_id 一致) |
version |
str | "" |
版本号 |
description |
str | "" |
描述信息 |
controller_type |
ControllerType | Undefined |
控制器类型 |
workflows |
List | [] |
关联的工作流 Schema 或 Card |
model |
Optional[ModelConfig] | None |
基类预留的模型配置(ChatAgent 使用子类强化的 model 字段) |
tools |
List[str] | [] |
工具名列表 |
2. LLMCallConfig:决定模型如何被调用
LLMCallConfig 是 ChatAgentConfig.model 的字段类型,也是整个智能体配置的核心,字段定义见 config.py:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
model |
Optional[ModelRequestConfig] | None |
模型请求配置,核心是 model 名称 |
model_client |
Optional[ModelClientConfig] | None |
模型客户端配置(provider、api_base、api_key、ssl 等) |
system_prompt |
List[Dict] | [] |
系统提示词,如 [{"role": "system", "content": "..."}] |
user_prompt |
List[Dict] | [] |
用户提示词模板,可含 {{query}} 占位符 |
freeze_system_prompt |
bool | False |
是否冻结系统提示词(冻结后优化器不会改写) |
freeze_user_prompt |
bool | True |
是否冻结用户提示词,默认冻结 |
其中 ModelRequestConfig 与 ModelClientConfig 来自 openjiuwen.core.foundation.llm。一个典型的模型配置组合是:
from openjiuwen.core.foundation.llm import ModelRequestConfig, ModelClientConfig
model_config = ModelRequestConfig(model="gpt-4o-mini")
model_client_config = ModelClientConfig(
client_provider="OpenAI",
api_base=API_BASE, # 例如 https://api.openai.com/v1
api_key=API_KEY,
)
在 ChatAgent.__init__ 中,这两个配置会被组装为 Model 实例(_init_model 方法),再连同 system_prompt、user_prompt、两个 freeze 开关一起构造 LLMCall(见 chat_agent.py):
self._llm_call = LLMCall(
llm_config.model.model_name,
self._init_model(llm_config.model, llm_config.model_client),
llm_config.system_prompt,
llm_config.user_prompt,
llm_config.freeze_system_prompt,
llm_config.freeze_user_prompt
)
四、深入 LLMCall:ChatAgent 的模型调用内核
LLMCall 位于 openjiuwen/core/operator/legacy/llm_call/base.py,是 ChatAgent 内部真正执行“提示词格式化 + 模型调用 + 优化器回调”的组件,其构造函数签名与 ChatAgent.__init__ 中的传参一一对应:
LLMCall(
model_name: str,
llm: Model,
system_prompt: str | List[BaseMessage] | List[Dict],
user_prompt: str | List[BaseMessage] | List[Dict],
freeze_system_prompt: bool = False,
freeze_user_prompt: bool = True,
llm_call_id: str = "llm_call",
)
几个值得注意的底层细节:
- 默认用户提示词:当
user_prompt为空时,LLMCall会使用DEFAULT_USER_PROMPT = "{{query}}"(见 base.py); - 消息组装顺序:
_format_llm_input将消息组装为system_messages + history_messages + user_messages,即系统提示词在前、会话历史居中、用户问题在后(base.py); - 提示词模板渲染:system/user prompt 会先经
PromptTemplate.format(inputs)渲染,inputs中的键即模板占位符; - 优化器回调:
LLMCall.invoke/stream在每次调用后,若设置了_optimizer_callback,会回调(llm_call_id, inputs, response, session),这正是 ChatAgent 与优化器衔接的桥梁(base.py)。
五、invoke:一次性对话调用
async invoke(inputs: Dict, session: Session = None) -> Dict
invoke 同步(一次性)调用智能体,返回模型文本输出与工具调用结果,返回值形如 {"output": str, "tool_calls": list}。其内部流程(见 chat_agent.py)分为三步:
- 会话准备:从
inputs中弹出conversation_id(缺省为"default_session"),作为自动创建的会话 ID; - 会话创建或复用:
- 若外部传入了
session,直接复用该会话; - 否则若实例内部已持有
self._session(兼容旧用法),复用之; - 否则调用
create_agent_session(session_id=..., card=AgentCard(id=self.agent_config.id))自动创建会话并执行pre_run(inputs=inputs);
- 若外部传入了
- 执行 LLM 调用:通过
self.context_engine.create_context(session=session)获取历史消息,再调用self._llm_call.invoke(...),其中tools来自Runner.resource_mgr.get_tool_infos(tool_id=[...], tag=self.agent_config.id),即把当前绑定工具的元信息传给模型。
参数说明:
- inputs (Dict):输入字典,键需对应 user_prompt 模板占位符,例如
{"query": "用户问题"};可额外携带conversation_id指定会话; - session (Session, optional):外部会话对象,默认
None(此时内部自动创建并管理会话)。
返回:Dict,包含 output(模型文本回复)与 tool_calls(工具调用列表)。
注意:会话的自动创建与复用逻辑对应一个已修复的历史缺陷——此前
self._session未在__init__中初始化,导致不传 session 调用invoke时会抛AttributeError。修复后__init__显式初始化self._session = None,并在未传 session 时自动创建(详见 tests/unit_tests/dev_tools/tune/test_chat_agent_invoke.py 的模块注释)。
六、stream:流式对话调用
async stream(inputs: Dict, session: Session = None) -> AsyncIterator[Any]
stream 以异步迭代器的形式逐块产出 {"output": str, "tool_calls": list},适用于打字机式流式输出场景。其流程与 invoke 类似,但有两处差异(chat_agent.py):
- 调用的是
self._llm_call.stream(...),逐块yield dict(output=result.content, tool_calls=result.tool_calls); - 当未传外部 session 时,在迭代结束后执行
await agent_session.close_stream()与await agent_session.commit()完成会话收尾。
返回:AsyncIterator[Dict],每个元素包含 output 与 tool_calls。
七、get_llm_calls:与提示词优化器对接的接口
get_llm_calls() -> Dict
该方法是 ChatAgent 参与 Prompt 调优的关键通道(chat_agent.py):
def get_llm_calls(self) -> Dict:
return dict(llm_call=self._llm_call)
它返回内部 LLMCall 映射,键固定为 "llm_call",值为 LLMCall 实例。这个映射可以直接作为优化器的 parameters 参数传入:
BaseOptimizer.bind_parameter(parameters)会将每个LLMCall包装为TextualParameter(见 openjiuwen/dev_tools/tune/optimizer/base.py),并在__enter__/__aenter__时批量把trace_callback设置到所有 LLM 调用上(base.py),从而在智能体每次推理时自动记录TraceNode(case_id、llm_call_id、inputs、outputs)到OptimizeHistory;- 优化器的
backward/update阶段依据评估结果计算梯度并改写提示词。由于ChatAgent的LLMCall遵循freeze_system_prompt/freeze_user_prompt开关(update_system_prompt/update_user_prompt只有在未冻结时才生效,见 llm_call/base.py),因此可以在构造LLMCallConfig时通过冻结标记控制哪些提示词允许被优化器改写; JointOptimizer内部同时组合InstructionOptimizer(指令优化)与ExampleOptimizer(示例优化),随机选择优化策略并将梯度写回(见 openjiuwen/dev_tools/tune/optimizer/joint_optimizer.py)。
典型的调优接入方式为:optimizer = JointOptimizer(model_config, model_client_config, parameters=agent.get_llm_calls()),随后在 with optimizer: 上下文中运行智能体并采集评估结果。
八、完整可运行示例(含流式与调优接线)
1. 基础对话(官方示例)
import os
import asyncio
from openjiuwen.dev_tools.tune import create_chat_agent_config, create_chat_agent
from openjiuwen.core.single_agent.legacy import LLMCallConfig
from openjiuwen.core.foundation.llm import ModelRequestConfig, ModelClientConfig
API_BASE = os.getenv("API_BASE", "your api base")
API_KEY = os.getenv("API_KEY", "your api key")
MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o-mini")
MODEL_PROVIDER = os.getenv("MODEL_PROVIDER", "OpenAI")
model_config = ModelRequestConfig(model=MODEL_NAME)
model_client_config = ModelClientConfig(
client_provider=MODEL_PROVIDER,
api_base=API_BASE,
api_key=API_KEY,
)
llm_config = LLMCallConfig(
model=model_config,
model_client=model_client_config,
system_prompt=[{"role": "system", "content": "你是一个助手。"}],
user_prompt=[{"role": "user", "content": "{{query}}"}],
)
config = create_chat_agent_config(
agent_id="chat_agent",
agent_version="1.0.0",
description="",
model=llm_config,
)
agent = create_chat_agent(config)
async def main():
result = await agent.invoke({"query": "你好"})
return result["output"]
asyncio.run(main())
# '你好!有什么可以帮助你的?'
2. 流式调用
async def stream_main():
async for chunk in agent.stream({"query": "请介绍 openJiuwen", "conversation_id": "conv-001"}):
print(chunk["output"], end="")
asyncio.run(stream_main())
3. 绑定工具
若智能体需要调用工具,可将工具列表传给工厂函数:
from openjiuwen.core.foundation.tool import Tool
# tool_list: List[Tool],例如通过工具管理器获取的工具实例
agent = create_chat_agent(config, tools=tool_list)
result = await agent.invoke({"query": "帮我查一下天气"})
# result["tool_calls"] 中即为模型发起的工具调用
4. 接入提示词优化器
from openjiuwen.dev_tools.tune import JointOptimizer
optimizer = JointOptimizer(
model_config=model_config,
model_client_config=model_client_config,
parameters=agent.get_llm_calls(), # {"llm_call": LLMCall}
)
# 在 with 上下文中运行 agent 采集轨迹,再依据评估结果 backward/update
async with optimizer:
result = await agent.invoke({"query": "..."})
# optimizer.backward(evaluated_cases) / optimizer.update()
九、测试佐证与行为约定
单元测试 tests/unit_tests/dev_tools/tune/test_chat_agent_invoke.py 覆盖了 invoke 的三种会话路径,可作为行为约定的依据:
- 不传 session:
agent.invoke({"query": "hello"})不再抛AttributeError,且返回结果包含output字段——验证了自动会话创建逻辑; - 显式传 session:
agent.invoke(inputs, session=create_agent_session(...))复用外部会话,结果同样包含output; - 携带 conversation_id:
agent.invoke({"query": "hello", "conversation_id": "my_custom_conv"})会以该 ID 作为自动创建的会话 ID。
测试中还透露出两条运行约定:
- 测试前置会设置环境变量
LLM_SSL_VERIFY=false与IS_SENSITIVE=false,在关闭 SSL 校验的非敏感环境下运行; - 调用前需
await Runner.start(),结束后await Runner.stop()——Runner是 openJiuwen 运行时的资源管理器(Runner.resource_mgr负责提供工具信息)。
十、注意事项与最佳实践
- 模型配置是硬性前提:
ChatAgent构造时要求agent_config.model为有效配置,否则会因model必填校验失败(Field(...)为必填),且缺少 api_base / api_key 将无法完成真实模型调用; - 占位符一致性:
inputs的键必须与user_prompt模板中的占位符对应(如{{query}}),否则模板渲染结果会与预期不符;user_prompt缺省时默认使用{{query}}; - 会话 ID 约定:
conversation_id缺省为"default_session",多轮对话若希望延续历史,请显式传入会话 ID 或复用外部session对象; - 冻结开关决定可调优范围:
LLMCallConfig.freeze_user_prompt默认True(用户提示词默认冻结),freeze_system_prompt默认False(系统提示词默认可调);接入优化器前应明确设置这两个开关,避免提示词被意外改写; - 工具绑定时机:工具在
create_chat_agent时一次性绑定,运行时通过Runner.resource_mgr.get_tool_infos按tool_id与agent_config.id过滤获取工具元信息,因此需确保对应工具已在运行时资源管理器中注册。
总结
ChatAgent 以不到 140 行的核心实现,为 openJiuwen 的 Prompt 调优工具链提供了最简智能体载体:create_chat_agent_config 负责配置装配,create_chat_agent 负责实例化与工具绑定,invoke / stream 覆盖对话与流式场景,get_llm_calls() 则打通了与 JointOptimizer / InstructionOptimizer 等优化器的数据通路。理解这份 API,就等于掌握了在 openJiuwen 中“快速搭一个可对话、可调优的最小智能体”的标准姿势。