> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coze.cn/llms.txt
> Use this file to discover all available pages before exploring further.

本文介绍如何将 [LiteLLM SDK](https://docs.litellm.ai/docs/#litellm-python-sdk) 运行过程的 Trace 数据自动上报到扣子罗盘。
本文适用于 LiteLLM SDK，如果你需要上报 LiteLLM Proxy 的 Trace 数据，请参考 [LiteLLM Proxy](/cozeloop/litellm_proxy_native_trace_report)。
## 功能介绍 {#94b7aaea}
[LiteLLM](https://docs.litellm.ai/docs/) 是一款开源的 Proxy 和 SDK。它提供了统一的 API 接口，能够兼容 OpenAI 的 Endpoint，实现对数百家大语言模型提供商及旗下模型的统一调用与管理。
LiteLLM SDK 能够与 OpenTelemetry 集成，自动捕获 LiteLLM SDK 运行过程中的关键操作和性能指标，并将这些数据作为 Trace 数据上报到扣子罗盘，完成可视化分析。本集成方案适用于监控 LiteLLM SDK 调用 AI 模型过程、排查调用问题的场景。
本文以 Python SDK 为例，介绍如何通过 OpenTelemetry SDK 将 Trace 数据上报至扣子罗盘平台，从而实现对应用程序性能和行为的高效监控与分析。
## 准备工作  {#fb286a19}
使用 OpenTelemetry Python SDK 前，需要先安装 Python SDK 以及相关的依赖库。 

1. 安装 Python SDK。 
   Opentelemetry Python SDK 要求 Python 3.9 或以上版本。你可以通过如下方法查看 Python 版本。 
   ```Python
   # 查看 Python 版本   
   python --version 
    
   # 回显信息显示已安装 3.9.2 版本 
   Python 3.9.2
   ```

2. 安装依赖库。 
   安装 Python 依赖库，分别用于调用 AI 模型、对调用过程进行可观测性管理以及将 Trace 数据导出到后端系统。 
   ```Python
   pip install opentelemetry-sdk
   pip install opentelemetry-exporter-otlp
   pip install openinference-instrumentation-litellm
   pip install litellm
   ```


## 配置环境变量  {#f4c3cbcb}
在上报 Trace 数据前，你需要正确配置 OpenTelemetry 的环境变量，以确保 LiteLLM SDK 能够正常调用 OpenAI 模型，并能正确发送 Trace 数据到指定的扣子罗盘空间中。环境变量配置格式及说明如下：  
```Python
OTEL_EXPORTER_OTLP_HEADERS=cozeloop-workspace-id={CozeLoop空间ID},Authorization=Bearer {个人访问令牌或服务访问令牌}
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.coze.cn/v1/loop/opentelemetry/v1/traces
OPENAI_BASE_URL=***
OPENAI_API_KEY=***
OPENAI_MODEL_NAME=***
```

<!-- @cols-width: 322,595 -->
| | | \
|**环境变量** |**说明** |
|---|---|
| | | \
|OTEL_EXPORTER_OTLP_HEADERS  |设置上报数据时所需的扣子罗盘认证信息和工作空间标识。包括如下参数：  |\
| | |\
| |* cozeloop-workspace-id：配置为扣子罗盘工作空间 ID。获取步骤，请参考[获取扣子罗盘工作空间 ID](/cozeloop/get_workspace_id_and_token#01dede13)。 |\
| |* Authorization：配置为扣子罗盘的个人访问令牌或服务访问令牌。获取步骤，请参考[配置个人访问令牌](/cozeloop/authentication-for-sdk#05d27a86)、[配置服务访问令牌](/cozeloop/authentication-for-sdk#83f924a1)。 |
| | | \
|OTEL_EXPORTER_OTLP_TRACES_ENDPOINT  |指定 OpenTelemetry 数据的上报地址，固定为 `https://api.coze.cn/v1/loop/opentelemetry/v1/traces`。  |
| | | \
|OPENAI_BASE_URL |配置 OpenAI 模型的服务地址。 |\
| | |\
| |* 推荐使用方舟模型，其 BASE_URL 固定为 `https://ark.cn-beijing.volces.com/api/v3`。 |\
| |* 使用其他 OpenAI 模型时，配置为其对应的 BASE_URL 即可。 |
| | | \
|OPENAI_API_KEY |配置 OpenAI 模型的 API Key。 |\
| | |\
| |* 推荐使用方舟模型，其 API Key 获取步骤，请参考 [API Key 管理](https://www.volcengine.com/docs/82379/1361424)。 |\
| |* 使用其他 OpenAI 模型时，配置为其对应的 API Key 即可。 |
| | | \
|OPENAI_MODEL_NAME |配置 OpenAI 模型名称，需使用模型提供方作为前缀。例如 openai/doubao-1-5-pro-256k-250115。 |

## 上报 Trace {#37b928e2}
完成上述配置后，你可以调用 OpenAI 模型开展 AI 对话，并通过 OpenTelemetry Python SDK 上报 Trace 数据到扣子罗盘。以下示例将分别展示自动上报 Trace 数据与上报自定义 Span 节点数据的具体实现方式。
### 自动上报 {#e6eaf719}
在本示例中，使用 LiteLLM 框架调用火山方舟 doubao-1-5-pro-256k-250115 模型完成天气查询功能，并通过 OpenTelemetry SDK 自动收集和上报 OpenAI 模型执行过程中的 Trace 数据到扣子罗盘，包括 LLM 调用、工具执行、请求处理流程等数据，从而实现对 AI 模型调用过程的全面监控和分析。
```Python
from time import sleep
import os
import json
from typing import Any, Dict, List, Optional

import litellm
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider  # noqa: E402
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, BatchSpanProcessor
from openinference.instrumentation.litellm import LiteLLMInstrumentor

# OpenAI env
os.environ['OPENAI_BASE_URL'] = 'https://ark.cn-beijing.volces.com/api/v3' # use ark model url
os.environ['OPENAI_API_KEY'] = '***' # your ark api key, from https://www.volcengine.com/docs/82379/1361424
os.environ['OPENAI_MODEL_NAME'] = 'openai/doubao-1-5-pro-256k-250115' # your model name, use model provider as prefix, like openai/

# OTEL env
os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = "https://api.coze.cn/v1/loop/opentelemetry/v1/traces" # cozeloop otel endpoint
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = "cozeloop-workspace-id=***,Authorization=Bearer ***" # set your 'spaceID' and 'pat or sat token'

# Set up OpenTelemetry tracing
otlp_exporter = OTLPSpanExporter(
    timeout=10,
)
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(otlp_exporter)
)
tracer = trace.get_tracer(__name__)

LiteLLMInstrumentor().instrument()


def get_current_weather(location: str, unit: str = "fahrenheit") -> str:
    with tracer.start_as_current_span("get_current_weather") as span:
        span.set_attribute("cozeloop.span_type", "tool")
        span.set_attribute("cozeloop.input", json.dumps({"location": location, "unit": unit}))

        res = ""
        if "tokyo" in location.lower():
            res = json.dumps({"location": "Tokyo", "temperature": "10", "unit": "celsius"})
        elif "san francisco" in location.lower():
            res = json.dumps({"location": "San Francisco", "temperature": "72", "unit": "fahrenheit"})
        elif "paris" in location.lower():
            res = json.dumps({"location": "Paris", "temperature": "22", "unit": "celsius"})
        else:
            res = json.dumps({"location": location, "temperature": "unknown"})
        span.set_attribute("cozeloop.output", res)

    return res


def to_dict_message(msg: Any) -> Dict[str, Any]:
    return {
        "role": str(getattr(msg, "role", "")),
        "content": str(getattr(msg, "content", "")),
    }


def parallel_function_call() -> None:
    try:
        messages: List[Dict[str, Any]] = [
            {
                "role": "user",
                "content": "What's the weather like in San Francisco, Tokyo, and Paris?",
            }
        ]
        tools = [
            {
                "type": "function",
                "function": {
                    "name": "get_current_weather",
                    "description": "Get the current weather in a given location",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "location": {
                                "type": "string",
                                "description": "The city and state, e.g. San Francisco, CA",
                            },
                            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                        },
                        "required": ["location"],
                    },
                },
            }
        ]
        response: Any = litellm.completion(
            base_url=os.environ["OPENAI_BASE_URL"],
            api_key=os.environ["OPENAI_API_KEY"],
            model=os.environ["OPENAI_MODEL_NAME"],
            messages=messages,
            tools=tools,
            tool_choice="auto",
        )
        print("\nFirst LLM Response:\n", response)
        response_message: Any = response.choices[0].message
        tool_calls: Optional[Any] = getattr(response_message, "tool_calls", None)

        print("\nLength of tool calls", len(list(tool_calls or [])))

        if tool_calls:
            available_functions = {
                "get_current_weather": get_current_weather,
            }
            messages.append(response_message)
            for tool_call in list(tool_calls):
                function_name = getattr(getattr(tool_call, "function", None), "name", None)
                if function_name is None:
                    continue
                function_to_call = available_functions[function_name]
                function_args = json.loads(
                    getattr(getattr(tool_call, "function", None), "arguments", "{}")
                )
                function_response = function_to_call(
                    location=function_args.get("location"),
                    unit=function_args.get("unit"),
                )
                messages.append(
                    {
                        "tool_call_id": getattr(tool_call, "id", ""),
                        "role": "tool",
                        "name": function_name,
                        "content": function_response,
                    }
                )
            second_response: Any = litellm.completion(
                base_url=os.environ["OPENAI_BASE_URL"],
                api_key=os.environ["OPENAI_API_KEY"],
                model=os.environ["OPENAI_MODEL_NAME"],
                messages=messages,
            )
            print("\nSecond LLM response:\n", second_response)
    except Exception as e:
        print(f"Error occurred: {e}")


if __name__ == "__main__":
    # set custom span, name is root_span
    with tracer.start_as_current_span("root_span") as span:
        span.set_attribute("cozeloop.span_type", "custom")

        # start call
        parallel_function_call()
```

### 上报自定义节点 {#cad1a9c1}
如果你需要上报自定义 Span 节点，可以使用 OpenTelemetry SDK 手动创建 Span 并上报。OpenTelemetry SDK 手动上报的节点数据支持和自动上报的节点数据串联，形成完整的 Trace 调用树。
自定义的 OpenTelemetry Span，attribute 和 event 规范需遵循 [OpenTelemetry 字段映射](https://loop.coze.cn/open/docs/cozeloop/opentelemetry_field_mapping)。
```Python
# 设置自定义Span节点 root_span
with tracer.start_as_current_span("root_span") as span:
    span.set_attribute("cozeloop.span_type", "custom")

    # 启动LiteLLM调用
    parallel_function_call()
```

## 查看 Trace 数据 {#6388e9cc}
上报 Trace 数据后，你可以在[扣子罗盘](https://loop.coze.cn/)的 **Trace** 页面，找到并单击目标 Span，查看上报的 Trace 数据。
![Image=684x339](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/8f848c48af274935ad9842518cf2ebdf~tplv-goo7wpa0wc-image.image)
## 更多示例 {#cb348d01}
关于上报 LiteLLM SDK Trace 数据的更多示例，请参考 [LiteLLM SDK](https://github.com/coze-dev/cozeloop-examples/tree/main/python/integration/framework/litellm_sdk)。

