LiteLLM 接入 Arize Phoenix 提示词管理:prompt_id 驱动的模板渲染与参数回注实战
本文以 LiteLLM 的 Arize Phoenix Prompt Management 集成为主线,讲清楚如何在 litellm.completion 中用 prompt_id + prompt_variables 直接消费 Phoenix 工作区里的提示词版本,并深入到 litellm/integrations/arize/ 的源码,解析模板拉取、Jinja2 沙箱渲染、消息合并与元数据参数回注的完整链路,帮助你在生产网关中安全地托管和热更新提示词。
1. 集成概览:Phoenix 提示词版本如何进入 LiteLLM
该集成让 LiteLLM 的 completion 能力直接读取 Arize Phoenix 提示词版本的 REST 数据,核心能力包括(见 模块 README):
- 从 Arize Phoenix API 拉取 prompt version;
- 通过 Phoenix 的 workspace 权限体系做访问控制(LiteLLM 侧不额外鉴权,401/403 由 Phoenix 返回);
- Mustache/Handlebars 风格变量模板(
{{variable}}); - 多消息 chat 模板(system/user 多轮结构);
- 从 prompt 元数据自动回填模型与调用参数(OpenAI / Anthropic 两套 provider 参数);
- 与调用方传入的
messages合并。
从源码结构看,整个能力由 arize_phoenix_prompt_manager.py 中的两个类承担:ArizePhoenixTemplateManager(拉取 + 渲染)和 ArizePhoenixPromptManager(实现 LiteLLM 提示词管理基类接口)。底层 HTTP 交互由 arize_phoenix_client.py 的 ArizePhoenixClient 完成。
注意区分:同一目录下的 arize_phoenix.py 是另一套能力——把 LiteLLM 的 trace 通过 OpenTelemetry 上报到 Phoenix 的
arize_phoenixsuccess_callback(可参考 tests/local_testing/test_arize_phoenix.py)。本文聚焦提示词管理集成,即 README 的主题。
2. 配置:api_key 与 api_base 的取值规则
在应用侧配置 Phoenix 访问凭据(README 原文):
import litellm
# Configure Arize Phoenix access
# api_base should include your workspace, e.g., "https://app.phoenix.arize.com/s/your-workspace/v1"
api_key = "your-arize-phoenix-token"
api_base = "https://app.phoenix.arize.com/s/krrishdholakia/v1"
三个关键取值约定:
- api_base 必须带 workspace 路径且以
/v1结尾,例如https://app.phoenix.arize.com/s/{workspace}/v1; - api_key 是 Phoenix 的 Bearer token。从 init.py 的
prompt_initializer可以看到,它优先取litellm_params.api_key,取不到时回退到环境变量PHOENIX_API_KEY:
api_key: Final = getattr(litellm_params, "api_key", None) or os.environ.get("PHOENIX_API_KEY")
api_base: Final = getattr(litellm_params, "api_base", None)
prompt_id: Final = getattr(litellm_params, "prompt_id", None)
if not api_key or not api_base:
raise ValueError("api_key and api_base are required for Arize Phoenix prompt integration")
也就是说 api_base 没有任何环境变量兜底,必须在请求参数或 proxy 配置里显式给出;api_key/api_base/prompt_id 会从 litellm_params 中剥离后再把其余字段透传给 ArizePhoenixPromptManager,所以像 ignore_prompt_manager_model 之类的开关可以随参数一路传下去。
该 initializer 通过 prompt_initializer_registry 以 arize_phoenix 键注册(见 init_prompts.py 中 ARIZE_PHOENIX = "arize_phoenix"),这也是 model 前缀 arize/ 被路由到本集成的依据(ArizePhoenixPromptManager.integration_name 返回 "arize")。
3. 用法:三种调用姿势
3.1 通过 completion 使用 prompt_id
import litellm
# Use with completion
response = litellm.completion(
model="arize/gpt-4o",
prompt_id="UHJvbXB0VmVyc2lvbjox", # Your prompt version ID
prompt_variables={"question": "What is artificial intelligence?"},
api_key="your-arize-phoenix-token",
api_base="https://app.phoenix.arize.com/s/krrishdholakia/v1",
)
print(response.choices[0].message.content)
3.2 与额外 messages 合并
提示词模板渲染出的消息会前置到调用方传入的 messages 之前(对应源码 pre_call_hook 中 final_messages = rendered_messages + messages,见 arize_phoenix_prompt_manager.py#L340-L344):
response = litellm.completion(
model="arize/gpt-4o",
prompt_id="UHJvbXB0VmVyc2lvbjox",
prompt_variables={"question": "Explain quantum computing"},
api_key="your-arize-phoenix-token",
api_base="https://app.phoenix.arize.com/s/krrishdholakia/v1",
messages=[
{"role": "user", "content": "Please keep your response under 100 words."}
],
)
典型用法即:模板提供 system 人设 + 第一个 user 问题,messages 追加约束或后续轮次。
3.3 直接使用 Manager
不经过 completion,也可以拿渲染结果和元数据:
from litellm.integrations.arize.arize_phoenix_prompt_manager import ArizePhoenixPromptManager
# Initialize the manager
manager = ArizePhoenixPromptManager(
api_key="your-arize-phoenix-token",
api_base="https://app.phoenix.arize.com/s/krrishdholakia/v1",
prompt_id="UHJvbXB0VmVyc2lvbjox",
)
# Get rendered messages
messages, metadata = manager.get_prompt_template(
prompt_id="UHJvbXB0VmVyc2lvbjox",
prompt_variables={"question": "What is machine learning?"}
)
print("Rendered messages:", messages)
print("Metadata:", metadata)
get_prompt_template 返回的 metadata 会包含 model、temperature、max_tokens,并展开 provider 参数中尚未出现的键(如 top_p、frequency_penalty、presence_penalty),逻辑见 arize_phoenix_prompt_manager.py#L296-L317。
4. Phoenix 提示词格式与变量替换
Phoenix 提示词版本的 JSON 结构(README 原文示例):
{
"data": {
"description": "A chatbot prompt",
"model_provider": "OPENAI",
"model_name": "gpt-4o",
"template": {
"type": "chat",
"messages": [
{
"role": "system",
"content": [
{"type": "text", "text": "You are a chatbot"}
]
},
{
"role": "user",
"content": [
{"type": "text", "text": "{{question}}"}
]
}
]
},
"template_type": "CHAT",
"template_format": "MUSTACHE",
"invocation_parameters": {
"type": "openai",
"openai": {"temperature": 1.0}
},
"id": "UHJvbXB0VmVyc2lvbjox"
}
}
对照 _parse_prompt_data 的解析逻辑:
content是分片数组,渲染时只处理type == "text"的 part,多个 text part 用空格 join 成最终字符串(L186-L208);invocation_parameters优先取openai键,其次anthropic,都没有则取第一个 dict 型嵌套值——这解释了 README 宣称的 “OpenAI and Anthropic provider parameter support”;- 解析出的
temperature/max_tokens会被提升进 metadata,供pre_call_hook/_compile_prompt_helper回注到请求参数。
变量替换使用 Mustache 语法:
Template: "Hello {{name}}, your order {{order_id}} is ready!"
Variables: {"name": "Alice", "order_id": "12345"}
Result: "Hello Alice, your order 12345 is ready!"
4.1 源码级要点:Jinja2 沙箱渲染
虽然对外承诺的是 Mustache 风格,实际渲染引擎是 Jinja2,且刻意使用了 ImmutableSandboxedEnvironment(L107-L117):
self.jinja_env = ImmutableSandboxedEnvironment(
loader=DictLoader({}),
autoescape=select_autoescape(["html", "xml"]),
# Use Mustache/Handlebars-style delimiters
variable_start_string="{{",
variable_end_string="}}",
...
)
源码注释解释得很直接:模板来自外部 workspace 用户,普通 Environment() 下恶意模板可能通过 __class__.__init__.__globals__ 之类的属性遍历在代理主机上执行任意代码,沙箱环境正好阻断这类遍历,同时保留正常的 {{ var }} 替换。这一点有专门的回归测试锁定:tests/test_litellm/integrations/test_prompt_manager_ssti.py 断言 Arize 管理器的 jinja_env 必须是 ImmutableSandboxedEnvironment,多个经典 SSTI 载荷渲染时抛出 SecurityError,而 Hello {{ name }} 这类正常替换保持可用。对自托管代理而言,这意味着 Phoenix 工作区成员可编辑提示词,但无法借此在 LiteLLM 进程内执行代码。
5. 客户端实现细节:请求路径、安全清洗与错误语义
ArizePhoenixClient 的关键行为:
- 构造时
api_key与api_base缺一不可,均会抛ValueError;请求头固定为Authorization: Bearer {api_key}+Accept: application/json; get_prompt_version(prompt_version_id)拼接为{api_base}/v1/prompt_versions/{safe_id}(注意 client 会在 api_base 之后再补一段/v1,与 api_base 本身以/v1结尾的约定组合出完整 URL);- 返回体取
data字段,404 时返回None(上层会包装成 “Prompt version not found” 类异常)。
5.1 路径穿越防护
_sanitize_id 对 prompt id 做了严格清洗:
def _sanitize_id(identifier: str) -> str:
"""Reject path traversal characters and URL-encode the identifier."""
if any(c in identifier for c in ("/", "\\", "#", "?")):
raise ValueError(f"Invalid identifier {identifier!r}: contains disallowed characters")
if ".." in identifier:
raise ValueError(f"Invalid identifier {identifier!r}: path traversal detected")
return urllib.parse.quote(identifier, safe="")
即 id 中不允许出现 /、\、#、? 与 ..,最终还会被 URL 编码。这意味着 README 示例中的 UHJvbXB0VmVyc2lvbjox(Base64 风格的 Phoenix prompt version id)是合法输入,而任何试图把 id 当作路径前缀/查询串的输入都会直接报错。
5.2 错误码与处理
README 的错误处理约定与 get_prompt_version 的 except 分支 一一对应:
| 状态码 | 含义 | 客户端行为 |
|---|---|---|
| 404 | Prompt version 不存在 | 返回 None,上层抛出 “not found” 类异常 |
| 401 | 认证失败 | 抛异常:检查 access token |
| 403 | 无权限 | 抛异常:检查 workspace 权限 |
| 其他 | 未知错误 | 原样包装抛出 |
使用侧的捕获示例(README 原文):
try:
response = litellm.completion(
model="arize/gpt-4o",
prompt_id="invalid-id",
arize_config=arize_config,
)
except Exception as e:
print(f"Error: {e}")
另外注意 ArizePhoenixPromptManager.pre_call_hook 的设计:渲染/拉取失败时只记 verbose_proxy_logger.error 并原样返回传入的 messages 和 params(L367-L372),即旧版 hook 路径下提示词故障不会阻断 LLM 调用;而新的 _compile_prompt_helper 路径(should_run_prompt_management 在 prompt_id 存在时返回 True)则会把编译失败包装为 ValueError 抛出。两种路径的取舍取决于你走哪套提示词管理入口,排查问题时值得先确认。
6. API 参考
ArizePhoenixPromptManager
提示词管理主类,实现 PromptManagementBase 接口。README 列出的方法与源码对应:
get_prompt_template(prompt_id, prompt_variables)— 渲染模板,返回(messages, metadata);get_available_prompts()— 列出当前已加载的 prompt ID(转发给list_templates);reload_prompts()— 置空内部 manager 触发重新拉取;pre_call_hook(...)— 旧式调用前置钩子:渲染并前置消息、按 metadata 回注model/temperature/max_tokens/top_p/frequency_penalty/presence_penalty;get_chat_completion_prompt(...)— 委托基类完成model, messages, non_default_params的最终组装,支持ignore_prompt_manager_model、ignore_prompt_manager_optional_params两个开关,用于禁止模板元数据覆盖调用方显式参数。
ArizePhoenixClient
get_prompt_version(prompt_version_id)— 拉取单个 prompt version(含 401/403/404 分支);test_connection()— 访问{api_base}/prompt_versions探活,成功返回True,异常返回False;close()— 关闭底层 HTTPHandler 释放连接。
7. 获取 Prompt Version ID 与 api_base
操作步骤(README 原文):
- 登录 Arize Phoenix;
- 进入你的 workspace;
- 打开 Prompts 分区;
- 选择一个 prompt version;
- ID 在 URL 中:
/s/{workspace}/v1/prompt_versions/{PROMPT_VERSION_ID}。
对应地 api_base 应为 https://app.phoenix.arize.com/s/{workspace}/v1。例如:
- Workspace:
krrishdholakia - API Base:
https://app.phoenix.arize.com/s/krrishdholakia/v1 - Prompt Version ID:
UHJvbXB0VmVyc2lvbjox
也可以直接用 curl 验证凭据与 ID(README 原文命令):
curl -L -X GET 'https://app.phoenix.arize.com/s/krrishdholakia/v1/prompt_versions/UHJvbXB0VmVyc2lvbjox' \
-H 'Authorization: Bearer YOUR_TOKEN'
8. 验证与测试入口
- tests/local_testing/test_arize_phoenix.py —
arize_phoenixtrace 回调的本地联调测试(需.env凭据,属于 live 测试); - tests/test_litellm/integrations/test_prompt_manager_ssti.py — 无网络单测,锁定 Arize 模板渲染器的沙箱行为,可本地直接运行;
- tests/test_litellm/integrations/arize/test_arize_phoenix.py —
ArizePhoenixConfig的项目名解析、OTLP HTTP/gRPC endpoint 选择等单测。
9. 小结
把 Phoenix 作为 LiteLLM 的提示词后端,核心就四件事:配好 api_base(含 workspace、以 /v1 结尾)与 Bearer token;用 model="arize/{model}" + prompt_id + prompt_variables 发起调用;理解模板消息会前置合并、invocation_parameters 会按 OpenAI/Anthropic 优先级回注到请求参数;以及在多租户场景下意识到模板渲染已经过 Jinja2 沙箱与 id 路径穿越清洗。需要覆盖调用方显式参数时,用 ignore_prompt_manager_model / ignore_prompt_manager_optional_params 两个开关即可。
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 StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00