fa45d8aa5f
- 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,直连正常
293 lines
11 KiB
Python
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)
|