首页
/ LiteLLM 接入 Arize Phoenix 提示词管理:prompt_id 驱动的模板渲染与参数回注实战

LiteLLM 接入 Arize Phoenix 提示词管理:prompt_id 驱动的模板渲染与参数回注实战

2026-09-06 10:48:28作者:申梦珏Efrain

本文以 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.pyArizePhoenixClient 完成。

注意区分:同一目录下的 arize_phoenix.py 是另一套能力——把 LiteLLM 的 trace 通过 OpenTelemetry 上报到 Phoenix 的 arize_phoenix success_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.pyprompt_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_registryarize_phoenix 键注册(见 init_prompts.pyARIZE_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_hookfinal_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 会包含 modeltemperaturemax_tokens,并展开 provider 参数中尚未出现的键(如 top_pfrequency_penaltypresence_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,且刻意使用了 ImmutableSandboxedEnvironmentL107-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_keyapi_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 和 paramsL367-L372),即旧版 hook 路径下提示词故障不会阻断 LLM 调用;而新的 _compile_prompt_helper 路径(should_run_prompt_managementprompt_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_modelignore_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 原文):

  1. 登录 Arize Phoenix;
  2. 进入你的 workspace;
  3. 打开 Prompts 分区;
  4. 选择一个 prompt version;
  5. 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. 验证与测试入口

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 两个开关即可。

登录后查看全文
热门项目推荐
相关项目推荐