chore: 添加虚拟环境到仓库

- 添加 backend_service/venv 虚拟环境
- 包含所有Python依赖包
- 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
2025-12-03 10:19:25 +08:00
parent a6c2027caa
commit c4f851d387
12655 changed files with 3009376 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
import asyncio
import os
from functools import wraps
from enum import Enum
from typing import Any, Callable, Dict, Optional, Sequence, Union, TypeVar
from opentelemetry import trace
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
)
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from chromadb.config import Component
from chromadb.config import System
class OpenTelemetryGranularity(Enum):
"""The granularity of the OpenTelemetry spans."""
NONE = "none"
"""No spans are emitted."""
OPERATION = "operation"
"""Spans are emitted for each operation."""
OPERATION_AND_SEGMENT = "operation_and_segment"
"""Spans are emitted for each operation and segment."""
ALL = "all"
"""Spans are emitted for almost every method call."""
# Greater is more restrictive. So "all" < "operation" (and everything else),
# "none" > everything.
def __lt__(self, other: Any) -> bool:
"""Compare two granularities."""
order = [
OpenTelemetryGranularity.ALL,
OpenTelemetryGranularity.OPERATION_AND_SEGMENT,
OpenTelemetryGranularity.OPERATION,
OpenTelemetryGranularity.NONE,
]
return order.index(self) < order.index(other)
class OpenTelemetryClient(Component):
def __init__(self, system: System):
super().__init__(system)
otel_init(
system.settings.chroma_otel_service_name,
system.settings.chroma_otel_collection_endpoint,
system.settings.chroma_otel_collection_headers,
OpenTelemetryGranularity(
system.settings.chroma_otel_granularity
if system.settings.chroma_otel_granularity
else "none"
),
)
tracer: Optional[trace.Tracer] = None
granularity: OpenTelemetryGranularity = OpenTelemetryGranularity("none")
def otel_init(
otel_service_name: Optional[str],
otel_collection_endpoint: Optional[str],
otel_collection_headers: Optional[Dict[str, str]],
otel_granularity: OpenTelemetryGranularity,
) -> None:
"""Initializes module-level state for OpenTelemetry.
Parameters match the environment variables which configure OTel as documented
at https://docs.trychroma.com/deployment/observability.
- otel_service_name: The name of the service for OTel tagging and aggregation.
- otel_collection_endpoint: The endpoint to which OTel spans are sent
(e.g. api.honeycomb.com).
- otel_collection_headers: The headers to send with OTel spans
(e.g. {"x-honeycomb-team": "abc123"}).
- otel_granularity: The granularity of the spans to emit.
"""
if otel_granularity == OpenTelemetryGranularity.NONE:
return
resource = Resource(attributes={SERVICE_NAME: str(otel_service_name)})
provider = TracerProvider(resource=resource)
provider.add_span_processor(
BatchSpanProcessor(
# TODO: we may eventually want to make this configurable.
OTLPSpanExporter(
endpoint=str(otel_collection_endpoint),
headers=otel_collection_headers,
)
)
)
trace.set_tracer_provider(provider)
global tracer, granularity
tracer = trace.get_tracer(__name__)
granularity = otel_granularity
T = TypeVar("T", bound=Callable) # type: ignore[type-arg]
def trace_method(
trace_name: str,
trace_granularity: OpenTelemetryGranularity,
attributes: Optional[
Dict[
str,
Union[
str,
bool,
float,
int,
Sequence[str],
Sequence[bool],
Sequence[float],
Sequence[int],
],
]
] = None,
) -> Callable[[T], T]:
"""A decorator that traces a method."""
def decorator(f: T) -> T:
if asyncio.iscoroutinefunction(f):
@wraps(f)
async def async_wrapper(*args, **kwargs): # type: ignore[no-untyped-def]
global tracer, granularity
if trace_granularity < granularity:
return await f(*args, **kwargs)
if not tracer:
return await f(*args, **kwargs)
with tracer.start_as_current_span(trace_name, attributes=attributes):
add_attributes_to_current_span(
{"pod_name": os.environ.get("HOSTNAME")}
)
return await f(*args, **kwargs)
return async_wrapper # type: ignore
else:
@wraps(f)
def wrapper(*args, **kwargs): # type: ignore[no-untyped-def]
global tracer, granularity
if trace_granularity < granularity:
return f(*args, **kwargs)
if not tracer:
return f(*args, **kwargs)
with tracer.start_as_current_span(trace_name, attributes=attributes):
add_attributes_to_current_span(
{"pod_name": os.environ.get("HOSTNAME")}
)
return f(*args, **kwargs)
return wrapper # type: ignore
return decorator
def add_attributes_to_current_span(
attributes: Dict[
str,
Union[
str,
bool,
float,
int,
Sequence[str],
Sequence[bool],
Sequence[float],
Sequence[int],
None,
],
]
) -> None:
"""Add attributes to the current span."""
global tracer, granularity
if granularity == OpenTelemetryGranularity.NONE:
return
if not tracer:
return
span = trace.get_current_span()
span.set_attributes({k: v for k, v in attributes.items() if v is not None})

