使用 Terraform Provider 管理 LiteLLM Proxy 上的 A2A Agent(litellm_agent 资源完整指南)
LiteLLM 官方 Terraform Provider(仓库位于 terraform/provider/)提供了 litellm_agent 资源,用于以 Infrastructure as Code 的方式在 LiteLLM Proxy 上管理遵循 A2A(Agent-to-Agent)协议的智能体。本文围绕该资源的 HCL 用法展开,逐项讲解 agent_card_params、litellm_params、object_permission、请求头转发与四组限流参数的字段语义,并结合 Provider 的 Go 实现与 LiteLLM Proxy 的 /v1/agents 管理端点源码,说明其底层 CRUD 行为、状态刷新规则、敏感值处理与 import 注意事项,帮助读者把 Agent 的注册、授权、限流全部纳入 Terraform 版本化管理。
一、A2A Agent 与 litellm_agent 资源定位
在 LiteLLM 中,A2A Agent 是指可以被发现(discover)、被调用(invoke)并通过 A2A 协议进行编排的 AI 实体。LiteLLM Proxy 将这些 Agent 以统一卡片形式注册为代理侧对象,配合模型(models)、MCP Server 等资源一起被网关统一纳管。
Terraform Provider 中的 litellm_agent 资源即对应这一对象的管理入口。它在 Provider 侧(Go 代码)封装了对 Proxy 管理端点的全部操作,对应的端点常量为 resource_agent.go:
POST /v1/agents:创建 Agent;GET /v1/agents/{agent_id}:读取单个 Agent;PATCH /v1/agents/{agent_id}:更新 Agent;DELETE /v1/agents/{agent_id}:删除 Agent。
这些端点与 LiteLLM Proxy 路由实现 agent_endpoints/endpoints.py 中定义的 POST /v1/agents、PATCH /v1/agents/{agent_id} 等一一对应,说明该资源直接对接网关原生管理 API,而非绕行其他模块。
二、Provider 声明与整体配置骨架
使用 litellm_agent 前,需先完成 Provider 声明与连接配置。Provider 版本与 LiteLLM 版本号保持一致(例如 ~> 1.99.0 对应运行 1.99.x 的 Proxy),见 provider/README.md 的 Versioning 说明:
terraform {
required_providers {
litellm = {
source = "BerriAI/litellm"
version = "~> 1.99.0" # 与你的 Proxy 运行版本保持一致
}
}
}
provider "litellm" {
api_base = var.litellm_api_base
api_key = var.litellm_api_key
}
api_base 与 api_key 也支持通过环境变量 LITELLM_API_BASE、LITELLM_API_KEY 提供,参见 docs/index.md。api_base 应指向你的 Proxy 实例地址,Provider 管理端点中的 /v1/agents 即基于该地址拼接。
三、资源完整示例与逐字段拆解
3.1 一份可运行的完整配置
resources/agent.md 给出的完整示例覆盖了本文讨论的全部参数,是学习该资源的最佳起点:
resource "litellm_agent" "hello_world" {
agent_name = "hello-world-agent"
agent_card_params = jsonencode({
protocolVersion = "1.0"
name = "Hello World Agent"
description = "Just a hello world agent"
url = "http://localhost:9999/"
version = "1.0.0"
defaultInputModes = ["text"]
defaultOutputModes = ["text"]
capabilities = {
streaming = true
}
skills = [
{
id = "hello_world"
name = "Returns hello world"
description = "just returns hello world"
tags = ["hello world"]
examples = ["hi", "hello world"]
}
]
})
litellm_params = jsonencode({
make_public = false
})
object_permission = jsonencode({
models = ["gpt-4-proxy"]
mcp_servers = ["my-mcp-server-id"]
})
static_headers = {
"x-api-key" = var.agent_api_key
}
extra_headers = ["x-request-id"]
tpm_limit = 100000
rpm_limit = 1000
session_tpm_limit = 10000
session_rpm_limit = 100
}
配置块逻辑清晰:先用 A2A Agent Card 描述"这个 Agent 是谁、有什么能力",再用 litellm_params 描述"网关侧如何代理它",接着用 object_permission 划定访问范围,最后通过请求头与限流字段约束运行时行为。
3.2 必填字段:agent_name 与 agent_card_params
agent_name(Required):Agent 在 Proxy 上的名称,必须全局唯一。它对应底层请求体中的agent_name字段。agent_card_params(Required):以 JSON 对象字符串形式提供的 A2A Agent Card(HCL 中用jsonencode构造)。支持标准 A2A Card 的全部常用字段,包括name、description、url、version、protocolVersion、capabilities、skills、defaultInputModes、defaultOutputModes、preferredTransport、iconUrl、provider、documentationUrl等。
这里有一个非常值得注意的行为:LiteLLM Proxy 在存储 Card 时会并入 LiteLLM 前端字段(例如 supportedInterfaces),因此 Provider 在设计上让配置值保持权威性(authoritative in state),即只要你在 HCL 中写了 agent_card_params,刷新状态时就不会用 API 返回的、被合并过的 Card 覆盖它。该逻辑在 resource_agent.go 的 Read 实现中非常明确:仅在 state 中 agent_card_params 为空(典型场景是 import)时,才从 API 回填 Card。
Go Schema 中还为 JSON 字符串字段挂载了 agentSuppressEquivalentJSON 作为 Diff 抑制函数(resource_agent.go)。它会把新旧 JSON 各自反序列化后再用 reflect.DeepEqual 比较,从而避免因 key 顺序、空白符差异而触发无意义的计划变更——这意味着你的 Card 即使调整了字段书写顺序,只要语义对象相同,Terraform 就不会产生 diff。
3.3 可选但关键:litellm_params(敏感)
litellm_params(Optional, Sensitive):LiteLLM 专有参数的 JSON 对象字符串。它可能包含 api_key 等密钥,因此 Provider 永远不会从 API 回读该字段;以你在 HCL 中配置的值为准(configured value is authoritative)。上例中的 {"make_public": false} 即通过该字段控制 Agent 是否公开展示。
测试 resource_agent_test.go 直接演示了这一设计:测试数据同时配置了 litellm_params: {"model": "gpt-5.2", "api_key": "sk-secret"},随后 Read 阶段 API 即便返回了掩码值 sk-1****,测试也断言 state 中的 litellm_params 仍保持原始配置 sk-secret(见 resource_agent_test.go),绝不会被回读值污染。
3.4 访问控制:object_permission
object_permission(Optional):以 JSON 对象字符串形式表达的访问控制权限,键支持:
mcp_serversmcp_access_groupsmcp_tool_permissionsmodelsagents
上例将 models 限定为 ["gpt-4-proxy"]、mcp_servers 限定为 ["my-mcp-server-id"],表示该 Agent 只允许访问这些模型与 MCP Server。与 agent_card_params 一致,object_permission 同样带 JSON 等价 Diff 抑制,且在非 import 场景下以配置值为权威。
3.5 请求头控制:static_headers 与 extra_headers
static_headers(Optional, Sensitive):随 Agent 请求发出的静态请求头 Map。它可能携带 token(如示例中的x-api-key),因此与litellm_params一样永不从 API 回读。在 Go Schema 中类型为schema.TypeMap、元素为字符串(resource_agent.go)。extra_headers(Optional):需要透传给 Agent 的入站请求头名称列表。示例中的["x-request-id"]表示:当客户端带x-request-id头访问该 Agent 时,Proxy 会将其原样转发给 Agent 后端。它对应底层响应结构中的extra_headers []string字段,且该字段是可回读的(Read 时会从 API 同步回 state)。
两个字段的差异本质在于敏感度:值是"常量密钥"的进 static_headers,值是"从入站请求动态提取的头部名称"的进 extra_headers。
3.6 限流参数:四组 TPM/RPM 限制
资源支持四个可选整数限制字段,对应 Proxy 侧 Agent 的 agentAPIResponse 结构体(resource_agent.go):
| 参数 | 含义 | 文档示例值 |
|---|---|---|
tpm_limit |
Agent 每分钟 Token 数上限 | 100000 |
rpm_limit |
Agent 每分钟请求数上限 | 1000 |
session_tpm_limit |
单会话每分钟 Token 数上限 | 10000 |
session_rpm_limit |
单会话每分钟请求数上限 | 100 |
这四个字段在 API 响应中为指针类型(*int),Read 时仅当非 nil 才写入 state,可据此推断:若 Proxy 返回的某字段为空值,Terraform 会将其按零值/缺省处理。将它们与 models、mcp_servers 权限配合,即可为一个 Agent 同时框定"能用什么资源"与"用多快"两层边界。
四、底层 CRUD 实现与状态管理
4.1 生命周期映射
从 resource_agent.go 的资源定义看,litellm_agent 走完整 CRUD 生命周期:
- Create:调用
buildAgentData聚合出请求体后POST /v1/agents。buildAgentData(resource_agent.go)先把agent_card_params解析为 JSON 对象,再按需附加litellm_params、object_permission,以及static_headers、extra_headers与四组限流字段。若agent_card_params不是合法 JSON 对象,会直接报错——测试 resource_agent_test.go 用"not-json"入参验证了这一校验。创建成功后将响应中的agent_id写入d.SetId(),随即触发一次 Read 以刷新 state。 - Read:
GET /v1/agents/{agent_id}。当 API 返回 404 时,Provider 会将该资源 ID 清空并从 state 中移除(resource_agent.go),确保外部删除不会造成 state 与真实状态脱节。 - Update:
PATCH /v1/agents/{agent_id},携带与 Create 相同结构的请求体,属于整体覆盖式更新,完成后同样回读刷新。 - Delete:
DELETE /v1/agents/{agent_id},随后清空 ID;对 404 做了幂等容忍(不报错、正常完成删除语义)。
这些行为全部有 httptest mock 服务器级别的单元测试覆盖(如 resource_agent_test.go 断言 Update 必须走 PATCH /v1/agents/agent-123,resource_agent_test.go 断言 Delete 必须走 DELETE),可作为"资源确实按此协议工作"的实现级证据。
4.2 敏感字段为何"只写不回读"
litellm_params 与 static_headers 的 Sensitive 语义来自安全设计而非 Terraform 限制:LiteLLM Proxy API 出于防泄漏考虑,从不返回这些字段的未掩码值(测试 mock 中返回的就是 sk-1****)。因此 Provider 在 Read 中对二者不做任何回填,state 与真实状态均以配置为准。
这一设计带来一个实用推论:对这两个字段做原地修改时,计划里看到的变更就是真实要发送的变更;但也意味着如果你在配置外手工改动 Agent 的密钥,Terraform 无法感知漂移。对密钥的轮换应始终通过修改 HCL 并 apply 进行。
4.3 Read 的"选择性回填"细节
对其他字段,Read 的逻辑是"只在 state 为空时才从 API 回填"(如 agent_card_params、object_permission),而 extra_headers、限流字段与审计属性(created_at、updated_at、created_by、updated_by)则每次 Read 都从响应同步。测试 resource_agent_test.go 验证了 import 场景下 agent_card_params 会被正确填充为可解析的 JSON,同时 litellm_params 仍保持为空字符串(因为不可回读)。
五、导出属性(Attribute Reference)
除上述参数外,资源导出以下只读属性(可在引用处直接读取,如 litellm_agent.hello_world.id):
| 属性 | 说明 |
|---|---|
id |
LiteLLM 分配的 Agent ID |
created_at |
Agent 创建时间戳 |
updated_at |
Agent 最近更新时间戳 |
created_by |
创建该 Agent 的用户 |
updated_by |
最近更新该 Agent 的用户 |
这些审计属性在 Go 的 agentAPIResponse 中均有对应 JSON 字段(resource_agent.go),数据来源即 Proxy /v1/agents/{agent_id} 的真实响应。
六、导入既有 Agent(Import)
已经通过控制台或 API 创建好的 Agent 可以纳入 Terraform 管理,导入命令按 Agent ID 进行:
terraform import litellm_agent.example <agent_id>
Resource 层通过 schema.ImportStatePassthroughContext 直接以传入 ID 落库,随后走正常 Read 流程。
需要特别留意文档中的提示:由于 litellm_params 与 static_headers 含有密钥,API 永远不会返回它们的未掩码值,因此import 后这两个字段无法恢复。正确流程是:import 完成后立即在 HCL 中补齐这两个字段的配置并执行 terraform apply,让配置值重新成为权威来源,否则后续 plan 会一直显示这两个字段的追加变更。
七、配套数据源:只读查询现有 Agent
除资源外,Provider 还提供只读数据源,便于在配置中引用已存在的 Agent。单个查询数据源 docs/data-sources/agent.md 用法如下:
data "litellm_agent" "existing" {
agent_id = "123e4567-e89b-12d3-a456-426614174000"
}
output "agent_card" {
value = jsondecode(data.litellm_agent.existing.agent_card_params)
}
数据源暴露 agent_name、agent_card_params、object_permission、extra_headers、四组限流字段以及额外的 spend(该 Agent 累计消费额)。其底层实现 data_source_agent.go 直接复用资源相同的 GET /v1/agents/{agent_id} 端点与 agentAPIResponse 解析结构;查询不存在的 Agent 会直接报错(与资源在 404 时清空 ID 的行为形成对照)。
安全上,数据源刻意不暴露 litellm_params 与 static_headers,原因正是它们可能含有 API Key 或 token(见 data-sources/agent.md 的 Security Note)。
此外还有列表形态的数据源 litellm_agents(见 data_source_agent.go),它通过 GET /v1/agents 拉取全部 Agent,并支持可选的 health_check = true——开启后 Proxy 会逐个探测各 Agent 的 URL,仅返回可达或未配置 URL 的 Agent,适合做网关侧 Agent 健康度巡检。
八、写在最后:把 A2A Agent 纳入 GitOps 的建议
综合文档与源码,把 A2A Agent 用 Terraform 管理时,以下几个实践能避免最常见的坑:
- Agent Card 与状态一致性:Proxy 会向 Card 合并
supportedInterfaces等自有字段,但 Provider 已用"配置值权威 + JSON 等价 Diff 抑制"消解了这一冲突,因此放心书写结构清晰的jsonencodeCard 即可,不必手工比对 API 返回的完整 Card。 - 敏感字段永不漂移检测:
litellm_params、static_headers只写不回读,密钥轮换请始终通过 HCL 变更 +terraform apply完成,不要绕过 Terraform 直接在 Proxy 上改。 - import 后必须补配置:导入的 Agent 请立即补齐上述两个敏感字段再 apply,否则会出现持续的 plan 差异。
- 权限与限流一同交付:用
object_permission约束 Agent 可触达的models、mcp_servers、agents等资源,同时用四组 TPM/RPM 参数限定用量,使 Agent 注册本身就带齐安全边界,配合数据源实现"既可写、可查、可审计"的完整闭环。
对于希望进一步深入源码的读者,推荐阅读 Provider 侧 resource_agent.go、data_source_agent.go 与全套行为测试 resource_agent_test.go,以及 Proxy 侧的路由实现 agent_endpoints/endpoints.py,两相对照即可完整还原 Agent 从 Terraform 声明到 Proxy 落库的完整链路。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00