> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Arize Phoenix：LLM观测框架
- URL: https://www.hubwiz.com/blog/arize-phoenix-framework-for-llm-observability/
- Published: 2026-02-11T03:03:20.000Z
- Updated: 2026-02-11T03:03:20.000Z
- Description: 一个"智能"系统做出难以检查、更难调试、几乎无法在规模上信任的决策。
- Author: admin
- Tags: LIBRARY, ARIZE PHOENIX, #Import 2026-10-14 14:41

> ***"如果你深入研究，一切都会很有趣。" — 理查德·费曼***

现代LLM系统之所以令人印象深刻，很大程度上是因为它们的行为隐藏在抽象之后。提示进去，答案出来——在这之间，一个"智能"系统做出难以检查、更难调试、几乎无法在规模上信任的决策。

本文故意走向相反的方向。与其将智能体、工具和模型视为魔法，我们剥开层次，追踪幕后实际发生的事情：LangChain/LangGraph智能体如何推理、何时调用工具、vLLM托管的模型如何响应，以及Arize Phoenix如何捕获每个步骤作为可观测、可测量的数据。通过深入研究——进入追踪、跨度、延迟和令牌流——我们将一个不透明的LLM工作流转变为你能够理解、推理并自信演进的系统。

在本指南中，我们将介绍如何为基于本地LLM的应用程序设置完整的可观测性栈。我们的栈将包括：

- **vLLM提供Qwen编码模型（Qwen3-Coder-Next，FP8）**，直接在**2节点NVIDIA DGX-Spark系统**的一个节点上运行，作为本地OpenAI兼容API暴露。
- **一个LangChain/LangGraph智能体**，在同一DGX-Spark系统上运行，调用本地vLLM端点并集成Tavily进行网络搜索。
- **Arize Phoenix**，在同一2节点DGX-Spark系统上的Kubernetes上自托管，由PostgreSQL支持，用于捕获追踪并提供可观测性UI。

我们将介绍每个组件如何组合在一起、如何通过Kustomize在K8s上部署Phoenix（带Postgres）、如何使用OpenTelemetry为智能体设置追踪工具，以及如何解决常见问题。让我们开始吧！

## 1、架构概述

完全本地运行在2节点DGX-Spark系统上。