View File

@@ -0,0 +1,10 @@
from typing import List, Optional
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
def instrument_fastapi(app: FastAPI, excluded_urls: Optional[List[str]] = None) -> None:
"""Instrument FastAPI to emit OpenTelemetry spans."""
FastAPIInstrumentor.instrument_app(
app, excluded_urls=",".join(excluded_urls) if excluded_urls else None
)

View File

@@ -0,0 +1,95 @@
import binascii
import collections
import grpc
from opentelemetry.trace import StatusCode, SpanKind
class _ClientCallDetails(
collections.namedtuple(
"_ClientCallDetails", ("method", "timeout", "metadata", "credentials")
),
grpc.ClientCallDetails,
):
pass
def _encode_span_id(span_id: int) -> str:
return binascii.hexlify(span_id.to_bytes(8, "big")).decode()
def _encode_trace_id(trace_id: int) -> str:
return binascii.hexlify(trace_id.to_bytes(16, "big")).decode()
# Using OtelInterceptor with gRPC:
# 1. Instantiate the interceptor: interceptors = [OtelInterceptor()]
# 2. Intercept the channel: channel = grpc.intercept_channel(channel, *interceptors)
class OtelInterceptor(
grpc.UnaryUnaryClientInterceptor,
grpc.UnaryStreamClientInterceptor,
grpc.StreamUnaryClientInterceptor,
grpc.StreamStreamClientInterceptor,
):
def _intercept_call(self, continuation, client_call_details, request_or_iterator):
from chromadb.telemetry.opentelemetry import tracer
if tracer is None:
return continuation(client_call_details, request_or_iterator)
with tracer.start_as_current_span(
f"RPC {client_call_details.method}", kind=SpanKind.CLIENT
) as span:
# Prepare metadata for propagation
metadata = (
client_call_details.metadata[:] if client_call_details.metadata else []
)
metadata.extend(
[
(
"chroma-traceid",
_encode_trace_id(span.get_span_context().trace_id),
),
("chroma-spanid", _encode_span_id(span.get_span_context().span_id)),
]
)
# Update client call details with new metadata
new_client_details = _ClientCallDetails(
client_call_details.method,
client_call_details.timeout,
tuple(metadata), # Ensure metadata is a tuple
client_call_details.credentials,
)
try:
result = continuation(new_client_details, request_or_iterator)
# Set attributes based on the result
if hasattr(result, "details") and result.details():
span.set_attribute("rpc.detail", result.details())
span.set_attribute("rpc.status_code", result.code().name.lower())
span.set_attribute("rpc.status_code_value", result.code().value[0])
# Set span status based on gRPC call result
if result.code() != grpc.StatusCode.OK:
span.set_status(StatusCode.ERROR, description=str(result.code()))
return result
except Exception as e:
# Log exception details and re-raise
span.set_attribute("rpc.error", str(e))
span.set_status(StatusCode.ERROR, description=str(e))
raise
def intercept_unary_unary(self, continuation, client_call_details, request):
return self._intercept_call(continuation, client_call_details, request)
def intercept_unary_stream(self, continuation, client_call_details, request):
return self._intercept_call(continuation, client_call_details, request)
def intercept_stream_unary(
self, continuation, client_call_details, request_iterator
):
return self._intercept_call(continuation, client_call_details, request_iterator)
def intercept_stream_stream(
self, continuation, client_call_details, request_iterator
):
return self._intercept_call(continuation, client_call_details, request_iterator)