引言
在之前的文章,我们分析了Trae Agent整体执行思路和LLM是如何调用内置工具的一些细节。还有一块内容我觉得是有必要了解到,就是Agent对于LLM客户端的封装,毕竟每个供应商提供的LLM使用方式都有些差异,Agent需要将差异性封装起来,使我们使用LLM时使用一个统一的LLM Client对象就可以和LLM进行交互了。
Agent 如何使用 LLMClient
LLMClient在使用上十分的简介,重点的就是初始化、设置轨迹记录器、chat。屏蔽了LLM供应商的细节。

LLMClient下的内容
这里需要用一个类图来展示llm_client.py中的内容了,它是一个工厂模式,根据我们允许命令时指定要使用的LLM供应商,来决定是实例化某一个对象,比如我们执行"trae-cli run "写一个最简单的网页" --provider doubao",会实例化DouBaoClient。可以看到LLMClient下包含了各种供应商Client。

DouBaoClient实现分析
基于 doubao_client.py
的代码,我们来分析一下实现一个 LLM 供应商客户端需要的关键细节。
初始化配置
python
def __init__(self, model_parameters: ModelParameters):
super().__init__(model_parameters)
# 1. API Key 获取(环境变量 + 配置文件)
if self.api_key == "":
self.api_key: str = os.getenv("DOUBAO_API_KEY", "")
# 2. Base URL 配置
if self.base_url is None or self.base_url == "":
self.base_url: str | None = os.getenv("DOUBAO_API_BASE_URL")
# 3. 创建底层客户端
self.client: openai.OpenAI = openai.OpenAI(
base_url=self.base_url, api_key=self.api_key
)
# 4. 初始化消息历史
self.message_history: list[ChatCompletionMessageParam] = []
核心实现的方法
ini
@override
def chat(
self,
messages: list[LLMMessage],
model_parameters: ModelParameters,
tools: list[Tool] | None = None,
reuse_history: bool = True,
) -> LLMResponse:
# 1. 消息格式转换
doubao_messages = self.parse_messages(messages)
# 2. 历史消息管理
if reuse_history:
self.message_history = self.message_history + doubao_messages
else:
self.message_history = doubao_messages
# 3. 工具模式转换
tool_schemas = None
if tools:
tool_schemas = [
ChatCompletionToolParam(
function=FunctionDefinition(
name=tool.get_name(),
description=tool.get_description(),
parameters=tool.get_input_schema(),
),
type="function",
)
for tool in tools
]
# 4. API 调用(带重试机制)
response = None
for i in range(model_parameters.max_retries):
try:
response = self.client.chat.completions.create(
model=model_parameters.model,
messages=self.message_history,
tools=tool_schemas if tool_schemas else openai.NOT_GIVEN,
temperature=model_parameters.temperature,
top_p=model_parameters.top_p,
max_tokens=model_parameters.max_tokens,
n=1,
)
break
except Exception as e:
# 重试逻辑
time.sleep(random.randint(3, 30))
continue
# 5. 响应解析和格式转换
choice = response.choices[0]
# 6. 工具调用解析
tool_calls: list[ToolCall] | None = None
if choice.message.tool_calls:
tool_calls = [
ToolCall(
name=tool_call.function.name,
call_id=tool_call.id,
arguments=json.loads(tool_call.function.arguments)
)
for tool_call in choice.message.tool_calls
]
# 7. 构建标准响应
llm_response = LLMResponse(
content=choice.message.content or "",
tool_calls=tool_calls,
finish_reason=choice.finish_reason,
model=response.model,
usage=LLMUsage(
input_tokens=response.usage.prompt_tokens,
output_tokens=response.usage.completion_tokens,
)
)
# 8. 更新消息历史
# ... 历史更新逻辑
# 9. 轨迹记录
if self.trajectory_recorder:
self.trajectory_recorder.record_llm_interaction(
messages=messages,
response=llm_response,
provider="doubao",
model=model_parameters.model,
tools=tools,
)
return llm_response
细节:豆包客户端使用了OpenAI兼容的API格式,可以直接使用OpenAI 的 Python SDK 作为底层实现。
具体Client之间的差异
每个Client实现类都略微有些差异。比如DoubaoClient和OpenAIClient底层SDK都是用openai Python SDK,AnthropicClient使用原生 anthropic Python SDK。
在消息处理上,DoubaoClient & OpenAIClient都使用相同的 OpenAI 消息格式,AnthropicClient会是用 Anthropic 特有格式。
还有在工具调用、响应处理等方面,不同的Client都会有些差异。
OpenRouterClient分析
我注意到有一个openai_client.py。它的特别指出在于不是一个具体的 LLM 供应商 ,而是一个 LLM 聚合服务平台 。它的作用类似于一个"中介"或"代理",提供统一的 API 接口来访问多个不同的 LLM 提供商。
OpenRouter 的优势
- 统一接口 : 通过一个 API 访问多个 LLM 提供商
- 模型选择 : 可以在不同模型间轻松切换
- 成本优化 : 可以根据价格和性能选择最合适的模型
- 简化集成 : 不需要为每个 LLM 提供商单独集成
- OpenAI 兼容 : 使用熟悉的 OpenAI API 格式
总结
这篇文章,我们对Agent中LLM Client的封装也有了一定的了解。这让我们学习到一个程序如何调用LLM的诸多细节。接下来会持续关注Trae Agent开源库的发展,会给我们带来什么新的思路和创意。