Files
MoFin/venv/lib/python3.12/site-packages/litellm/integrations/otel/model/semconv.py
T
知微 fa45d8aa5f fix: 小果地址统一node122(兼容LAN+EasyTier)
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
  Privoxy对node122:18003返回500,直连正常
2026-06-30 02:56:35 +08:00

293 lines
11 KiB
Python

"""
Keys follow the OpenTelemetry GenAI semantic conventions (experimental). Anything
without a semconv equivalent lives under the ``litellm.*`` vendor namespace.
"""
from enum import Enum
from typing import Final
class GenAIOperation(str, Enum):
"""Values for ``gen_ai.operation.name``."""
CHAT = "chat"
TEXT_COMPLETION = "text_completion"
EMBEDDINGS = "embeddings"
GENERATE_CONTENT = "generate_content"
CREATE_AGENT = "create_agent" # reserved for future agent spans
INVOKE_AGENT = "invoke_agent" # reserved for future agent spans
EXECUTE_TOOL = "execute_tool" # MCP tool-call spans
class GenAIProvider(str, Enum):
"""Common values for the ``gen_ai.provider.name`` attribute."""
OPENAI = "openai"
ANTHROPIC = "anthropic"
AWS_BEDROCK = "aws.bedrock"
AZURE_AI_OPENAI = "azure.ai.openai"
AZURE_AI_INFERENCE = "azure.ai.inference"
GCP_GEMINI = "gcp.gemini"
GCP_VERTEX_AI = "gcp.vertex_ai"
COHERE = "cohere"
MISTRAL_AI = "mistral_ai"
DEEPSEEK = "deepseek"
GROQ = "groq"
PERPLEXITY = "perplexity"
X_AI = "x_ai"
IBM_WATSONX_AI = "ibm.watsonx.ai"
class MCPMethod(str, Enum):
"""Well-known values for ``mcp.method.name`` that litellm's MCP gateway
serves. The value is the JSON-RPC method exactly as it travels on the wire."""
TOOLS_CALL = "tools/call"
TOOLS_LIST = "tools/list"
PROMPTS_GET = "prompts/get"
PROMPTS_LIST = "prompts/list"
class GenAI:
"""Canonical OTel GenAI span-attribute keys."""
# request
OPERATION_NAME: Final = "gen_ai.operation.name"
PROVIDER_NAME: Final = "gen_ai.provider.name"
REQUEST_MODEL: Final = "gen_ai.request.model"
REQUEST_TEMPERATURE: Final = "gen_ai.request.temperature"
REQUEST_TOP_P: Final = "gen_ai.request.top_p"
REQUEST_TOP_K: Final = "gen_ai.request.top_k"
REQUEST_MAX_TOKENS: Final = "gen_ai.request.max_tokens"
REQUEST_FREQUENCY_PENALTY: Final = "gen_ai.request.frequency_penalty"
REQUEST_PRESENCE_PENALTY: Final = "gen_ai.request.presence_penalty"
REQUEST_STOP_SEQUENCES: Final = "gen_ai.request.stop_sequences"
REQUEST_SEED: Final = "gen_ai.request.seed"
REQUEST_CHOICE_COUNT: Final = "gen_ai.request.choice.count"
REQUEST_ENCODING_FORMATS: Final = "gen_ai.request.encoding_formats"
# response
RESPONSE_ID: Final = "gen_ai.response.id"
RESPONSE_MODEL: Final = "gen_ai.response.model"
RESPONSE_FINISH_REASONS: Final = "gen_ai.response.finish_reasons"
# usage
USAGE_INPUT_TOKENS: Final = "gen_ai.usage.input_tokens"
USAGE_OUTPUT_TOKENS: Final = "gen_ai.usage.output_tokens"
# content (opt-in, gated by capture mode)
INPUT_MESSAGES: Final = "gen_ai.input.messages"
OUTPUT_MESSAGES: Final = "gen_ai.output.messages"
SYSTEM_INSTRUCTIONS: Final = "gen_ai.system_instructions"
OUTPUT_TYPE: Final = "gen_ai.output.type"
CONVERSATION_ID: Final = "gen_ai.conversation.id"
# agent (reserved)
AGENT_ID: Final = "gen_ai.agent.id"
AGENT_NAME: Final = "gen_ai.agent.name"
# tool / tool-call (stamped on MCP tool-call spans). Arguments and result are
# the tool's input/output payloads — sensitive, so they're opt-in and gated by
# the same content-capture mode as prompt/response content.
TOOL_NAME: Final = "gen_ai.tool.name"
TOOL_CALL_ID: Final = "gen_ai.tool.call.id"
TOOL_CALL_ARGUMENTS: Final = "gen_ai.tool.call.arguments"
TOOL_CALL_RESULT: Final = "gen_ai.tool.call.result"
# prompt (MCP ``prompts/get`` etc.)
PROMPT_NAME: Final = "gen_ai.prompt.name"
class MCP:
"""OTel GenAI MCP (Model Context Protocol) span-attribute keys.
``METHOD_NAME`` is the only key litellm populates from a closed request today;
the rest are part of the convention's vocabulary and are stamped when the
corresponding signal (session, protocol version, resource) is available.
"""
METHOD_NAME: Final = "mcp.method.name"
SESSION_ID: Final = "mcp.session.id"
PROTOCOL_VERSION: Final = "mcp.protocol.version"
RESOURCE_URI: Final = "mcp.resource.uri"
class JsonRpc:
"""JSON-RPC keys carried on MCP spans. The error/status code lives in the
``rpc.*`` namespace per semconv, not ``jsonrpc.*``."""
REQUEST_ID: Final = "jsonrpc.request.id"
PROTOCOL_VERSION: Final = "jsonrpc.protocol.version"
RESPONSE_STATUS_CODE: Final = "rpc.response.status_code"
class NetworkTransport(str, Enum):
"""Well-known values for ``network.transport``."""
TCP = "tcp"
UDP = "udp"
QUIC = "quic"
UNIX = "unix"
PIPE = "pipe"
class Network:
"""OTel network keys, recommended on MCP spans to describe the transport
carrying the JSON-RPC messages (stdio pipe, HTTP, websocket, …)."""
PROTOCOL_NAME: Final = "network.protocol.name"
PROTOCOL_VERSION: Final = "network.protocol.version"
TRANSPORT: Final = "network.transport"
class Client:
"""Peer (client) network keys, stamped on MCP *server* spans the same way
``server.*`` is stamped on client spans."""
ADDRESS: Final = "client.address"
PORT: Final = "client.port"
class Error:
TYPE: Final = "error.type"
class ExceptionEvent:
"""OTel exception-event name and attribute keys (semconv ``exception.*``).
The full error message rides ``exception.message`` on a span event rather than
a custom string attribute. Backends recognise these semantic-convention names
and map them as full text; an unrecognised key (e.g. ``error_message``) falls
into the default dynamic template, which truncates strings to a 1024-char
``keyword``.
"""
NAME: Final = "exception"
TYPE: Final = "exception.type"
MESSAGE: Final = "exception.message"
class Server:
ADDRESS: Final = "server.address"
PORT: Final = "server.port"
class DB:
"""Database / cache client-span keys (OTel ``db.*`` semconv).
Stamped on ``DB_CALL`` spans (redis / postgres), which are CLIENT spans for
outbound datastore calls — not on the INTERNAL ``SERVICE`` spans.
"""
SYSTEM_NAME: Final = "db.system.name"
OPERATION_NAME: Final = "db.operation.name"
class HTTP:
"""HTTP server-span keys. Belong on the SERVER span only (never promoted)."""
REQUEST_METHOD: Final = "http.request.method"
ROUTE: Final = "http.route"
RESPONSE_STATUS_CODE: Final = "http.response.status_code"
URL_PATH: Final = "url.path"
class LiteLLM:
"""Vendor-extension keys (no semconv equivalent). Always ``litellm.*``."""
CALL_ID: Final = "litellm.call_id"
COST_PREFIX: Final = "litellm.cost."
METADATA_PREFIX: Final = "litellm.metadata."
TEAM_ID: Final = "litellm.team.id"
TEAM_ALIAS: Final = "litellm.team.alias"
# The team's free-form metadata dict, JSON-serialized into a single value.
TEAM_METADATA: Final = "litellm.team.metadata"
KEY_HASH: Final = "litellm.api_key.hash"
END_USER: Final = "litellm.end_user.id"
# The model string litellm actually sent to the provider (the deployment's
# ``litellm_params.model``), distinct from the user-facing ``gen_ai.request.model``.
PROVIDER_MODEL: Final = "litellm.provider.model"
REQUEST_STREAMING: Final = "litellm.request.streaming"
GUARDRAIL_NAME: Final = "litellm.guardrail.name"
GUARDRAIL_MODE: Final = "litellm.guardrail.mode"
GUARDRAIL_STATUS: Final = "litellm.guardrail.status"
GUARDRAIL_PROVIDER: Final = "litellm.guardrail.provider"
GUARDRAIL_ACTION: Final = "litellm.guardrail.action"
GUARDRAIL_RESPONSE: Final = "litellm.guardrail.response"
GUARDRAIL_VIOLATION_CATEGORIES: Final = "litellm.guardrail.violation_categories"
GUARDRAIL_CONFIDENCE_SCORE: Final = "litellm.guardrail.confidence_score"
GUARDRAIL_RISK_SCORE: Final = "litellm.guardrail.risk_score"
GUARDRAIL_MASKED_ENTITY_COUNT: Final = "litellm.guardrail.masked_entity_count"
GUARDRAIL_DURATION: Final = "litellm.guardrail.duration"
GUARDRAIL_ID: Final = "litellm.guardrail.id"
GUARDRAIL_POLICY_TEMPLATE: Final = "litellm.guardrail.policy_template"
GUARDRAIL_DETECTION_METHOD: Final = "litellm.guardrail.detection_method"
SERVICE_NAME: Final = "litellm.service.name"
SERVICE_CALL_TYPE: Final = "litellm.service.call_type"
PREPROCESSING_MS: Final = "litellm.preprocessing.duration_ms"
# The logical name of the MCP server a tool call was routed to. There is no
# semconv key for an MCP server's *name* (the convention uses ``server.address``
# for its network location), so it lives under the vendor namespace.
MCP_SERVER_NAME: Final = "litellm.mcp.server.name"
class Metric:
"""GenAI metric instrument names."""
TOKEN_USAGE: Final = "gen_ai.client.token.usage"
OPERATION_DURATION: Final = "gen_ai.client.operation.duration"
TOKEN_COST: Final = "gen_ai.client.token.cost"
TIME_TO_FIRST_TOKEN: Final = "gen_ai.client.response.time_to_first_token"
TIME_PER_OUTPUT_TOKEN: Final = "gen_ai.client.response.time_per_output_token"
RESPONSE_DURATION: Final = "gen_ai.client.response.duration"
# litellm ``custom_llm_provider`` -> ``gen_ai.provider.name`` value.
_PROVIDER_BY_LITELLM: dict[str, GenAIProvider] = {
"openai": GenAIProvider.OPENAI,
"text-completion-openai": GenAIProvider.OPENAI,
"azure": GenAIProvider.AZURE_AI_OPENAI,
"azure_ai": GenAIProvider.AZURE_AI_INFERENCE,
"anthropic": GenAIProvider.ANTHROPIC,
"bedrock": GenAIProvider.AWS_BEDROCK,
"bedrock_converse": GenAIProvider.AWS_BEDROCK,
"vertex_ai": GenAIProvider.GCP_VERTEX_AI,
"vertex_ai_beta": GenAIProvider.GCP_VERTEX_AI,
"gemini": GenAIProvider.GCP_GEMINI,
"cohere": GenAIProvider.COHERE,
"cohere_chat": GenAIProvider.COHERE,
"mistral": GenAIProvider.MISTRAL_AI,
"deepseek": GenAIProvider.DEEPSEEK,
"groq": GenAIProvider.GROQ,
"perplexity": GenAIProvider.PERPLEXITY,
"xai": GenAIProvider.X_AI,
"watsonx": GenAIProvider.IBM_WATSONX_AI,
}
# litellm ``call_type`` -> ``gen_ai.operation.name``.
_OPERATION_BY_CALL_TYPE: dict[str, GenAIOperation] = {
"completion": GenAIOperation.CHAT,
"acompletion": GenAIOperation.CHAT,
"completion_with_retries": GenAIOperation.CHAT,
"text_completion": GenAIOperation.TEXT_COMPLETION,
"atext_completion": GenAIOperation.TEXT_COMPLETION,
"embedding": GenAIOperation.EMBEDDINGS,
"aembedding": GenAIOperation.EMBEDDINGS,
"responses": GenAIOperation.CHAT,
"aresponses": GenAIOperation.CHAT,
"call_mcp_tool": GenAIOperation.EXECUTE_TOOL,
}
def resolve_provider(custom_llm_provider: str | None) -> str:
"""Map a litellm provider string to a ``gen_ai.provider.name`` value.
Unknown providers pass through verbatim — the convention explicitly allows
provider-specific values, so an unmapped name is still valid.
"""
if not custom_llm_provider:
return ""
mapped = _PROVIDER_BY_LITELLM.get(custom_llm_provider.lower())
return mapped.value if mapped is not None else custom_llm_provider
def resolve_operation(call_type: str | None) -> GenAIOperation:
"""Map a litellm ``call_type`` to a ``gen_ai.operation.name`` value."""
if not call_type:
return GenAIOperation.CHAT
return _OPERATION_BY_CALL_TYPE.get(call_type.lower(), GenAIOperation.CHAT)