![无](https://www.hubwiz.com/blog/content/images/2026/02/1-dLMXEdTGBb_0KpIUwAGLJg.png)

我们的目标是一个生产级的LLM可观测性设置，**完全运行在双节点NVIDIA DGX-Spark系统上**。

**vLLM模型服务器（DGX-Spark节点A）**我们在一个DGX-Spark节点上运行vLLM，托管一个大型、专注于代码的模型（Qwen3-Coder-Next-FP8），位于OpenAI兼容的REST API之后。vLLM通过连续批处理和FP8量化提供高吞吐量推理，暴露如`/v1/chat/completions`的端点。在此设置中，vLLM直接在DGX-Spark上启动并监听端口`8888`。

**LangChain + LangGraph智能体（DGX-Spark节点A）**LLM智能体与模型服务器一起运行在同一DGX-Spark系统上，通常来自Jupyter笔记本或Python运行时。它使用OpenAI兼容接口调用本地vLLM端点，并将Tavily集成为实时网络搜索的工具。从智能体的角度来看，这 behaves 完全像调用OpenAI——只是所有内容都保持本地。

**Arize Phoenix + PostgreSQL（DGX-Spark节点B）**第二个DGX-Spark节点通过Kubernetes运行Arize Phoenix和PostgreSQL。Phoenix从智能体接收OpenTelemetry追踪，将其存储在Postgres中，并暴露一个UI用于检查LLM调用、工具使用、延迟和推理步骤。虽然Phoenix部署在Kubernetes上，但它仍然是同一DGX-Spark系统的一部分。

逻辑分离的组件，单一的物理系统——这保持整个可观测性循环本地、可复现且易于理解。

**为什么这个设置？** 它允许你在具有完整可观测性的情况下**本地迭代LLM应用程序**，而无需将数据发送到外部服务。vLLM + Qwen给你一个类似于OpenAI的"AI服务器"，Tavily添加实时搜索能力，Phoenix带来类似于LangSmith或OpenAI可观测性的工具，但完全自托管。你将能够*精确*检查你的智能体正在做什么一步一步——它生成了什么提示、每个调用花了多长时间、使用了多少令牌等。这对于调试和优化复杂的LLM智能体来说是无价的。

在我们动手之前，让我们先设置所需的基础设施组件。

## 2、基础设施设置：一台DGX-Spark，两个节点

此部署使用**单台NVIDIA DGX-Spark系统，两个节点**：

- **节点A**运行vLLM模型服务器和LangChain/LangGraph智能体运行时。
- **节点B**运行Kubernetes工作负载，用于Arize Phoenix和PostgreSQL。

没有外部GPU服务器或单独的开发机器。智能体、模型服务和可观测性栈都在同一DGX-Spark环境内执行。

Kubernetes仅在增加价值的地方使用（Phoenix + Postgres）。vLLM服务器和智能体可以直接在主机或容器中运行，但仍保持在DGX-Spark本地。网络是集群内部的，Phoenix通过Kubernetes LoadBalancer或`kubectl port-forward`访问。

接下来，我们将在K8s集群上部署Phoenix，包括其Postgres数据库，并确保为我们的环境配置（命名空间、节点调度、存储等）。

## 3、在Kubernetes上部署Arize Phoenix

我们通过Kustomize使用Postgres。

Arize提供了一个我们可以部署在自己集群上的Phoenix开源发行版。我们将使用官方GitHub仓库的Kustomize模板来设置它。默认情况下，Kustomize部署将启动**Phoenix（服务器）和PostgreSQL**，并带有持久卷。我们将自定义一些东西：命名空间、节点亲和性和存储类。

**步骤1：克隆Phoenix仓库并准备命名空间**首先，克隆仓库并为Phoenix创建命名空间：

```
git clone https://github.com/Arize-ai/phoenix.git
cd phoenix
kubectl create namespace phoenix   # 选择一个命名空间（例如"phoenix"）

```

默认情况下，Kustomize清单没有指定命名空间，因此创建并定位一个可以保持整洁。

**步骤2：（可选）自定义部署参数**我们可以使用Kustomize覆盖或补丁来定制部署。例如，让我们将Phoenix和Postgres固定到特定节点，并确保使用Longhorn进行存储。创建一个文件（例如kustomize/local/patches.yaml），包含以下内容，根据需要调整标签/名称：

```
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: phoenix  # 在"phoenix"命名空间中部署所有资源
resources:
  - ../base

# 自定义基础资源的补丁
patches:
  # 将Phoenix服务器部署固定到特定节点（例如，具有标签或主机名"spark-ba63"的节点）
  - target:
      kind: Deployment
      name: phoenix-server
    patch: |-
      spec:
        template:
          spec:
            nodeSelector:
              kubernetes.io/hostname: spark-ba63   # 替换为你的节点名称或使用标签
  # 将Postgres固定（假设它是一个名为"postgres"的StatefulSet）
  - target:
      kind: StatefulSet
      name: postgres
    patch: |-
      spec:
        template:
          spec:
            nodeSelector:
              kubernetes.io/hostname: spark-ba63
  # 对Postgres PVC使用Longhorn存储类（假设PVC名为"postgres-data"）
  - target:
      kind: PersistentVolumeClaim
      name: postgres-data
    patch: |-
      spec:
        storageClassName: longhorn   # 确保这与你的Longhorn SC名称匹配
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi   # 或你需要的任何大小

```

此覆盖层做三件事：在我们的phoenix命名空间中部署所有内容，在节点spark-ba63上调度pod（你可以使用目标节点上存在的任何标签，或不同的nodeSelector表达式），并强制执行Postgres数据PVC使用Longhorn进行存储（在此示例中为10Gi卷请求）。节点固定是可选的；如果你的集群可以在具有存储访问权限的任何节点上调度pod，你可以跳过它或根据需要使用反亲和性。**Longhorn存储类**确保我们的Postgres数据被复制并在节点故障时存活，而不是使用可能不受Longhorn支持的默认值。

**步骤3：部署Phoenix和Postgres**应用Kustomize配置来设置Phoenix。如果你创建了如上所示的覆盖层，应用它；否则，你可以直接应用基础（但请记住单独指定命名空间）：

```
# 使用我们的自定义覆盖层
kubectl apply -k kustomize/local

# （或者，在phoenix命名空间中应用基础）
# kubectl apply -k kustomize/base -n phoenix

```

Kustomize将创建Phoenix部署和Postgres数据库部署（或StatefulSet）以及用于存储的PVC。验证pod是否已启动并正在运行：

```
kubectl get pods -n phoenix
NAME                     READY   STATUS    RESTARTS      AGE
phoenix-dc8c8d9c-ktwdc   1/1     Running   2 (13h ago)   13h
postgres-0               1/1     Running   0             13h

```

还要检查Phoenix的服务是否存在。在基础配置中，它可能默认为ClusterIP服务。要外部访问Phoenix，让我们暴露它：

**步骤4：暴露Phoenix UI**如果你的集群中有LoadBalancer，你可以编辑Phoenix服务以使用type: LoadBalancer。例如：

```
kubectl patch service phoenix-server -n phoenix -p '{"spec": {"type": "LoadBalancer"}}'

```

片刻之后，kubectl get svc -n phoenix phoenix-server应该会显示一个外部IP（如果配置了MetalLB或云LB）。注意，Phoenix默认监听端口6006，它在同一端口上提供Web UI和HTTP追踪收集器。

如果你**没有**LoadBalancer，你可以在需要时使用端口转发：

```
kubectl port-forward svc/phoenix-server -n phoenix 6006:6006

```

这将使Phoenix UI在你的本地机器上可访问于[http://localhost:6006](http://localhost:6006/?ref=hubwiz.com)（并且也允许向此端口发送OTLP追踪）。

此时，Phoenix已部署并且（假设默认情况下没有身份验证）其Web界面已准备好。你可以通过在浏览器中打开URL（LoadBalancer URL或如果端口转发的localhost:6006）来测试。你应该看到Phoenix UI正在加载。最初它将没有数据，这是预期的。

**步骤5：配置Phoenix（Postgres等）**Kustomize部署已设置为使用Postgres DB（通过环境变量或Phoenix默认值）。Phoenix将使用提供的PHOENIX\_SQL\_DATABASE\_URL或单独的Postgres主机/用户/密码环境变量。在我们的设置中，容器可能从ConfigMap或直接规范中获取这些。如果你需要调整（例如，如果你设置了自己的Postgres实例），你可以设置：

```
# 在Phoenix部署环境中：
- name: PHOENIX_POSTGRES_HOST
  value: <postgres-service-host>
- name: PHOENIX_POSTGRES_USER
  value: <user>
# ... 以及密码、数据库名称等

```

但使用捆绑的Postgres，使用默认值（检查Phoenix日志以查看它是连接到SQLite还是Postgres）。Arize文档指出，如果没有提供SQL URL，Phoenix将默认以基于文件的SQLite启动。我们的Kustomize应该覆盖这一点以指向Postgres，为我们提供持久存储用于追踪。

这就是部署Phoenix的全部！我们现在有一个本地运行的可观测性服务。接下来，我们将设置开发环境以使用它。

## 4、设置智能体环境（LangChain + Phoenix工具）

在开发端（**DGX-Spark**），我们需要为智能体和可观测性工具安装必要的Python包。重要的是要安装**Arize的Phoenix客户端/SDK和OpenInference工具**包，而不是任何类似命名的包，以避免混淆。特别要注意的是，**不要**从PyPI安装遗留的phoenix==0.9.1包——那不是Arize的Phoenix（它是一个不相关的包，会导致错误）。相反，使用官方的arize-phoenix库。

打开你的Python环境（这可以是conda env或Jupyter中的venv）并安装：

```
pip install arize-phoenix arize-phoenix-otel openinference-instrumentation-langchain langchain-openai langchain-core

```

让我们分解这些：

- **arize-phoenix** — Phoenix平台客户端（和CLI）库。严格来说，仅发送追踪不是必需的（OTEL包可以独立运行），但如果需要使用Phoenix的Python API或CLI，它可能很有用。它还确保你为任何子包提供匹配的版本。
- **arize-phoenix-otel** — Phoenix OpenTelemetry SDK。这提供了phoenix.otel模块，带有便捷的register()函数来设置追踪。将其视为使用Phoenix特定默认值配置OpenTelemetry的更简单方法。
- **OpenInference工具包** — 我们特别包括openinference-instrumentation-langchain（用于追踪LangChain）。如果auto\_instrument=True，Phoenix OTEL SDK将自动获取任何已安装的OpenInference工具，因此通过安装它，我们启用了LangChain内部的详细追踪。（Phoenix/OpenInference还有其他框架的工具，如OpenAI API、LlamaIndex等，可以根据需要安装，但对于我们的情况，LangChain覆盖了智能体并间接覆盖了OpenAI调用。）
- **LangChain包** — 我们安装langchain-openai和langchain-core，它们对应于LangChain的新模块化设置（在LangChain v1.x中，像OpenAI这样的提供商是单独的子包）。这将使我们能够访问ChatOpenAI和智能体工具。或者，你可以直接安装langchain，但拆分确保我们有需要的部分。如果我们计划直接使用其工具，我们还包括langgraph（可选，但推荐，因为我们的智能体是"基于LangGraph的"）。事实上，类似示例的推荐安装是：pip install arize-phoenix-otel openinference-instrumentation-langchain langgraph langchain-openai langchain-core。

安装后，验证你**没有**安装错误的Phoenix包：

```
import phoenix
print(phoenix.__version__)

# 例如 12.33.1

```

它应该显示一个版本 >= 1.x（或最新的，例如，撰写本文时为0.13.x或更高，因为Phoenix从0.9开始进行了重大更新）。如果你不小心看到0.9.1，请卸载它并重新安装arize-phoenix。

## 5、工具化和创建LangChain智能体

有了依赖项，我们可以编写智能体代码。大纲是：

1. **注册Phoenix追踪器**在其他任何操作之前，以便所有后续导入和调用都被工具化。
2. 设置OpenAI API基础以指向我们的vLLM服务器（以便ChatOpenAI调用转到Qwen）。
3. 使用LangChain的create\_agent或LangGraph的create\_react\_agent定义我们的智能体——包括任何工具（Tavily搜索等）。
4. 运行智能体并观察追踪被发送到Phoenix。

让我们一步一步地在代码中进行：

```
import os
from phoenix.otel import register

# 1. 初始化Phoenix追踪
tracer_provider = register(
    project_name="local-llm-app",
    auto_instrument=True
)

```

register()调用连接到我们的Phoenix实例并配置OpenTelemetry。默认情况下，除了项目名称之外没有其他参数，它将读取环境变量以获取收集器端点。我们需要告诉它*在哪里*发送追踪。如果你在同一台机器上使用端口转发运行Phoenix，则使用默认的[http://localhost:6006](http://localhost:6006/?ref=hubwiz.com)。为了清楚起见，让我们明确设置它：

```
# 在你的shell或笔记本环境中设置：
export PHOENIX_COLLECTOR_ENDPOINT="http://localhost:6006"

```

此环境变量被register()拾取以将追踪发送到Phoenix的收集器。如果你为Phoenix有LoadBalancer IP或域名，请使用它（例如，http://<phoenix\_host>:6006）。**注意：** 对于我们的自托管实例（禁用身份验证），不需要API密钥；对于Phoenix Cloud，你还需要设置PHOENIX\_API\_KEY。

使用auto\_instrument=True，Phoenix的SDK将自动激活所有已安装的相关OpenInference工具。这意味着一旦我们导入LangChain或OpenAI库，它们就会被修补以发出遥测数据。（在底层，它等同于手动执行类似LangChainInstrumentor().instrument()或OpenAIInstrumentor().instrument()的操作，但我们是免费获得它的。）

现在，让我们配置OpenAI聊天模型以使用我们的**vLLM Qwen**端点：

```
from langchain_openai import ChatOpenAI

# 2. 配置OpenAI API以使用本地vLLM服务器
os.environ["OPENAI_API_KEY"] = "not-needed"  # vLLM不使用它，但LangChain/OpenAI SDK可能需要一个变量
llm = ChatOpenAI(
    model_name="Qwen/Qwen3-Coder-Next-FP8",  # vLLM期望的模型名称（如加载的）
    temperature=0,
    openai_api_base="http://localhost:8888/v1",  # vLLM端点；如果不是本地则调整主机
    openai_api_key="unused"
)

```

在这里，我们创建一个ChatOpenAI实例指向localhost:8888/v1（vLLM正在提供我们的模型的地方）。我们传递model\_name为Qwen/Qwen3-Coder-Next-FP8，因为我们就是这样启动vLLM的（它将在请求中接受此作为模型标识符）。如果你用不同的名称或别名启动vLLM，请使用它。openai\_api\_base包括/v1路径以模仿完整的OpenAI API URL。我们还设置了一个虚拟API密钥以满足客户端；vLLM默认不需要身份验证，因此任何字符串（或可能是空的）都可以。现在llm的行为就像一个指向我们本地模型的OpenAI ChatCompletion代理。

接下来，我们为智能体定义**工具**。我们将集成**Tavily搜索**，以便智能体可以获取实时信息。Tavily有一个API；假设我们在TAVILY\_API\_KEY中设置了API密钥。LangChain没有像JS那样为Tavily内置的Python包装器，因此我们将使用简单的requests调用或Tavily的SDK（如果可用）实现一个自定义工具。为了说明：

```
import requests
from langchain_core.tools import Tool

TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY", "<your Tavily key>")

def tavily_search(query: str) -> str:
    """使用Tavily API获取查询的搜索结果。"""
    url = "https://api.tavily.com/search"
    params = {"query": query, "max_results": 5, "topic": "general"}
    headers = {"x-api-key": TAVILY_API_KEY}
    resp = requests.get(url, params=params, headers=headers)
    if resp.status_code == 200:
        data = resp.json()
        # 将响应简化为LLM可以使用的字符串。
        results = [item.get("snippet", "") for item in data.get("results", [])]
        return " ; ".join(results)  # 返回片段连接
    else:
        return f"搜索错误（状态 {resp.status_code}）"

# 将此函数包装为LangChain工具
tavily_tool = Tool.from_function(
    func=tavily_search, 
    name="TavilySearch", 
    description="使用Tavily搜索网络获取最新信息"
)

```

*（上面是一个基本示例。在实践中，你可能会使用LangChain或LangGraph的@tool装饰器来更优雅地创建工具。另外，考虑速率限制和错误——如果查询太宽泛，Tavily可能需要处理。）*

我们还可以包括其他工具或基础工具。例如，如果我们指定，LangGraph的create\_react\_agent会自动添加一些基础工具（如计算器）。为了聚焦，我们将在此智能体中仅使用我们的Tavily工具。

现在，让我们**创建智能体**。我们有两个选择：— 使用LangChain的新create\_agent函数（它在内部使用LangGraph构建基于图的智能体）。— 直接使用LangGraph的create\_react\_agent（非常相似的结果）。

我们将使用LangChain的create\_agent以获得熟悉度。如果需要，我们还为智能体的行为准备系统提示。

```
from langchain.agents import create_agent

SYSTEM_PROMPT = "你是一个有用的研究助手，可以访问网络搜索工具。在需要时使用该工具查找信息，然后提供详细答案。"

# 3. 创建智能体
agent = create_agent(
    model=llm,
    tools=[tavily_tool],
    system_prompt=SYSTEM_PROMPT
)

```

就这样——我们现在有一个智能体，它将使用我们的Qwen模型（llm），并可以在适当时调用Tavily搜索工具。该智能体本质上实现了ReAct循环：它将决定是否/何时使用TavilySearch工具，然后使用模型来形成最终答案。

使用LangChain v1.x，智能体暴露一个invoke()方法来在提示上运行它。让我们用一个简单的查询来测试它。

```
# 4. 使用示例问题运行智能体
user_question = "最新的Python版本是什么，它的主要新功能有哪些？"
result = agent.invoke({"messages": [
    {"role": "user", "content": user_question}
]})
print(result["messages"][-1]["content"])

```

我们以预期的格式传递用户查询（一个带有"messages"列表的字典，包括用户角色内容）。智能体现在将经历推理：模型可能会决定使用搜索工具（Tavily）来查找最新Python版本的信息，通过我们的tavily\_search函数执行搜索，获取结果，然后继续对话以产生答案。

关键的是，*因为我们在开始时注册了追踪器和工具*，所有这些步骤都被追踪。LangChain的OpenInference工具将自动为智能体的操作创建跨度。对于每次对Qwen的LLM调用（通过ChatOpenAI），将有一个跨度，对于每次工具调用（TavilySearch），也将有一个跨度——包括工具的输入和输出。Phoenix正在实时捕获这些。

## 6、在Phoenix中观察追踪

通过智能体运行一个或多个查询后，前往Phoenix UI（端口6006上的Web界面）。你应该在主页上看到**追踪列表**——每个追踪对应一次智能体调用（每次agent.invoke调用）。例如，如果你运行了上面的user\_question，将出现一个追踪条目。

每个追踪条目将有元数据，如追踪ID、项目名称（例如"local-llm-app"）和时间戳。点击追踪以打开**追踪详细视图**。在这里，你将看到智能体的*完整执行流程*，可视化为跨度树（通常以时间线/甘特图样式或分层列表显示）：

将有一个顶级跨度用于整体智能体运行（可能标有智能体或链名称）。

在它下面，你将看到每个步骤的跨度。例如，LLM思考步骤的跨度，然后是TavilySearch工具调用的跨度（带有它搜索的查询），然后是智能体处理工具结果的另一个LLM调用跨度，最后是LLM答案生成跨度。

每个跨度可以点击以显示详细信息，例如：

- **输入**：例如，发送到模型的提示或工具输入。
- **输出**：例如，模型生成的文本或工具的返回值。
- **延迟**：该调用花费了多长时间（跨度持续时间）。
- **属性**：元数据，如模型名称、令牌数等（Phoenix追踪令牌计数，甚至成本（如果适用），对于支持的提供商）。

因为我们安装了LangChain工具，Phoenix使用OpenInference语义约定来有意义地标记这些跨度（它知道什么是工具与LLM）。例如，智能体的每个步骤、工具执行和最终响应都会自动追踪到Phoenix。你应该在追踪中看到我们的工具名称（例如"TavilySearch"）。如果你展开跨度，你可能会看到像input.value这样的属性，包含工具调用的JSON（在Phoenix UI中可能格式很好）和output.value作为工具结果。对于LLM跨度，你将看到提示和模型的原始响应。如果可用，Phoenix还可能突出显示令牌使用情况（对于OpenAI API调用它会；对于我们的vLLM，由于它模仿OpenAI，如果vLLM在响应中返回它们，我们可能会获得令牌计数）。

此外，在Phoenix UI中，你有过滤或搜索追踪、比较追踪等功能。例如，Phoenix可以在表格中显示所有追踪，列有延迟、总令牌数等，你可以按这些排序以找到异常值。这是一个强大的**AI可观测性**工具包，为你提供类似于APM（应用程序性能监控）工具为微服务所做的洞察，但专门针对LLM应用程序。我们现在基本上有了自己的OpenAI风格的*开发者控制台*，但是自托管的——我们可以实时观看我们的提示和工具的表现如何。

以下是单个查询追踪你应该在Phoenix中看到的内容的快速回顾：

- **追踪时间线：** 按时间顺序排列的动作序列（例如"LLM：思考"、"工具：TavilySearch"、"LLM：回答"）。
- **跨度详细信息：** 点击动作显示内容。对于Tavily搜索跨度，你可能会看到智能体构建的搜索查询（可能包括原始问题或修改版本）和返回的顶级结果。对于LLM跨度，你将看到提示（包括对话上下文、系统提示等）和模型的输出。
- **性能指标：** 每个跨度的持续时间是可见的，在顶层你可能会看到总追踪时间。如果使用OpenAI或类似，你还会看到令牌计数和成本；使用vLLM，除非我们添加自定义工具，否则这些可能不会出现，但延迟是有的。
- **项目和会话信息：** 由于我们在注册时设置了project\_name="local-llm-app"，Phoenix在该项目下对追踪进行分组。你可以有多个项目（例如dev vs prod）。我们没有手动设置会话ID，但如果你提供span config中的session\_id，Phoenix可以对多轮会话进行分组。（在我们的示例代码中，我们没有，但你可以传递config={"configurable": {"thread\_id": "some-session-id"}}给agent.invoke，如某些LangGraph示例中所示。）

这种可见性让我们**调试和优化**智能体：— 我们可以看到智能体是否不必要地或太多次调用工具。— 我们可以测量模型花费的时间与工具调用的时间。— 我们可能会发现模型提示不是我们预期的（可能系统提示没有正确应用或工具的说明需要调整）。— 如果某些东西失败（工具中的异常等），追踪将在该跨度上显示错误状态。

## 7、操作技巧和辅助脚本

管理设置涉及一些重复性任务，因此这里有一些辅助脚本/命令和技巧：

- **部署/更新脚本**

如果你需要重新部署Phoenix（比如你更新了版本或更改了配置），你可以创建一个简单的脚本deploy\_phoenix.sh：

```
#!/bin/bash
kubectl apply -k path/to/phoenix/kustomize/local -n phoenix

```

这将重新应用Kustomize配置。因为我们对Postgres使用持久存储，重新部署Phoenix（或甚至删除其pod）将保持PVC上的数据完整。

- **删除脚本**

要拆除部署，例如delete\_phoenix.sh：

```
kubectl delete -k path/to/phoenix/kustomize/local -n phoenix
# 如果你想完全删除命名空间：
# kubectl delete namespace phoenix

```

*注意：* 这也会删除PVC（取决于你的Kustomize设置和回收策略），因此如果你想保留追踪，请谨慎使用。

- **端口转发**

对于无需LB的快速访问，请使用：

```
kubectl port-forward svc/phoenix-server -n phoenix 6006:6006 >/dev/null 2>&1 &
echo "Phoenix UI available at http://localhost:6006"

```

&将其置于后台。你可以将此脚本化为forward\_phoenix.sh。完成后记得终止端口转发。

- **环境配置**

在你的开发环境中，而不是每次都设置PHOENIX\_COLLECTOR\_ENDPOINT，你可以将其放在.env或Jupyter内核启动中。或者，如果你不想依赖环境变量，你可以在代码中通过register(endpoint="http://:6006")提供它。Phoenix文档显示两种选项；环境变量往往更简单。

- **项目命名**

你可以在register()中使用不同的project\_name来逻辑上分离追踪（例如"my-llm-app-dev" vs "my-llm-app-prod"）。Phoenix的UI允许你按项目过滤。对于本地开发，它可能都是"default"或一个名称——由你决定。

## 8、故障排除和常见问题

即使有最好的指南，事情也可能出错。以下是此设置的一些故障排除技巧：

- **Phoenix UI中没有显示追踪**

如果你在运行智能体后看到Phoenix UI但没有追踪，首先确保智能体代码在调用register()后实际执行。如果是，可能的问题是连接性。检查PHOENIX\_COLLECTOR\_ENDPOINT是否正确设置为Phoenix服务器的可达URL。如果使用端口转发，应该是[http://localhost:6006](http://localhost:6006/?ref=hubwiz.com)（不需要/v1/traces——SDK会附加路径）。如果使用LoadBalancer，可能是http://:6006。你可以通过从开发机器运行如下内容来测试可达性：

```
curl -X POST http://<phoenix_host>:6006/v1/traces -H "Content-Type: application/x-protobuf" --data-binary @/dev/null

```

没有适当数据它不会完全工作，但应该返回400左右，确认你可以命中端点。另外，检查Phoenix服务器日志（kubectl logs phoenix-server）以查看任何传入的追踪日志或错误。如果端点不正确或被防火墙阻止，追踪永远不会到达。明确设置环境变量（如上所示）通常可以修复不匹配。

- **安装了错误的phoenix包**

如前所述，如果你不小心执行了pip install phoenix（这给出2013年的0.9.1版本），你将遇到错误（特别是在Python 3.11+上）。解决方法是卸载它并改为安装arize-phoenix。始终使用arize-前缀的包名。

- **版本不匹配**

确保arize-phoenix-otel和openinference-instrumentation-langchain版本兼容。这些更新很快；如果你在不同时间安装它们，较新的Phoenix可能期望较新的工具。最好一起更新它们。例如，如果Phoenix是0.13.x版本，使用类似版本的arize-phoenix-otel。openinference-instrumentation-langchain应该与OpenInference规范保持最新。如果你在追踪期间看到错误（如缺少属性或关于跨度处理的警告），请考虑升级这些包（pip install -U …）。

- **LangChain版本问题**

我们的智能体代码使用LangChain v1风格API（create\_agent，agent.invoke）。确保你有langchain>=1.0.0。如果你有旧的0.xx版本，create\_agent函数将不存在。单独的langchain-openai包通常会拉入LangChain 1.x核心。如果你遇到麻烦，请显式安装langchain==1.2.*（或最新）以获得正确的版本。还要注意，LangChain v1更改了导入路径（例如，像我们那样使用langchain\_openai）。如果你看到导入错误，请仔细检查你是否安装了正确的子包。* 

- **Tavily API问题**

如果智能体没有从搜索中获得好的结果，或者出错，可能是Tavily集成。确保你的Tavily API密钥有效，并且你没有耗尽任何配额。你可以在测试期间打印/记录tavily\_search函数的行为。由于这是第三方工具调用，它不会...· 如果**LoadBalancer IP未分配：** 如果kubectl get svc phoenix-server显示外部IP为，你的集群可能没有LoadBalancer供应程序。在裸机上，你需要安装MetalLB并用IP池配置它。如果没有完成，请坚持使用端口转发或NodePort服务作为变通方法。

- **OpenTelemetry数据量**

如果你生成大量追踪（比如在循环中运行许多查询），请注意内存/存储。Phoenix将把追踪数据存储在Postgres中——对于文本数据来说可能会增长。对于开发来说这很好（Phoenix UI有过滤功能并且可以导出数据），但要注意如果你以某种方式摄取了数千条追踪；你可能需要分配更多Postgres存储或修剪旧追踪。Phoenix有数据保留设置（默认无限期保留），你可以通过环境变量配置。

- **性能考虑**

在vLLM上运行Qwen FP8模型是资源密集型的。如果智能体很慢，请考虑Qwen可能很重——原始生成约43令牌/秒，但如果你的提示很大或需要多个步骤，响应将需要几秒钟。对于单个GPU上的7B+模型来说这是正常的。可观测性增加了最小的开销（OpenTelemetry追踪非常轻量级），但如果你怀疑有开销，你可以在register()中切换batch=False以立即发送跨度（非批处理）或根据需要调整协议为gRPC。默认情况下它使用HTTP/protobuf，这对于开发来说很好。

最后，请记住**可观测性是一种迭代辅助**。通过Phoenix追踪，你可能会识别出使智能体不必要循环的提示或耗时太长的工具。然后你可以优化系统提示或代码，重新部署（Phoenix端无需更改），然后再次测试。Phoenix将捕获新的追踪，你可以比较前后。这种紧密的循环加速了LLM应用程序的调试，否则可能会感觉像黑盒。

## 9、演示：运行智能体 + 在Phoenix中查看追踪

本节是一个实用的"健全性演示"，用于确认完整循环正在工作：

> **智能体（LangChain/LangGraph）→ vLLM（Qwen）→ Tavily工具调用 → OpenTelemetry跨度 → Phoenix UI**

### 9.1 使用可用的Jupyter笔记本 + requirements

我保持一个可用的、可复现的演示笔记本（加上`requirements.txt`）在[这里](https://github.com/dorangao/article-scripts/tree/main/arize?ref=hubwiz.com)。

在你的DGX-Spark节点（或你的智能体运行时所在的地方）克隆它：

```
git clone https://github.com/dorangao/article-scripts.git
cd article-scripts/arize

```

创建一个环境并安装依赖项（选择一条路径）：

```
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

```

### 9.2 设置环境变量（vLLM + Phoenix + Tavily）

```
export OPENAI_API_KEY="unused"                 # vLLM不需要它，但SDK通常期望一个值
export OPENAI_BASE_URL="http://localhost:8888/v1"

export PHOENIX_COLLECTOR_ENDPOINT="http://localhost:6006"   # 或 http://<phoenix-lb-ip>:6006
export TAVILY_API_KEY="<your_tavily_key>"

```

> *如果Phoenix在Kubernetes上而你未使用LoadBalancer，请在另一个终端中运行端口转发：*

```
kubectl port-forward svc/phoenix-server -n phoenix 6006:6006

```

### 9.3 运行笔记本演示

打开笔记本并从上到下运行单元格。你应该看到：

- 至少一个**LLM跨度**（vLLM调用）
- 至少一个**工具跨度**（Tavily搜索）
- 每次智能体调用的单个追踪

### 9.4 在Phoenix UI中验证

打开Phoenix：

- `http://localhost:6006`（端口转发），或
- `http://<LB-IP>:6006`（LoadBalancer）

在追踪视图中，点击追踪并确认你可以看到：

- 智能体步骤
- LLM请求/响应
- Tavily工具输入/输出
- 每个跨度的时间（延迟）

## 10、结束语

我们已经设置了一个强大的本地LLM可观测性栈：一个vLLM服务器托管一个强大的开放模型，一个由LangChain/LangGraph增强的智能体具有实时搜索功能，以及Arize Phoenix来追踪每一步。这种组合允许MLOps工程师和LLM开发人员在本地或本地环境中自由实验，同时对智能体的行为和性能保持**清晰的可见性**。我们涵盖了每个组件的架构和目的（vLLM用于本地模型服务，Tavily用于知识增强，Phoenix用于追踪和评估），部署了必要的基础设施（使用Kustomize YAML在K8s上部署Phoenix + Postgres），使用现代的基于OpenTelemetry的工具对代码进行工具化，并演示了如何运行查询和检查追踪。

通过遵循本指南，你可以**构建自己的"OpenAI风格"开发栈**，它是完全自包含的。你将能够回答不仅仅是 *"模型回复了什么？"而是"它为什么那样回复，以及它做了什么才得到那个答案？"*——所有这些都在本地设置的舒适环境中。愉快的追踪！

---

原文链接: [Building a Local LLM Observability Stack with vLLM, Tavily, and Arize Phoenix](https://medium.com/@dorangao/building-a-local-llm-observability-stack-with-vllm-tavily-and-arize-phoenix-5185a0298deb?ref=hubwiz.com)

汇智网翻译整理，转载请标明出处