Files
wehub-resource-sync 85742ab165
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:17 +08:00

542 lines
21 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import logging
import threading
import warnings
from datetime import datetime, timezone
from typing import Any, Callable, Dict, Iterator, List
import weave.trace.weave_init
from pydantic import validate_call
from weave.trace_server import trace_server_interface as tsi
from weave.trace_server.ids import generate_id
from weave.trace_server_bindings.client_interface import TraceServerClientInterface
from weave.trace_server_bindings.models import ServerInfoRes
logger = logging.getLogger(__name__)
__all__ = [
"instrument_weave",
"uninstrument_weave",
"InMemoryWeaveTraceServer",
]
class InMemoryWeaveTraceServer(TraceServerClientInterface):
"""A minimal in-memory implementation of the TraceServerInterface.
It stores calls and objects in local dictionaries and returns valid Pydantic
responses to satisfy the Weave client and FullTraceServerInterface protocol.
"""
def __init__(self):
# Minimal storage to allow basic querying in tests
self.calls: Dict[str, tsi.CallSchema] = {}
self.partial_calls: Dict[str, Dict[str, Any]] = {}
self.objs: Dict[str, Any] = {}
self.files: Dict[str, bytes] = {}
self.feedback: List[tsi.FeedbackCreateReq] = []
self._call_threading_lock = threading.Lock()
@classmethod
def from_env(cls, *args: Any, **kwargs: Any) -> InMemoryWeaveTraceServer:
return cls()
def server_info(self) -> ServerInfoRes:
return ServerInfoRes(min_required_weave_python_version="0.52.22")
def ensure_project_exists(self, entity: str, project: str) -> tsi.EnsureProjectExistsRes:
return tsi.EnsureProjectExistsRes(project_name=project)
# --- Call API ---
@validate_call
def call_start(self, req: tsi.CallStartReq) -> tsi.CallStartRes:
# NOTE: It's not necessary that call_end must be called after call_start.
request_content = req.start.model_dump(exclude_none=True)
# If id needs to be generated here, it's very likely we won't be able to find the call later.
# This is just to make the type checker happy.
call_id = request_content.get("id") or generate_id()
trace_id = request_content.get("trace_id") or generate_id()
request_content["id"] = call_id
request_content["trace_id"] = trace_id
with self._call_threading_lock:
if call_id in self.partial_calls:
# call_end has already been called for this call.
kwargs = {**request_content, **self.partial_calls[call_id]}
self.calls[call_id] = tsi.CallSchema(**kwargs)
del self.partial_calls[call_id]
else:
self.partial_calls[call_id] = request_content
return tsi.CallStartRes(id=call_id, trace_id=trace_id)
@validate_call
def call_end(self, req: tsi.CallEndReq) -> tsi.CallEndRes:
request_content = req.end.model_dump(exclude_none=True)
call_id = req.end.id
with self._call_threading_lock:
if call_id in self.partial_calls:
# End request always override the start request content.
kwargs = {**self.partial_calls[call_id], **request_content}
self.calls[call_id] = tsi.CallSchema(**kwargs)
del self.partial_calls[call_id]
else:
self.partial_calls[call_id] = request_content
return tsi.CallEndRes()
@validate_call
def call_start_batch(self, req: tsi.CallCreateBatchReq) -> tsi.CallCreateBatchRes:
for item in req.batch:
if isinstance(item, tsi.CallStartReq):
self.call_start(item)
elif isinstance(item, tsi.CallEndReq):
self.call_end(item)
return tsi.CallCreateBatchRes(res=[])
@validate_call
def call_read(self, req: tsi.CallReadReq) -> tsi.CallReadRes:
call_data = self.calls.get(req.id)
return tsi.CallReadRes(call=call_data)
@validate_call
def calls_query(self, req: tsi.CallsQueryReq) -> tsi.CallsQueryRes:
return tsi.CallsQueryRes(calls=list(self.calls_query_stream(req)))
@validate_call
def calls_query_stream(self, req: tsi.CallsQueryReq) -> Iterator[tsi.CallSchema]:
yield from self.calls.values()
@validate_call
def calls_delete(self, req: tsi.CallsDeleteReq) -> tsi.CallsDeleteRes:
num_deleted = 0
for call_id in req.call_ids:
if call_id in self.calls:
del self.calls[call_id]
num_deleted += 1
return tsi.CallsDeleteRes(num_deleted=num_deleted)
@validate_call
def call_update(self, req: tsi.CallUpdateReq) -> tsi.CallUpdateRes:
return tsi.CallUpdateRes()
@validate_call
def calls_query_stats(self, req: tsi.CallsQueryStatsReq) -> tsi.CallsQueryStatsRes:
return tsi.CallsQueryStatsRes(count=len(self.calls))
# --- Cost API ---
@validate_call
def cost_create(self, req: tsi.CostCreateReq) -> tsi.CostCreateRes:
return tsi.CostCreateRes(ids=[(generate_id(), generate_id()) for _ in req.costs])
@validate_call
def cost_query(self, req: tsi.CostQueryReq) -> tsi.CostQueryRes:
return tsi.CostQueryRes(results=[])
@validate_call
def cost_purge(self, req: tsi.CostPurgeReq) -> tsi.CostPurgeRes:
return tsi.CostPurgeRes()
# --- Object API (Legacy V1) ---
@validate_call
def obj_create(self, req: tsi.ObjCreateReq) -> tsi.ObjCreateRes:
digest = generate_id()
self.objs[digest] = req.obj
return tsi.ObjCreateRes(digest=digest)
@validate_call
def obj_read(self, req: tsi.ObjReadReq) -> tsi.ObjReadRes:
return tsi.ObjReadRes(obj=self.objs.get(req.digest, {}))
@validate_call
def objs_query(self, req: tsi.ObjQueryReq) -> tsi.ObjQueryRes:
return tsi.ObjQueryRes(objs=[])
@validate_call
def obj_delete(self, req: tsi.ObjDeleteReq) -> tsi.ObjDeleteRes:
return tsi.ObjDeleteRes(num_deleted=0)
# --- Table API ---
@validate_call
def table_create(self, req: tsi.TableCreateReq) -> tsi.TableCreateRes:
return tsi.TableCreateRes(digest=generate_id(), row_digests=[])
@validate_call
def table_create_from_digests(self, req: tsi.TableCreateFromDigestsReq) -> tsi.TableCreateFromDigestsRes:
return tsi.TableCreateFromDigestsRes(digest=generate_id())
@validate_call
def table_update(self, req: tsi.TableUpdateReq) -> tsi.TableUpdateRes:
return tsi.TableUpdateRes(digest=generate_id(), updated_row_digests=[])
@validate_call
def table_query(self, req: tsi.TableQueryReq) -> tsi.TableQueryRes:
return tsi.TableQueryRes(rows=[])
@validate_call
def table_query_stream(self, req: tsi.TableQueryReq) -> Iterator[tsi.TableRowSchema]:
yield from []
@validate_call
def table_query_stats(self, req: tsi.TableQueryStatsReq) -> tsi.TableQueryStatsRes:
return tsi.TableQueryStatsRes(count=0)
@validate_call
def table_query_stats_batch(self, req: tsi.TableQueryStatsBatchReq) -> tsi.TableQueryStatsBatchRes:
return tsi.TableQueryStatsBatchRes(tables=[])
# --- Ref API ---
@validate_call
def refs_read_batch(self, req: tsi.RefsReadBatchReq) -> tsi.RefsReadBatchRes:
return tsi.RefsReadBatchRes(vals=[])
# --- File API ---
def file_create(self, req: tsi.FileCreateReq) -> tsi.FileCreateRes:
self.files[req.name] = req.content
return tsi.FileCreateRes(digest=generate_id())
def file_content_read(self, req: tsi.FileContentReadReq) -> tsi.FileContentReadRes:
return tsi.FileContentReadRes(content=self.files.get(req.digest, b"dummy_content"))
def files_stats(self, req: tsi.FilesStatsReq) -> tsi.FilesStatsRes:
total_size = sum(len(c) for c in self.files.values())
return tsi.FilesStatsRes(total_size_bytes=total_size)
# --- Feedback API ---
@validate_call
def feedback_create(self, req: tsi.FeedbackCreateReq) -> tsi.FeedbackCreateRes:
req.id = req.id or generate_id()
self.feedback.append(req)
return tsi.FeedbackCreateRes(
id=req.id,
created_at=datetime.now(timezone.utc),
wb_user_id="dummy_user",
payload=req.payload,
)
def feedback_create_batch(self, req: tsi.FeedbackCreateBatchReq) -> tsi.FeedbackCreateBatchRes:
results: List[tsi.FeedbackCreateRes] = []
for item in req.batch:
res = self.feedback_create(item)
results.append(res)
return tsi.FeedbackCreateBatchRes(res=results)
@validate_call
def feedback_query(self, req: tsi.FeedbackQueryReq) -> tsi.FeedbackQueryRes:
return tsi.FeedbackQueryRes(result=[])
@validate_call
def feedback_purge(self, req: tsi.FeedbackPurgeReq) -> tsi.FeedbackPurgeRes:
self.feedback.clear()
return tsi.FeedbackPurgeRes()
@validate_call
def feedback_replace(self, req: tsi.FeedbackReplaceReq) -> tsi.FeedbackReplaceRes:
return tsi.FeedbackReplaceRes(
id=req.id or generate_id(),
created_at=datetime.now(timezone.utc),
wb_user_id="dummy",
payload={},
)
# --- Action API ---
@validate_call
def actions_execute_batch(self, req: tsi.ActionsExecuteBatchReq) -> tsi.ActionsExecuteBatchRes:
return tsi.ActionsExecuteBatchRes()
# --- Execute LLM API ---
@validate_call
def completions_create(self, req: tsi.CompletionsCreateReq) -> tsi.CompletionsCreateRes:
return tsi.CompletionsCreateRes(response={"choices": [{"text": "dummy completion"}]})
@validate_call
def completions_create_stream(self, req: tsi.CompletionsCreateReq) -> Iterator[dict[str, Any]]:
yield {"choices": [{"text": "dummy "}]}
yield {"choices": [{"text": "stream"}]}
# --- Execute Image Generation API ---
@validate_call
def image_create(self, req: tsi.ImageGenerationCreateReq) -> tsi.ImageGenerationCreateRes:
return tsi.ImageGenerationCreateRes(response={})
# --- Project Statistics API ---
@validate_call
def project_stats(self, req: tsi.ProjectStatsReq) -> tsi.ProjectStatsRes:
return tsi.ProjectStatsRes(
trace_storage_size_bytes=0,
objects_storage_size_bytes=0,
tables_storage_size_bytes=0,
files_storage_size_bytes=0,
)
# --- Thread API ---
@validate_call
def threads_query_stream(self, req: tsi.ThreadsQueryReq) -> Iterator[tsi.ThreadSchema]:
yield from []
# --- Evaluation API (V1) ---
@validate_call
def evaluate_model(self, req: tsi.EvaluateModelReq) -> tsi.EvaluateModelRes:
return tsi.EvaluateModelRes(call_id=generate_id())
@validate_call
def evaluation_status(self, req: tsi.EvaluationStatusReq) -> tsi.EvaluationStatusRes:
return tsi.EvaluationStatusRes(status=tsi.EvaluationStatusNotFound())
# --- OTEL API ---
def otel_export(self, req: tsi.OtelExportReq) -> tsi.OtelExportRes:
return tsi.OtelExportRes()
# ==========================================
# Object Interface (V2 APIs)
# ==========================================
# --- Ops ---
def op_create(self, req: tsi.OpCreateReq) -> tsi.OpCreateRes:
return tsi.OpCreateRes(digest=generate_id(), object_id=generate_id(), version_index=0)
def op_read(self, req: tsi.OpReadReq) -> tsi.OpReadRes:
return tsi.OpReadRes(op=None) # type: ignore
def op_list(self, req: tsi.OpListReq) -> Iterator[tsi.OpReadRes]:
yield from []
def op_delete(self, req: tsi.OpDeleteReq) -> tsi.OpDeleteRes:
return tsi.OpDeleteRes(num_deleted=0)
# --- Datasets ---
def dataset_create(self, req: tsi.DatasetCreateReq) -> tsi.DatasetCreateRes:
return tsi.DatasetCreateRes(digest=generate_id(), object_id=generate_id(), version_index=0)
def dataset_read(self, req: tsi.DatasetReadReq) -> tsi.DatasetReadRes:
return tsi.DatasetReadRes(dataset=None) # type: ignore
def dataset_list(self, req: tsi.DatasetListReq) -> Iterator[tsi.DatasetReadRes]:
yield from []
def dataset_delete(self, req: tsi.DatasetDeleteReq) -> tsi.DatasetDeleteRes:
return tsi.DatasetDeleteRes(num_deleted=0)
# --- Scorers ---
def scorer_create(self, req: tsi.ScorerCreateReq) -> tsi.ScorerCreateRes:
return tsi.ScorerCreateRes(digest=generate_id(), object_id=generate_id(), version_index=0, scorer=generate_id())
def scorer_read(self, req: tsi.ScorerReadReq) -> tsi.ScorerReadRes:
return tsi.ScorerReadRes(scorer=None) # type: ignore
def scorer_list(self, req: tsi.ScorerListReq) -> Iterator[tsi.ScorerReadRes]:
yield from []
def scorer_delete(self, req: tsi.ScorerDeleteReq) -> tsi.ScorerDeleteRes:
return tsi.ScorerDeleteRes(num_deleted=0)
# --- Evaluations (V2) ---
def evaluation_create(self, req: tsi.EvaluationCreateReq) -> tsi.EvaluationCreateRes:
return tsi.EvaluationCreateRes(
digest=generate_id(), object_id=generate_id(), version_index=0, evaluation_ref=generate_id()
)
def evaluation_read(self, req: tsi.EvaluationReadReq) -> tsi.EvaluationReadRes:
return tsi.EvaluationReadRes(evaluation=None) # type: ignore
def evaluation_list(self, req: tsi.EvaluationListReq) -> Iterator[tsi.EvaluationReadRes]:
yield from []
def evaluation_delete(self, req: tsi.EvaluationDeleteReq) -> tsi.EvaluationDeleteRes:
return tsi.EvaluationDeleteRes(num_deleted=0)
# --- Models ---
def model_create(self, req: tsi.ModelCreateReq) -> tsi.ModelCreateRes:
return tsi.ModelCreateRes(
digest=generate_id(), object_id=generate_id(), version_index=0, model_ref=generate_id()
)
def model_read(self, req: tsi.ModelReadReq) -> tsi.ModelReadRes:
return tsi.ModelReadRes(model=None) # type: ignore
def model_list(self, req: tsi.ModelListReq) -> Iterator[tsi.ModelReadRes]:
yield from []
def model_delete(self, req: tsi.ModelDeleteReq) -> tsi.ModelDeleteRes:
return tsi.ModelDeleteRes(num_deleted=0)
# --- Evaluation Runs ---
def evaluation_run_create(self, req: tsi.EvaluationRunCreateReq) -> tsi.EvaluationRunCreateRes:
return tsi.EvaluationRunCreateRes(evaluation_run_id=generate_id())
def evaluation_run_read(self, req: tsi.EvaluationRunReadReq) -> tsi.EvaluationRunReadRes:
return tsi.EvaluationRunReadRes(evaluation_run=None) # type: ignore
def evaluation_run_list(self, req: tsi.EvaluationRunListReq) -> Iterator[tsi.EvaluationRunReadRes]:
yield from []
def evaluation_run_delete(self, req: tsi.EvaluationRunDeleteReq) -> tsi.EvaluationRunDeleteRes:
return tsi.EvaluationRunDeleteRes(num_deleted=0)
def evaluation_run_finish(self, req: tsi.EvaluationRunFinishReq) -> tsi.EvaluationRunFinishRes:
return tsi.EvaluationRunFinishRes(success=True)
# --- Predictions ---
def prediction_create(self, req: tsi.PredictionCreateReq) -> tsi.PredictionCreateRes:
return tsi.PredictionCreateRes(prediction_id=generate_id())
def prediction_read(self, req: tsi.PredictionReadReq) -> tsi.PredictionReadRes:
return tsi.PredictionReadRes(prediction=None) # type: ignore
def prediction_list(self, req: tsi.PredictionListReq) -> Iterator[tsi.PredictionReadRes]:
yield from []
def prediction_delete(self, req: tsi.PredictionDeleteReq) -> tsi.PredictionDeleteRes:
return tsi.PredictionDeleteRes(num_deleted=0)
def prediction_finish(self, req: tsi.PredictionFinishReq) -> tsi.PredictionFinishRes:
return tsi.PredictionFinishRes(success=True)
# --- Scores ---
def score_create(self, req: tsi.ScoreCreateReq) -> tsi.ScoreCreateRes:
return tsi.ScoreCreateRes(score_id=generate_id())
def score_read(self, req: tsi.ScoreReadReq) -> tsi.ScoreReadRes:
return tsi.ScoreReadRes(score=None) # type: ignore
def score_list(self, req: tsi.ScoreListReq) -> Iterator[tsi.ScoreReadRes]:
yield from []
def score_delete(self, req: tsi.ScoreDeleteReq) -> tsi.ScoreDeleteRes:
return tsi.ScoreDeleteRes(num_deleted=0)
# Experimental unstable APIs
# We don't support these APIs yet.
def annotation_queue_create(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def annotation_queues_query_stream(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def annotation_queue_read(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def annotation_queue_add_calls(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def annotation_queues_stats(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def annotation_queue_items_query(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def annotator_queue_items_progress_update(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def calls_complete(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def call_start_v2(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def call_end_v2(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def call_stats(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def trace_usage(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
def calls_usage(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError()
# Module-level storage for originals
_original_init_weave_get_server: Callable[..., Any] | None = None
_original_get_entity_project_from_project_name: Callable[..., Any] | None = None
_original_get_username: Callable[..., Any] | None = None
def init_weave_get_server_factory(server: InMemoryWeaveTraceServer) -> Callable[..., Any]:
# Bypass the usage of Weave remote server
def init_weave_get_server(*args: Any, **kwargs: Any) -> InMemoryWeaveTraceServer:
return server
return init_weave_get_server
def get_entity_project_from_project_name_factory(entity_name: str) -> tuple[str, str]:
# Bypass the usage of API
try:
assert _original_get_entity_project_from_project_name is not None
if _original_get_entity_project_from_project_name is not get_entity_project_from_project_name_factory:
return _original_get_entity_project_from_project_name(entity_name)
else:
warnings.warn("W&B integration might have been repeatedly/recursively instrumented.")
return "agl", "weave"
except weave.trace.weave_init.WeaveWandbAuthenticationException:
# In case API is not available.
return "agl", "weave"
def get_username() -> str:
# Bypass the usage of API
try:
assert _original_get_username is not None
return _original_get_username()
except RuntimeError:
return "agl"
except Exception as exc:
warnings.warn(f"Unexpected error in get_username. Using default username. Error: {exc}")
return "agl"
def instrument_weave(server: InMemoryWeaveTraceServer):
"""Patch the Weave/W&B integration to bypass actual network calls for testing."""
global _original_init_weave_get_server, _original_get_entity_project_from_project_name, _original_get_username
_original_init_weave_get_server = weave.trace.weave_init.init_weave_get_server
_original_get_entity_project_from_project_name = weave.trace.weave_init.get_entity_project_from_project_name
_original_get_username = weave.trace.weave_init.get_username
weave.trace.weave_init.init_weave_get_server = init_weave_get_server_factory(server)
weave.trace.weave_init.get_entity_project_from_project_name = get_entity_project_from_project_name_factory
weave.trace.weave_init.get_username = get_username
def uninstrument_weave():
"""Restore the original Weave/W&B integration methods and HTTP requests."""
global _original_init_weave_get_server, _original_get_entity_project_from_project_name, _original_get_username
if _original_init_weave_get_server is not None:
weave.trace.weave_init.init_weave_get_server = _original_init_weave_get_server
_original_init_weave_get_server = None
else:
raise RuntimeError("Weave/W&B integration was not instrumented.")
if _original_get_entity_project_from_project_name is not None:
weave.trace.weave_init.get_entity_project_from_project_name = _original_get_entity_project_from_project_name
_original_get_entity_project_from_project_name = None
else:
raise RuntimeError("Weave/W&B integration was not instrumented.")
if _original_get_username is not None:
weave.trace.weave_init.get_username = _original_get_username
_original_get_username = None
else:
raise RuntimeError("Weave/W&B integration was not instrumented.")