openJiuwen ChatAgent 实战指南:基于 LLMCall 与 ContextEngine 的最小对话智能体及 Prompt 调优集成

原创2026-10-10 02:52:461,449 阅读
文章标签:人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习

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)分为三步:

  1. 会话准备:从 inputs 中弹出 conversation_id(缺省为 "default_session"),作为自动创建的会话 ID;
  2. 会话创建或复用:
    • 若外部传入了 session,直接复用该会话;
    • 否则若实例内部已持有 self._session(兼容旧用法),复用之;
    • 否则调用 create_agent_session(session_id=..., card=AgentCard(id=self.agent_config.id)) 自动创建会话并执行 pre_run(inputs=inputs);
  3. 执行 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 的三种会话路径,可作为行为约定的依据:

  1. 不传 session:agent.invoke({"query": "hello"}) 不再抛 AttributeError,且返回结果包含 output 字段——验证了自动会话创建逻辑;
  2. 显式传 session:agent.invoke(inputs, session=create_agent_session(...)) 复用外部会话,结果同样包含 output;
  3. 携带 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 负责提供工具信息)。

十、注意事项与最佳实践

  1. 模型配置是硬性前提:ChatAgent 构造时要求 agent_config.model 为有效配置,否则会因 model 必填校验失败(Field(...) 为必填),且缺少 api_base / api_key 将无法完成真实模型调用;
  2. 占位符一致性:inputs 的键必须与 user_prompt 模板中的占位符对应(如 {{query}}),否则模板渲染结果会与预期不符;user_prompt 缺省时默认使用 {{query}};
  3. 会话 ID 约定:conversation_id 缺省为 "default_session",多轮对话若希望延续历史,请显式传入会话 ID 或复用外部 session 对象;
  4. 冻结开关决定可调优范围:LLMCallConfig.freeze_user_prompt 默认 True(用户提示词默认冻结),freeze_system_prompt 默认 False(系统提示词默认可调);接入优化器前应明确设置这两个开关,避免提示词被意外改写;
  5. 工具绑定时机:工具在 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 中“快速搭一个可对话、可调优的最小智能体”的标准姿势。

登录后查看全文
agent-core