docent-python 0.1.24a0__py3-none-any.whl → 0.1.28a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of docent-python might be problematic. Click here for more details.
- docent/_llm_util/data_models/llm_output.py +8 -0
- docent/_llm_util/llm_svc.py +6 -6
- docent/_llm_util/model_registry.py +4 -0
- docent/_llm_util/providers/anthropic.py +1 -1
- docent/data_models/agent_run.py +1 -0
- docent/judges/runner.py +75 -12
- docent/sdk/client.py +118 -1
- docent/trace.py +312 -47
- {docent_python-0.1.24a0.dist-info → docent_python-0.1.28a0.dist-info}/METADATA +1 -1
- {docent_python-0.1.24a0.dist-info → docent_python-0.1.28a0.dist-info}/RECORD +12 -12
- {docent_python-0.1.24a0.dist-info → docent_python-0.1.28a0.dist-info}/WHEEL +0 -0
- {docent_python-0.1.24a0.dist-info → docent_python-0.1.28a0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -8,6 +8,7 @@ from pydantic import BaseModel
|
|
|
8
8
|
from docent._llm_util.data_models.exceptions import (
|
|
9
9
|
LLM_ERROR_TYPES,
|
|
10
10
|
CompletionTooLongException,
|
|
11
|
+
ContextWindowException,
|
|
11
12
|
LLMException,
|
|
12
13
|
)
|
|
13
14
|
from docent._log_util import get_logger
|
|
@@ -148,6 +149,13 @@ class LLMOutput:
|
|
|
148
149
|
def from_dict(cls, data: dict[str, Any]) -> "LLMOutput":
|
|
149
150
|
error_type_map = {e.error_type_id: e for e in LLM_ERROR_TYPES}
|
|
150
151
|
errors = data.get("errors", [])
|
|
152
|
+
error_types_to_not_log: list[str] = [
|
|
153
|
+
CompletionTooLongException.error_type_id,
|
|
154
|
+
ContextWindowException.error_type_id,
|
|
155
|
+
]
|
|
156
|
+
errors_to_log = [e for e in errors if e not in error_types_to_not_log]
|
|
157
|
+
if errors_to_log:
|
|
158
|
+
logger.error(f"Loading LLM output with errors: {errors}")
|
|
151
159
|
errors = [error_type_map.get(e, LLMException)() for e in errors]
|
|
152
160
|
|
|
153
161
|
completions = data.get("completions", [])
|
docent/_llm_util/llm_svc.py
CHANGED
|
@@ -75,7 +75,7 @@ async def _parallelize_calls(
|
|
|
75
75
|
completion_callback: AsyncLLMOutputStreamingCallback | None,
|
|
76
76
|
# Arguments for the individual completion getter
|
|
77
77
|
client: Any,
|
|
78
|
-
inputs:
|
|
78
|
+
inputs: Sequence[MessagesInput],
|
|
79
79
|
model_name: str,
|
|
80
80
|
tools: list[ToolInfo] | None,
|
|
81
81
|
tool_choice: Literal["auto", "required"] | None,
|
|
@@ -176,7 +176,7 @@ async def _parallelize_calls(
|
|
|
176
176
|
)
|
|
177
177
|
if retry_count >= MAX_VALIDATION_ATTEMPTS:
|
|
178
178
|
logger.error(
|
|
179
|
-
f"Validation failed for {model_name} after {
|
|
179
|
+
f"Validation failed for {model_name} after {retry_count} attempts. Original output: {e.failed_output}"
|
|
180
180
|
)
|
|
181
181
|
result = LLMOutput(
|
|
182
182
|
model=model_name,
|
|
@@ -195,8 +195,8 @@ async def _parallelize_calls(
|
|
|
195
195
|
break
|
|
196
196
|
except Exception as e:
|
|
197
197
|
if not isinstance(e, LLMException):
|
|
198
|
-
logger.
|
|
199
|
-
f"LLM call raised an exception that is not an LLMException: {e}"
|
|
198
|
+
logger.error(
|
|
199
|
+
f"LLM call raised an exception that is not an LLMException: {e}. Failure traceback:\n{traceback.format_exc()}"
|
|
200
200
|
)
|
|
201
201
|
llm_exception = LLMException(e)
|
|
202
202
|
llm_exception.__cause__ = e
|
|
@@ -306,7 +306,7 @@ async def _parallelize_calls(
|
|
|
306
306
|
|
|
307
307
|
class BaseLLMService:
|
|
308
308
|
def __init__(self, max_concurrency: int = DEFAULT_SVC_MAX_CONCURRENCY):
|
|
309
|
-
self._semaphore = Semaphore(max_concurrency)
|
|
309
|
+
self.max_concurrency, self._semaphore = max_concurrency, Semaphore(max_concurrency)
|
|
310
310
|
self._client_cache: dict[tuple[str, str | None], Any] = {} # (provider, api_key) -> client
|
|
311
311
|
self._client_cache_lock = Lock()
|
|
312
312
|
|
|
@@ -326,7 +326,7 @@ class BaseLLMService:
|
|
|
326
326
|
async def get_completions(
|
|
327
327
|
self,
|
|
328
328
|
*,
|
|
329
|
-
inputs:
|
|
329
|
+
inputs: Sequence[MessagesInput],
|
|
330
330
|
model_options: list[ModelOption],
|
|
331
331
|
tools: list[ToolInfo] | None = None,
|
|
332
332
|
tool_choice: Literal["auto", "required"] | None = None,
|
|
@@ -54,6 +54,10 @@ _REGISTRY: list[tuple[str, ModelInfo]] = [
|
|
|
54
54
|
"claude-sonnet-4",
|
|
55
55
|
ModelInfo(rate={"input": 3.0, "output": 15.0}, context_window=200_000),
|
|
56
56
|
),
|
|
57
|
+
(
|
|
58
|
+
"claude-haiku-4-5",
|
|
59
|
+
ModelInfo(rate={"input": 1.0, "output": 5.0}, context_window=200_000),
|
|
60
|
+
),
|
|
57
61
|
(
|
|
58
62
|
"gemini-2.5-flash-lite",
|
|
59
63
|
ModelInfo(
|
|
@@ -178,7 +178,7 @@ def _parse_tool_choice(tool_choice: Literal["auto", "required"] | None) -> ToolC
|
|
|
178
178
|
|
|
179
179
|
def _convert_anthropic_error(e: Exception):
|
|
180
180
|
if isinstance(e, BadRequestError):
|
|
181
|
-
if "context limit" in e.message.lower():
|
|
181
|
+
if "context limit" in e.message.lower() or "prompt is too long" in e.message.lower():
|
|
182
182
|
return ContextWindowException()
|
|
183
183
|
if isinstance(e, RateLimitError):
|
|
184
184
|
return RateLimitException(e)
|
docent/data_models/agent_run.py
CHANGED
docent/judges/runner.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Protocol, Sequence, runtime_checkable
|
|
2
|
+
|
|
1
3
|
import anyio
|
|
2
4
|
from tqdm.auto import tqdm
|
|
3
5
|
|
|
@@ -14,12 +16,28 @@ from docent.judges.impl import build_judge
|
|
|
14
16
|
logger = get_logger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class AgentRunResolver(Protocol):
|
|
21
|
+
async def __call__(self) -> AgentRun | None: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
AgentRunInput = AgentRun | AgentRunResolver
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def _resolve_agent_run(agent_run_input: AgentRunInput) -> AgentRun | None:
|
|
28
|
+
if isinstance(agent_run_input, AgentRun):
|
|
29
|
+
return agent_run_input
|
|
30
|
+
else:
|
|
31
|
+
return await agent_run_input()
|
|
32
|
+
|
|
33
|
+
|
|
17
34
|
async def run_rubric(
|
|
18
|
-
agent_runs:
|
|
35
|
+
agent_runs: Sequence[AgentRunInput],
|
|
19
36
|
rubric: Rubric,
|
|
20
37
|
llm_svc: BaseLLMService,
|
|
21
38
|
callback: JudgeResultCompletionCallback | None = None,
|
|
22
39
|
*,
|
|
40
|
+
n_rollouts_per_input: int | list[int] = 1,
|
|
23
41
|
show_progress: bool = True,
|
|
24
42
|
) -> list[JudgeResult | None]:
|
|
25
43
|
if not agent_runs:
|
|
@@ -27,26 +45,70 @@ async def run_rubric(
|
|
|
27
45
|
if rubric.n_rollouts_per_input <= 0:
|
|
28
46
|
raise ValueError("rubric.n_rollouts_per_input must be greater than 0")
|
|
29
47
|
|
|
48
|
+
# Normalize n_rollouts_per_input to a list
|
|
49
|
+
if isinstance(n_rollouts_per_input, int):
|
|
50
|
+
if n_rollouts_per_input < 0:
|
|
51
|
+
raise ValueError("n_rollouts_per_input must be non-negative")
|
|
52
|
+
rollouts_per_run = [n_rollouts_per_input] * len(agent_runs)
|
|
53
|
+
else:
|
|
54
|
+
rollouts_per_run = n_rollouts_per_input
|
|
55
|
+
if len(rollouts_per_run) != len(agent_runs):
|
|
56
|
+
raise ValueError("n_rollouts_per_input list must match agent_runs length")
|
|
57
|
+
if any(n < 0 for n in rollouts_per_run):
|
|
58
|
+
raise ValueError("All values in n_rollouts_per_input must be non-negative")
|
|
59
|
+
|
|
30
60
|
judge = build_judge(rubric, llm_svc)
|
|
31
61
|
|
|
62
|
+
total_rollouts = sum(rollouts_per_run)
|
|
32
63
|
logger.info(
|
|
33
|
-
"Running rubric %s version %s against %d agent runs",
|
|
64
|
+
"Running rubric %s version %s against %d agent runs with %d total rollouts",
|
|
34
65
|
rubric.id,
|
|
35
66
|
rubric.version,
|
|
36
67
|
len(agent_runs),
|
|
68
|
+
total_rollouts,
|
|
37
69
|
)
|
|
38
70
|
|
|
39
|
-
agent_results: list[JudgeResult | None] = [
|
|
71
|
+
agent_results: list[list[JudgeResult | None]] = [[] for _ in agent_runs]
|
|
40
72
|
progress_bar = tqdm(
|
|
41
|
-
total=
|
|
73
|
+
total=total_rollouts,
|
|
74
|
+
desc=f"Rubric {rubric.id}",
|
|
75
|
+
disable=not show_progress,
|
|
42
76
|
)
|
|
43
77
|
|
|
44
|
-
|
|
45
|
-
|
|
78
|
+
# NOTE(mengk): using a (2 * llm max concurrency) semaphore is a hack to avoid
|
|
79
|
+
# hammering _resolve_agent_run, which makes expensive DB calls, when they aren't going to be
|
|
80
|
+
# immediately processed by the LLMService anyways.
|
|
81
|
+
# TODO(mengk): We should eventually implement a more idiomatic solution to this.
|
|
82
|
+
# It's related to the idea of a global concurrency limiter.
|
|
83
|
+
run_judge_semaphore = anyio.Semaphore(llm_svc.max_concurrency * 2)
|
|
84
|
+
|
|
85
|
+
async def _run_single_judge(index: int, agent_run_input: AgentRunInput):
|
|
86
|
+
async with run_judge_semaphore:
|
|
87
|
+
rollout_results: list[JudgeResult | None] = []
|
|
88
|
+
|
|
89
|
+
if rollouts_per_run[index] == 0:
|
|
90
|
+
agent_results[index] = []
|
|
91
|
+
if callback is not None:
|
|
92
|
+
await callback(index, None)
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
agent_run = await _resolve_agent_run(agent_run_input)
|
|
96
|
+
if agent_run is None:
|
|
97
|
+
if callback is not None:
|
|
98
|
+
await callback(index, None)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
for _ in range(rollouts_per_run[index]):
|
|
102
|
+
result = await judge(agent_run)
|
|
103
|
+
rollout_results.append(result)
|
|
104
|
+
progress_bar.update()
|
|
105
|
+
|
|
106
|
+
agent_results[index] = rollout_results
|
|
46
107
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
108
|
+
if callback is not None:
|
|
109
|
+
# Filter out None results for the callback
|
|
110
|
+
valid_results = [r for r in rollout_results if r is not None]
|
|
111
|
+
await callback(index, valid_results if valid_results else None)
|
|
50
112
|
|
|
51
113
|
try:
|
|
52
114
|
async with anyio.create_task_group() as tg:
|
|
@@ -55,12 +117,13 @@ async def run_rubric(
|
|
|
55
117
|
finally:
|
|
56
118
|
progress_bar.close()
|
|
57
119
|
|
|
58
|
-
|
|
120
|
+
flattened_results = [result for rollouts in agent_results for result in rollouts]
|
|
121
|
+
successful = sum(result is not None for result in flattened_results)
|
|
59
122
|
logger.info(
|
|
60
123
|
"Finished rubric %s: produced %d/%d judge results",
|
|
61
124
|
rubric.id,
|
|
62
125
|
successful,
|
|
63
|
-
len(
|
|
126
|
+
len(flattened_results),
|
|
64
127
|
)
|
|
65
128
|
|
|
66
|
-
return
|
|
129
|
+
return flattened_results
|
docent/sdk/client.py
CHANGED
|
@@ -200,7 +200,7 @@ class Docent:
|
|
|
200
200
|
version: The version of the rubric to get run state for. If None, the latest version is used.
|
|
201
201
|
|
|
202
202
|
Returns:
|
|
203
|
-
dict: Dictionary containing rubric run state with results, job_id, and
|
|
203
|
+
dict: Dictionary containing rubric run state with results, job_id, and total_results_needed.
|
|
204
204
|
|
|
205
205
|
Raises:
|
|
206
206
|
requests.exceptions.HTTPError: If the API request fails.
|
|
@@ -450,6 +450,123 @@ class Docent:
|
|
|
450
450
|
logger.info(f"Successfully shared Collection '{collection_id}' with {email}")
|
|
451
451
|
return response.json()
|
|
452
452
|
|
|
453
|
+
def collection_exists(self, collection_id: str) -> bool:
|
|
454
|
+
"""Check if a collection exists without raising if it does not."""
|
|
455
|
+
url = f"{self._server_url}/{collection_id}/exists"
|
|
456
|
+
response = self._session.get(url)
|
|
457
|
+
self._handle_response_errors(response)
|
|
458
|
+
return bool(response.json())
|
|
459
|
+
|
|
460
|
+
def has_collection_permission(self, collection_id: str, permission: str = "write") -> bool:
|
|
461
|
+
"""Check whether the authenticated user has a specific permission on a collection.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
collection_id: Collection to check.
|
|
465
|
+
permission: Permission level to verify (`read`, `write`, or `admin`).
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
bool: True if the current API key has the requested permission; otherwise False.
|
|
469
|
+
|
|
470
|
+
Raises:
|
|
471
|
+
ValueError: If an unsupported permission value is provided.
|
|
472
|
+
requests.exceptions.HTTPError: If the API request fails.
|
|
473
|
+
"""
|
|
474
|
+
valid_permissions = {"read", "write", "admin"}
|
|
475
|
+
if permission not in valid_permissions:
|
|
476
|
+
raise ValueError(f"permission must be one of {sorted(valid_permissions)}")
|
|
477
|
+
|
|
478
|
+
url = f"{self._server_url}/{collection_id}/has_permission"
|
|
479
|
+
response = self._session.get(url, params={"permission": permission})
|
|
480
|
+
self._handle_response_errors(response)
|
|
481
|
+
|
|
482
|
+
payload = response.json()
|
|
483
|
+
return bool(payload.get("has_permission", False))
|
|
484
|
+
|
|
485
|
+
def get_dql_schema(self, collection_id: str) -> dict[str, Any]:
|
|
486
|
+
"""Retrieve the DQL schema for a collection.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
collection_id: ID of the Collection.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
dict: Dictionary containing available tables, columns, and metadata for DQL queries.
|
|
493
|
+
|
|
494
|
+
Raises:
|
|
495
|
+
requests.exceptions.HTTPError: If the API request fails.
|
|
496
|
+
"""
|
|
497
|
+
url = f"{self._server_url}/dql/{collection_id}/schema"
|
|
498
|
+
response = self._session.get(url)
|
|
499
|
+
self._handle_response_errors(response)
|
|
500
|
+
return response.json()
|
|
501
|
+
|
|
502
|
+
def execute_dql(self, collection_id: str, dql: str) -> dict[str, Any]:
|
|
503
|
+
"""Execute a DQL query against a collection.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
collection_id: ID of the Collection.
|
|
507
|
+
dql: The DQL query string to execute.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
dict: Query execution results including rows, columns, execution metadata, and selected columns.
|
|
511
|
+
|
|
512
|
+
Raises:
|
|
513
|
+
ValueError: If `dql` is empty.
|
|
514
|
+
requests.exceptions.HTTPError: If the API request fails or the query is invalid.
|
|
515
|
+
"""
|
|
516
|
+
if not dql.strip():
|
|
517
|
+
raise ValueError("dql must be a non-empty string")
|
|
518
|
+
|
|
519
|
+
url = f"{self._server_url}/dql/{collection_id}/execute"
|
|
520
|
+
response = self._session.post(url, json={"dql": dql})
|
|
521
|
+
self._handle_response_errors(response)
|
|
522
|
+
return response.json()
|
|
523
|
+
|
|
524
|
+
def select_agent_run_ids(
|
|
525
|
+
self,
|
|
526
|
+
collection_id: str,
|
|
527
|
+
where_clause: str | None = None,
|
|
528
|
+
limit: int | None = None,
|
|
529
|
+
) -> list[str]:
|
|
530
|
+
"""Convenience helper to fetch agent run IDs via DQL.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
collection_id: ID of the Collection to query.
|
|
534
|
+
where_clause: Optional DQL WHERE clause applied to the agent_runs table.
|
|
535
|
+
limit: Optional LIMIT applied to the underlying DQL query.
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
list[str]: Agent run IDs matching the criteria.
|
|
539
|
+
|
|
540
|
+
Raises:
|
|
541
|
+
ValueError: If the inputs are invalid.
|
|
542
|
+
requests.exceptions.HTTPError: If the API request fails.
|
|
543
|
+
"""
|
|
544
|
+
query = "SELECT agent_runs.id AS agent_run_id FROM agent_runs"
|
|
545
|
+
|
|
546
|
+
if where_clause:
|
|
547
|
+
where_clause = where_clause.strip()
|
|
548
|
+
if not where_clause:
|
|
549
|
+
raise ValueError("where_clause must be a non-empty string when provided")
|
|
550
|
+
query += f" WHERE {where_clause}"
|
|
551
|
+
|
|
552
|
+
if limit is not None:
|
|
553
|
+
if limit <= 0:
|
|
554
|
+
raise ValueError("limit must be a positive integer when provided")
|
|
555
|
+
query += f" LIMIT {limit}"
|
|
556
|
+
|
|
557
|
+
result = self.execute_dql(collection_id, query)
|
|
558
|
+
rows = result.get("rows", [])
|
|
559
|
+
agent_run_ids = [str(row[0]) for row in rows if row]
|
|
560
|
+
|
|
561
|
+
if result.get("truncated"):
|
|
562
|
+
logger.warning(
|
|
563
|
+
"DQL query truncated at applied limit %s; returning %s agent run IDs",
|
|
564
|
+
result.get("applied_limit"),
|
|
565
|
+
len(agent_run_ids),
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
return agent_run_ids
|
|
569
|
+
|
|
453
570
|
def list_agent_run_ids(self, collection_id: str) -> list[str]:
|
|
454
571
|
"""Get all agent run IDs for a collection.
|
|
455
572
|
|
docent/trace.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import atexit
|
|
2
2
|
import contextvars
|
|
3
3
|
import itertools
|
|
4
|
+
import json
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
import sys
|
|
@@ -12,7 +13,19 @@ from contextvars import ContextVar, Token
|
|
|
12
13
|
from datetime import datetime, timezone
|
|
13
14
|
from enum import Enum
|
|
14
15
|
from importlib.metadata import Distribution, distributions
|
|
15
|
-
from typing import
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
AsyncIterator,
|
|
19
|
+
Callable,
|
|
20
|
+
Dict,
|
|
21
|
+
Iterator,
|
|
22
|
+
List,
|
|
23
|
+
Mapping,
|
|
24
|
+
Optional,
|
|
25
|
+
Set,
|
|
26
|
+
Union,
|
|
27
|
+
cast,
|
|
28
|
+
)
|
|
16
29
|
|
|
17
30
|
import requests
|
|
18
31
|
from opentelemetry import trace
|
|
@@ -28,12 +41,23 @@ from opentelemetry.sdk.trace.export import (
|
|
|
28
41
|
SimpleSpanProcessor,
|
|
29
42
|
)
|
|
30
43
|
from opentelemetry.trace import Span
|
|
44
|
+
from requests import Response
|
|
31
45
|
|
|
32
46
|
logger = logging.getLogger(__name__)
|
|
33
47
|
|
|
34
48
|
# Default configuration
|
|
35
49
|
DEFAULT_ENDPOINT = "https://api.docent.transluce.org/rest/telemetry"
|
|
36
50
|
DEFAULT_COLLECTION_NAME = "default-collection-name"
|
|
51
|
+
ERROR_DETAIL_MAX_CHARS = 500
|
|
52
|
+
|
|
53
|
+
# Sentinel values for when tracing is disabled
|
|
54
|
+
DISABLED_AGENT_RUN_ID = "disabled"
|
|
55
|
+
DISABLED_TRANSCRIPT_ID = "disabled"
|
|
56
|
+
DISABLED_TRANSCRIPT_GROUP_ID = "disabled"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DocentTelemetryRequestError(RuntimeError):
|
|
60
|
+
"""Raised when the Docent telemetry backend rejects a client request."""
|
|
37
61
|
|
|
38
62
|
|
|
39
63
|
class Instruments(Enum):
|
|
@@ -129,6 +153,8 @@ class DocentTracer:
|
|
|
129
153
|
lambda: itertools.count(0)
|
|
130
154
|
)
|
|
131
155
|
self._transcript_counter_lock = threading.Lock()
|
|
156
|
+
self._transcript_group_states: dict[str, dict[str, Optional[str]]] = {}
|
|
157
|
+
self._transcript_group_state_lock = threading.Lock()
|
|
132
158
|
self._flush_lock = threading.Lock()
|
|
133
159
|
|
|
134
160
|
def get_current_agent_run_id(self) -> Optional[str]:
|
|
@@ -487,6 +513,24 @@ class DocentTracer:
|
|
|
487
513
|
"""Verify if the manager is properly initialized."""
|
|
488
514
|
return self._initialized
|
|
489
515
|
|
|
516
|
+
def get_disabled_agent_run_id(self, agent_run_id: Optional[str]) -> str:
|
|
517
|
+
"""Return sentinel value for agent run ID when tracing is disabled."""
|
|
518
|
+
if agent_run_id is None:
|
|
519
|
+
return DISABLED_AGENT_RUN_ID
|
|
520
|
+
return agent_run_id
|
|
521
|
+
|
|
522
|
+
def get_disabled_transcript_id(self, transcript_id: Optional[str]) -> str:
|
|
523
|
+
"""Return sentinel value for transcript ID when tracing is disabled."""
|
|
524
|
+
if transcript_id is None:
|
|
525
|
+
return DISABLED_TRANSCRIPT_ID
|
|
526
|
+
return transcript_id
|
|
527
|
+
|
|
528
|
+
def get_disabled_transcript_group_id(self, transcript_group_id: Optional[str]) -> str:
|
|
529
|
+
"""Return sentinel value for transcript group ID when tracing is disabled."""
|
|
530
|
+
if transcript_group_id is None:
|
|
531
|
+
return DISABLED_TRANSCRIPT_GROUP_ID
|
|
532
|
+
return transcript_group_id
|
|
533
|
+
|
|
490
534
|
@contextmanager
|
|
491
535
|
def agent_run_context(
|
|
492
536
|
self,
|
|
@@ -508,11 +552,8 @@ class DocentTracer:
|
|
|
508
552
|
Tuple of (agent_run_id, transcript_id)
|
|
509
553
|
"""
|
|
510
554
|
if self._disabled:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
agent_run_id = str(uuid.uuid4())
|
|
514
|
-
if transcript_id is None:
|
|
515
|
-
transcript_id = str(uuid.uuid4())
|
|
555
|
+
agent_run_id = self.get_disabled_agent_run_id(agent_run_id)
|
|
556
|
+
transcript_id = self.get_disabled_transcript_id(transcript_id)
|
|
516
557
|
yield agent_run_id, transcript_id
|
|
517
558
|
return
|
|
518
559
|
|
|
@@ -535,7 +576,7 @@ class DocentTracer:
|
|
|
535
576
|
try:
|
|
536
577
|
self.send_agent_run_metadata(agent_run_id, metadata)
|
|
537
578
|
except Exception as e:
|
|
538
|
-
logger.
|
|
579
|
+
logger.error(f"Failed sending agent run metadata: {e}")
|
|
539
580
|
|
|
540
581
|
yield agent_run_id, transcript_id
|
|
541
582
|
finally:
|
|
@@ -565,11 +606,8 @@ class DocentTracer:
|
|
|
565
606
|
Tuple of (agent_run_id, transcript_id)
|
|
566
607
|
"""
|
|
567
608
|
if self._disabled:
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
agent_run_id = str(uuid.uuid4())
|
|
571
|
-
if transcript_id is None:
|
|
572
|
-
transcript_id = str(uuid.uuid4())
|
|
609
|
+
agent_run_id = self.get_disabled_agent_run_id(agent_run_id)
|
|
610
|
+
transcript_id = self.get_disabled_transcript_id(transcript_id)
|
|
573
611
|
yield agent_run_id, transcript_id
|
|
574
612
|
return
|
|
575
613
|
|
|
@@ -615,15 +653,184 @@ class DocentTracer:
|
|
|
615
653
|
|
|
616
654
|
return headers
|
|
617
655
|
|
|
656
|
+
def _ensure_json_serializable_metadata(self, metadata: Dict[str, Any], context: str) -> None:
|
|
657
|
+
"""
|
|
658
|
+
Validate that metadata can be serialized to JSON before sending it to the backend.
|
|
659
|
+
"""
|
|
660
|
+
try:
|
|
661
|
+
json.dumps(metadata)
|
|
662
|
+
except (TypeError, ValueError) as exc:
|
|
663
|
+
raise TypeError(f"{context} metadata must be JSON serializable") from exc
|
|
664
|
+
offending_path = self._find_null_character_path(metadata)
|
|
665
|
+
if offending_path is not None:
|
|
666
|
+
raise ValueError(
|
|
667
|
+
f"{context} metadata cannot contain null characters (found at {offending_path}). "
|
|
668
|
+
"Remove or replace '\\u0000' before calling Docent tracing APIs."
|
|
669
|
+
)
|
|
670
|
+
|
|
618
671
|
def _post_json(self, path: str, data: Dict[str, Any]) -> None:
|
|
672
|
+
self._post_json_sync(path, data)
|
|
673
|
+
|
|
674
|
+
def _post_json_sync(self, path: str, data: Dict[str, Any]) -> None:
|
|
619
675
|
if not self._api_endpoint_base:
|
|
620
676
|
raise RuntimeError("API endpoint base is not configured")
|
|
621
677
|
url = f"{self._api_endpoint_base}{path}"
|
|
622
678
|
try:
|
|
623
679
|
resp = requests.post(url, json=data, headers=self._api_headers(), timeout=(10, 60))
|
|
624
680
|
resp.raise_for_status()
|
|
625
|
-
except requests.exceptions.RequestException as
|
|
626
|
-
|
|
681
|
+
except requests.exceptions.RequestException as exc:
|
|
682
|
+
message = self._format_request_exception(url, exc)
|
|
683
|
+
raise DocentTelemetryRequestError(message) from exc
|
|
684
|
+
|
|
685
|
+
def _format_request_exception(self, url: str, exc: requests.exceptions.RequestException) -> str:
|
|
686
|
+
response: Optional[Response] = getattr(exc, "response", None)
|
|
687
|
+
message_parts: List[str] = [f"Failed POST {url}"]
|
|
688
|
+
suggestion: Optional[str]
|
|
689
|
+
|
|
690
|
+
if response is not None:
|
|
691
|
+
status_phrase = f"HTTP {response.status_code}"
|
|
692
|
+
if response.reason:
|
|
693
|
+
status_phrase = f"{status_phrase} {response.reason}"
|
|
694
|
+
message_parts.append(f"({status_phrase})")
|
|
695
|
+
|
|
696
|
+
detail = self._extract_response_detail(response)
|
|
697
|
+
if detail:
|
|
698
|
+
message_parts.append(f"- Backend detail: {detail}")
|
|
699
|
+
|
|
700
|
+
request_id = response.headers.get("x-request-id")
|
|
701
|
+
if request_id:
|
|
702
|
+
message_parts.append(f"(request-id: {request_id})")
|
|
703
|
+
|
|
704
|
+
suggestion = self._suggest_fix_for_status(response.status_code)
|
|
705
|
+
else:
|
|
706
|
+
message_parts.append(f"- {exc}")
|
|
707
|
+
suggestion = self._suggest_fix_for_status(None)
|
|
708
|
+
|
|
709
|
+
if suggestion:
|
|
710
|
+
message_parts.append(suggestion)
|
|
711
|
+
|
|
712
|
+
return " ".join(part for part in message_parts if part)
|
|
713
|
+
|
|
714
|
+
def _extract_response_detail(self, response: Response) -> Optional[str]:
|
|
715
|
+
try:
|
|
716
|
+
body = response.json()
|
|
717
|
+
except ValueError:
|
|
718
|
+
text = response.text.strip()
|
|
719
|
+
if not text:
|
|
720
|
+
return None
|
|
721
|
+
normalized = " ".join(text.split())
|
|
722
|
+
return self._truncate_error_message(normalized)
|
|
723
|
+
|
|
724
|
+
if isinstance(body, dict):
|
|
725
|
+
typed_body = cast(Dict[str, Any], body)
|
|
726
|
+
structured_message = self._structured_detail_message(typed_body)
|
|
727
|
+
if structured_message:
|
|
728
|
+
return self._truncate_error_message(structured_message)
|
|
729
|
+
return self._truncate_error_message(self._normalize_error_value(typed_body))
|
|
730
|
+
|
|
731
|
+
return self._truncate_error_message(self._normalize_error_value(body))
|
|
732
|
+
|
|
733
|
+
def _structured_detail_message(self, data: Dict[str, Any]) -> Optional[str]:
|
|
734
|
+
for key in ("detail", "message", "error"):
|
|
735
|
+
if key in data:
|
|
736
|
+
structured_value = self._structured_detail_value(data[key])
|
|
737
|
+
if structured_value:
|
|
738
|
+
return structured_value
|
|
739
|
+
return self._structured_detail_value(data)
|
|
740
|
+
|
|
741
|
+
def _structured_detail_value(self, value: Any) -> Optional[str]:
|
|
742
|
+
if isinstance(value, Mapping):
|
|
743
|
+
mapping_value = cast(Mapping[str, Any], value)
|
|
744
|
+
message = mapping_value.get("message")
|
|
745
|
+
hint = mapping_value.get("hint")
|
|
746
|
+
error_code = mapping_value.get("error_code")
|
|
747
|
+
request_id = mapping_value.get("request_id")
|
|
748
|
+
fallback_detail = mapping_value.get("detail")
|
|
749
|
+
|
|
750
|
+
parts: List[str] = []
|
|
751
|
+
if isinstance(message, str) and message.strip():
|
|
752
|
+
parts.append(message.strip())
|
|
753
|
+
elif isinstance(fallback_detail, str) and fallback_detail.strip():
|
|
754
|
+
parts.append(fallback_detail.strip())
|
|
755
|
+
|
|
756
|
+
if isinstance(hint, str) and hint.strip():
|
|
757
|
+
parts.append(f"(hint: {hint.strip()})")
|
|
758
|
+
if isinstance(error_code, str) and error_code.strip():
|
|
759
|
+
parts.append(f"[code: {error_code.strip()}]")
|
|
760
|
+
if isinstance(request_id, str) and request_id.strip():
|
|
761
|
+
parts.append(f"(request-id: {request_id.strip()})")
|
|
762
|
+
|
|
763
|
+
return " ".join(parts) if parts else None
|
|
764
|
+
|
|
765
|
+
if isinstance(value, str) and value.strip():
|
|
766
|
+
return value.strip()
|
|
767
|
+
|
|
768
|
+
return None
|
|
769
|
+
|
|
770
|
+
def _normalize_error_value(self, value: Any) -> str:
|
|
771
|
+
if isinstance(value, str):
|
|
772
|
+
return " ".join(value.split())
|
|
773
|
+
|
|
774
|
+
try:
|
|
775
|
+
serialized = json.dumps(value)
|
|
776
|
+
except (TypeError, ValueError):
|
|
777
|
+
serialized = str(value)
|
|
778
|
+
|
|
779
|
+
return " ".join(serialized.split())
|
|
780
|
+
|
|
781
|
+
def _truncate_error_message(self, message: str) -> str:
|
|
782
|
+
message = message.strip()
|
|
783
|
+
if len(message) <= ERROR_DETAIL_MAX_CHARS:
|
|
784
|
+
return message
|
|
785
|
+
return f"{message[:ERROR_DETAIL_MAX_CHARS]}..."
|
|
786
|
+
|
|
787
|
+
def _suggest_fix_for_status(self, status_code: Optional[int]) -> Optional[str]:
|
|
788
|
+
if status_code in (401, 403):
|
|
789
|
+
return (
|
|
790
|
+
"Verify that the Authorization header or DOCENT_API_KEY grants write access to the "
|
|
791
|
+
"target collection."
|
|
792
|
+
)
|
|
793
|
+
if status_code == 404:
|
|
794
|
+
return (
|
|
795
|
+
"Ensure the tracing endpoint passed to initialize_tracing matches the Docent server's "
|
|
796
|
+
"/rest/telemetry route."
|
|
797
|
+
)
|
|
798
|
+
if status_code in (400, 422):
|
|
799
|
+
return (
|
|
800
|
+
"Confirm the payload includes collection_id, agent_run_id, metadata, and timestamp in "
|
|
801
|
+
"the expected format."
|
|
802
|
+
)
|
|
803
|
+
if status_code and status_code >= 500:
|
|
804
|
+
return "Inspect the Docent backend logs for the referenced request."
|
|
805
|
+
if status_code is None:
|
|
806
|
+
return "Confirm the Docent telemetry endpoint is reachable from this process."
|
|
807
|
+
return None
|
|
808
|
+
|
|
809
|
+
def _find_null_character_path(self, value: Any, path: str = "") -> Optional[str]:
|
|
810
|
+
"""Backend rejects NUL bytes, so detect them before we send metadata to the backend."""
|
|
811
|
+
return None
|
|
812
|
+
if isinstance(value, str):
|
|
813
|
+
if "\x00" in value or "\\u0000" in value or "\\x00" in value:
|
|
814
|
+
return path or "<root>"
|
|
815
|
+
return None
|
|
816
|
+
|
|
817
|
+
if isinstance(value, dict):
|
|
818
|
+
for key, item in value.items():
|
|
819
|
+
next_path = f"{path}.{key}" if path else str(key)
|
|
820
|
+
result = self._find_null_character_path(item, next_path)
|
|
821
|
+
if result:
|
|
822
|
+
return result
|
|
823
|
+
return None
|
|
824
|
+
|
|
825
|
+
if isinstance(value, (list, tuple)):
|
|
826
|
+
for index, item in enumerate(value):
|
|
827
|
+
next_path = f"{path}[{index}]" if path else f"[{index}]"
|
|
828
|
+
result = self._find_null_character_path(item, next_path)
|
|
829
|
+
if result:
|
|
830
|
+
return result
|
|
831
|
+
return None
|
|
832
|
+
|
|
833
|
+
return None
|
|
627
834
|
|
|
628
835
|
def send_agent_run_score(
|
|
629
836
|
self,
|
|
@@ -660,6 +867,8 @@ class DocentTracer:
|
|
|
660
867
|
if self._disabled:
|
|
661
868
|
return
|
|
662
869
|
|
|
870
|
+
self._ensure_json_serializable_metadata(metadata, "Agent run")
|
|
871
|
+
|
|
663
872
|
collection_id = self.collection_id
|
|
664
873
|
payload: Dict[str, Any] = {
|
|
665
874
|
"collection_id": collection_id,
|
|
@@ -705,6 +914,7 @@ class DocentTracer:
|
|
|
705
914
|
if transcript_group_id is not None:
|
|
706
915
|
payload["transcript_group_id"] = transcript_group_id
|
|
707
916
|
if metadata is not None:
|
|
917
|
+
self._ensure_json_serializable_metadata(metadata, "Transcript")
|
|
708
918
|
payload["metadata"] = metadata
|
|
709
919
|
|
|
710
920
|
self._post_json("/v1/transcript-metadata", payload)
|
|
@@ -756,9 +966,7 @@ class DocentTracer:
|
|
|
756
966
|
The transcript ID
|
|
757
967
|
"""
|
|
758
968
|
if self._disabled:
|
|
759
|
-
|
|
760
|
-
if transcript_id is None:
|
|
761
|
-
transcript_id = str(uuid.uuid4())
|
|
969
|
+
transcript_id = self.get_disabled_transcript_id(transcript_id)
|
|
762
970
|
yield transcript_id
|
|
763
971
|
return
|
|
764
972
|
|
|
@@ -788,7 +996,7 @@ class DocentTracer:
|
|
|
788
996
|
transcript_id, name, description, transcript_group_id, metadata
|
|
789
997
|
)
|
|
790
998
|
except Exception as e:
|
|
791
|
-
logger.
|
|
999
|
+
logger.error(f"Failed sending transcript data: {e}")
|
|
792
1000
|
|
|
793
1001
|
yield transcript_id
|
|
794
1002
|
finally:
|
|
@@ -818,9 +1026,7 @@ class DocentTracer:
|
|
|
818
1026
|
The transcript ID
|
|
819
1027
|
"""
|
|
820
1028
|
if self._disabled:
|
|
821
|
-
|
|
822
|
-
if transcript_id is None:
|
|
823
|
-
transcript_id = str(uuid.uuid4())
|
|
1029
|
+
transcript_id = self.get_disabled_transcript_id(transcript_id)
|
|
824
1030
|
yield transcript_id
|
|
825
1031
|
return
|
|
826
1032
|
|
|
@@ -850,7 +1056,7 @@ class DocentTracer:
|
|
|
850
1056
|
transcript_id, name, description, transcript_group_id, metadata
|
|
851
1057
|
)
|
|
852
1058
|
except Exception as e:
|
|
853
|
-
logger.
|
|
1059
|
+
logger.error(f"Failed sending transcript data: {e}")
|
|
854
1060
|
|
|
855
1061
|
yield transcript_id
|
|
856
1062
|
finally:
|
|
@@ -888,6 +1094,27 @@ class DocentTracer:
|
|
|
888
1094
|
)
|
|
889
1095
|
return
|
|
890
1096
|
|
|
1097
|
+
with self._transcript_group_state_lock:
|
|
1098
|
+
state: dict[str, Optional[str]] = self._transcript_group_states.setdefault(
|
|
1099
|
+
transcript_group_id, {}
|
|
1100
|
+
)
|
|
1101
|
+
final_name: Optional[str] = name if name is not None else state.get("name")
|
|
1102
|
+
final_description: Optional[str] = (
|
|
1103
|
+
description if description is not None else state.get("description")
|
|
1104
|
+
)
|
|
1105
|
+
final_parent_transcript_group_id: Optional[str] = (
|
|
1106
|
+
parent_transcript_group_id
|
|
1107
|
+
if parent_transcript_group_id is not None
|
|
1108
|
+
else state.get("parent_transcript_group_id")
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
if final_name is not None:
|
|
1112
|
+
state["name"] = final_name
|
|
1113
|
+
if final_description is not None:
|
|
1114
|
+
state["description"] = final_description
|
|
1115
|
+
if final_parent_transcript_group_id is not None:
|
|
1116
|
+
state["parent_transcript_group_id"] = final_parent_transcript_group_id
|
|
1117
|
+
|
|
891
1118
|
payload: Dict[str, Any] = {
|
|
892
1119
|
"collection_id": collection_id,
|
|
893
1120
|
"transcript_group_id": transcript_group_id,
|
|
@@ -895,13 +1122,14 @@ class DocentTracer:
|
|
|
895
1122
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
896
1123
|
}
|
|
897
1124
|
|
|
898
|
-
if
|
|
899
|
-
payload["name"] =
|
|
900
|
-
if
|
|
901
|
-
payload["description"] =
|
|
902
|
-
if
|
|
903
|
-
payload["parent_transcript_group_id"] =
|
|
1125
|
+
if final_name is not None:
|
|
1126
|
+
payload["name"] = final_name
|
|
1127
|
+
if final_description is not None:
|
|
1128
|
+
payload["description"] = final_description
|
|
1129
|
+
if final_parent_transcript_group_id is not None:
|
|
1130
|
+
payload["parent_transcript_group_id"] = final_parent_transcript_group_id
|
|
904
1131
|
if metadata is not None:
|
|
1132
|
+
self._ensure_json_serializable_metadata(metadata, "Transcript group")
|
|
905
1133
|
payload["metadata"] = metadata
|
|
906
1134
|
|
|
907
1135
|
self._post_json("/v1/transcript-group-metadata", payload)
|
|
@@ -929,9 +1157,7 @@ class DocentTracer:
|
|
|
929
1157
|
The transcript group ID
|
|
930
1158
|
"""
|
|
931
1159
|
if self._disabled:
|
|
932
|
-
|
|
933
|
-
if transcript_group_id is None:
|
|
934
|
-
transcript_group_id = str(uuid.uuid4())
|
|
1160
|
+
transcript_group_id = self.get_disabled_transcript_group_id(transcript_group_id)
|
|
935
1161
|
yield transcript_group_id
|
|
936
1162
|
return
|
|
937
1163
|
|
|
@@ -963,7 +1189,7 @@ class DocentTracer:
|
|
|
963
1189
|
transcript_group_id, name, description, parent_transcript_group_id, metadata
|
|
964
1190
|
)
|
|
965
1191
|
except Exception as e:
|
|
966
|
-
logger.
|
|
1192
|
+
logger.error(f"Failed sending transcript group data: {e}")
|
|
967
1193
|
|
|
968
1194
|
yield transcript_group_id
|
|
969
1195
|
finally:
|
|
@@ -993,9 +1219,7 @@ class DocentTracer:
|
|
|
993
1219
|
The transcript group ID
|
|
994
1220
|
"""
|
|
995
1221
|
if self._disabled:
|
|
996
|
-
|
|
997
|
-
if transcript_group_id is None:
|
|
998
|
-
transcript_group_id = str(uuid.uuid4())
|
|
1222
|
+
transcript_group_id = self.get_disabled_transcript_group_id(transcript_group_id)
|
|
999
1223
|
yield transcript_group_id
|
|
1000
1224
|
return
|
|
1001
1225
|
|
|
@@ -1027,7 +1251,7 @@ class DocentTracer:
|
|
|
1027
1251
|
transcript_group_id, name, description, parent_transcript_group_id, metadata
|
|
1028
1252
|
)
|
|
1029
1253
|
except Exception as e:
|
|
1030
|
-
logger.
|
|
1254
|
+
logger.error(f"Failed sending transcript group data: {e}")
|
|
1031
1255
|
|
|
1032
1256
|
yield transcript_group_id
|
|
1033
1257
|
finally:
|
|
@@ -1231,28 +1455,33 @@ def agent_run_metadata(metadata: Dict[str, Any]) -> None:
|
|
|
1231
1455
|
|
|
1232
1456
|
tracer.send_agent_run_metadata(agent_run_id, metadata)
|
|
1233
1457
|
except Exception as e:
|
|
1234
|
-
logger.error(f"Failed to send metadata: {e}")
|
|
1458
|
+
logger.error(f"Failed to send agent run metadata: {e}")
|
|
1235
1459
|
|
|
1236
1460
|
|
|
1237
1461
|
def transcript_metadata(
|
|
1462
|
+
metadata: Dict[str, Any],
|
|
1463
|
+
*,
|
|
1238
1464
|
name: Optional[str] = None,
|
|
1239
1465
|
description: Optional[str] = None,
|
|
1240
1466
|
transcript_group_id: Optional[str] = None,
|
|
1241
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
1242
1467
|
) -> None:
|
|
1243
1468
|
"""
|
|
1244
1469
|
Send transcript metadata directly to the backend for the current transcript.
|
|
1245
1470
|
|
|
1246
1471
|
Args:
|
|
1472
|
+
metadata: Dictionary of metadata to attach to the current transcript (required)
|
|
1247
1473
|
name: Optional transcript name
|
|
1248
1474
|
description: Optional transcript description
|
|
1249
|
-
|
|
1250
|
-
metadata: Optional metadata to send
|
|
1475
|
+
transcript_group_id: Optional transcript group ID to associate with
|
|
1251
1476
|
|
|
1252
1477
|
Example:
|
|
1253
|
-
transcript_metadata(
|
|
1254
|
-
transcript_metadata(
|
|
1255
|
-
transcript_metadata(
|
|
1478
|
+
transcript_metadata({"user": "John", "model": "gpt-4"})
|
|
1479
|
+
transcript_metadata({"env": "prod"}, name="data_processing")
|
|
1480
|
+
transcript_metadata(
|
|
1481
|
+
{"team": "search"},
|
|
1482
|
+
name="validation",
|
|
1483
|
+
transcript_group_id="group-123",
|
|
1484
|
+
)
|
|
1256
1485
|
"""
|
|
1257
1486
|
try:
|
|
1258
1487
|
tracer = get_tracer()
|
|
@@ -1271,23 +1500,29 @@ def transcript_metadata(
|
|
|
1271
1500
|
|
|
1272
1501
|
|
|
1273
1502
|
def transcript_group_metadata(
|
|
1503
|
+
metadata: Dict[str, Any],
|
|
1504
|
+
*,
|
|
1274
1505
|
name: Optional[str] = None,
|
|
1275
1506
|
description: Optional[str] = None,
|
|
1276
1507
|
parent_transcript_group_id: Optional[str] = None,
|
|
1277
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
1278
1508
|
) -> None:
|
|
1279
1509
|
"""
|
|
1280
1510
|
Send transcript group metadata directly to the backend for the current transcript group.
|
|
1281
1511
|
|
|
1282
1512
|
Args:
|
|
1513
|
+
metadata: Dictionary of metadata to attach to the current transcript group (required)
|
|
1283
1514
|
name: Optional transcript group name
|
|
1284
1515
|
description: Optional transcript group description
|
|
1285
1516
|
parent_transcript_group_id: Optional parent transcript group ID
|
|
1286
|
-
metadata: Optional metadata to send
|
|
1287
1517
|
|
|
1288
1518
|
Example:
|
|
1289
|
-
transcript_group_metadata(
|
|
1290
|
-
transcript_group_metadata(
|
|
1519
|
+
transcript_group_metadata({"team": "search", "env": "prod"})
|
|
1520
|
+
transcript_group_metadata({"env": "prod"}, name="pipeline")
|
|
1521
|
+
transcript_group_metadata(
|
|
1522
|
+
{"team": "search"},
|
|
1523
|
+
name="pipeline",
|
|
1524
|
+
parent_transcript_group_id="root-group",
|
|
1525
|
+
)
|
|
1291
1526
|
"""
|
|
1292
1527
|
try:
|
|
1293
1528
|
tracer = get_tracer()
|
|
@@ -1324,6 +1559,11 @@ class AgentRunContext:
|
|
|
1324
1559
|
|
|
1325
1560
|
def __enter__(self) -> tuple[str, str]:
|
|
1326
1561
|
"""Sync context manager entry."""
|
|
1562
|
+
if is_disabled():
|
|
1563
|
+
tracer = get_tracer()
|
|
1564
|
+
self.agent_run_id = tracer.get_disabled_agent_run_id(self.agent_run_id)
|
|
1565
|
+
self.transcript_id = tracer.get_disabled_transcript_id(self.transcript_id)
|
|
1566
|
+
return self.agent_run_id, self.transcript_id
|
|
1327
1567
|
self._sync_context = get_tracer().agent_run_context(
|
|
1328
1568
|
self.agent_run_id, self.transcript_id, metadata=self.metadata, **self.attributes
|
|
1329
1569
|
)
|
|
@@ -1336,6 +1576,11 @@ class AgentRunContext:
|
|
|
1336
1576
|
|
|
1337
1577
|
async def __aenter__(self) -> tuple[str, str]:
|
|
1338
1578
|
"""Async context manager entry."""
|
|
1579
|
+
if is_disabled():
|
|
1580
|
+
tracer = get_tracer()
|
|
1581
|
+
self.agent_run_id = tracer.get_disabled_agent_run_id(self.agent_run_id)
|
|
1582
|
+
self.transcript_id = tracer.get_disabled_transcript_id(self.transcript_id)
|
|
1583
|
+
return self.agent_run_id, self.transcript_id
|
|
1339
1584
|
self._async_context = get_tracer().async_agent_run_context(
|
|
1340
1585
|
self.agent_run_id, self.transcript_id, metadata=self.metadata, **self.attributes
|
|
1341
1586
|
)
|
|
@@ -1476,6 +1721,10 @@ class TranscriptContext:
|
|
|
1476
1721
|
|
|
1477
1722
|
def __enter__(self) -> str:
|
|
1478
1723
|
"""Sync context manager entry."""
|
|
1724
|
+
if is_disabled():
|
|
1725
|
+
tracer = get_tracer()
|
|
1726
|
+
self.transcript_id = tracer.get_disabled_transcript_id(self.transcript_id)
|
|
1727
|
+
return self.transcript_id
|
|
1479
1728
|
self._sync_context = get_tracer().transcript_context(
|
|
1480
1729
|
name=self.name,
|
|
1481
1730
|
transcript_id=self.transcript_id,
|
|
@@ -1492,6 +1741,10 @@ class TranscriptContext:
|
|
|
1492
1741
|
|
|
1493
1742
|
async def __aenter__(self) -> str:
|
|
1494
1743
|
"""Async context manager entry."""
|
|
1744
|
+
if is_disabled():
|
|
1745
|
+
tracer = get_tracer()
|
|
1746
|
+
self.transcript_id = tracer.get_disabled_transcript_id(self.transcript_id)
|
|
1747
|
+
return self.transcript_id
|
|
1495
1748
|
self._async_context = get_tracer().async_transcript_context(
|
|
1496
1749
|
name=self.name,
|
|
1497
1750
|
transcript_id=self.transcript_id,
|
|
@@ -1653,6 +1906,12 @@ class TranscriptGroupContext:
|
|
|
1653
1906
|
|
|
1654
1907
|
def __enter__(self) -> str:
|
|
1655
1908
|
"""Sync context manager entry."""
|
|
1909
|
+
if is_disabled():
|
|
1910
|
+
tracer = get_tracer()
|
|
1911
|
+
self.transcript_group_id = tracer.get_disabled_transcript_group_id(
|
|
1912
|
+
self.transcript_group_id
|
|
1913
|
+
)
|
|
1914
|
+
return self.transcript_group_id
|
|
1656
1915
|
self._sync_context = get_tracer().transcript_group_context(
|
|
1657
1916
|
name=self.name,
|
|
1658
1917
|
transcript_group_id=self.transcript_group_id,
|
|
@@ -1669,6 +1928,12 @@ class TranscriptGroupContext:
|
|
|
1669
1928
|
|
|
1670
1929
|
async def __aenter__(self) -> str:
|
|
1671
1930
|
"""Async context manager entry."""
|
|
1931
|
+
if is_disabled():
|
|
1932
|
+
tracer = get_tracer()
|
|
1933
|
+
self.transcript_group_id = tracer.get_disabled_transcript_group_id(
|
|
1934
|
+
self.transcript_group_id
|
|
1935
|
+
)
|
|
1936
|
+
return self.transcript_group_id
|
|
1672
1937
|
self._async_context = get_tracer().async_transcript_group_context(
|
|
1673
1938
|
name=self.name,
|
|
1674
1939
|
transcript_group_id=self.transcript_group_id,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
docent/__init__.py,sha256=fuhETwJPcesiB76Zxa64HBJxeaaTyRalIH-fs77TWsU,112
|
|
2
2
|
docent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
docent/trace.py,sha256=
|
|
3
|
+
docent/trace.py,sha256=J05K9MykKGkeBjh9idTOPtiMA5_h0AdL8zRR-yKu5Yg,79525
|
|
4
4
|
docent/trace_temp.py,sha256=Z0lAPwVzXjFvxpiU-CuvfWIslq9Q4alNkZMoQ77Xudk,40711
|
|
5
5
|
docent/_llm_util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
docent/_llm_util/llm_cache.py,sha256=nGrvfFikFbEnfmzZRvWvZ60gfVSTvW1iC8-ciCXwbAk,6430
|
|
7
|
-
docent/_llm_util/llm_svc.py,sha256=
|
|
8
|
-
docent/_llm_util/model_registry.py,sha256=
|
|
7
|
+
docent/_llm_util/llm_svc.py,sha256=LqrI8DdhqOmkcz3tsyzSlhrJv2gA4-0DE105WLys6sw,18156
|
|
8
|
+
docent/_llm_util/model_registry.py,sha256=CdOi4g3eZCBQjLQDNQtprXpby0Ldc6AIRvLAD6Ajc90,3502
|
|
9
9
|
docent/_llm_util/data_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
docent/_llm_util/data_models/exceptions.py,sha256=IW4BVMVp8r5TufNXyrhy3acgwJiQQQPQjB9VA4RVXw8,1489
|
|
11
|
-
docent/_llm_util/data_models/llm_output.py,sha256=
|
|
11
|
+
docent/_llm_util/data_models/llm_output.py,sha256=UCYewoXN72skigN_fm414TzQol1KxmVbQGwgGVROE_4,10602
|
|
12
12
|
docent/_llm_util/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
docent/_llm_util/providers/anthropic.py,sha256
|
|
13
|
+
docent/_llm_util/providers/anthropic.py,sha256=M5ryu_lGKZ3PDJLSCV07zsiAvEeAyEgZ13rbzOeutS8,18765
|
|
14
14
|
docent/_llm_util/providers/common.py,sha256=dgcTuU4XkCKoAaM48UW8zMgRYUzj7TDBhvWqtnxBO7g,1166
|
|
15
15
|
docent/_llm_util/providers/google.py,sha256=2D9mDgenZW0pt0_V7koX-aoZzpl8jo8xE5EWOLK7I0k,20314
|
|
16
16
|
docent/_llm_util/providers/openai.py,sha256=4niQV9CNaJ-iiEwYG0BSFxCwcsCAWZz0JuUs4wBKu9M,25904
|
|
@@ -21,7 +21,7 @@ docent/_log_util/__init__.py,sha256=3HXXrxrSm8PxwG4llotrCnSnp7GuroK1FNHsdg6f7aE,
|
|
|
21
21
|
docent/_log_util/logger.py,sha256=kwM0yRW1IJd6-XTorjWn48B4l8qvD2ZM6VDjY5eskQI,4422
|
|
22
22
|
docent/data_models/__init__.py,sha256=vEcFppE6wtKFp37KF_hUv00Ncn6fK_qUbVGZE5ltz-o,383
|
|
23
23
|
docent/data_models/_tiktoken_util.py,sha256=hC0EDDWItv5-0cONBnHWgZtQOflDU7ZNEhXPFo4DvPc,3057
|
|
24
|
-
docent/data_models/agent_run.py,sha256=
|
|
24
|
+
docent/data_models/agent_run.py,sha256=D9KVGVChm2q4B_cruVYtQH-5Xk31ZxTYhoZn6RGrc_o,19392
|
|
25
25
|
docent/data_models/citation.py,sha256=2_M1-_olVOJtjCGGFx1GIwGYWl0ILHxRsW8-EFDS9j0,7844
|
|
26
26
|
docent/data_models/judge.py,sha256=BOKAfZmNoLPclJNz_b7NvH8G8FzfR7kc6OpIv91GMDQ,336
|
|
27
27
|
docent/data_models/metadata_util.py,sha256=E-EClAP5vVm9xbfTlPSz0tUyCalOfN9Jujd6JGoRnBg,487
|
|
@@ -37,7 +37,7 @@ docent/data_models/chat/tool.py,sha256=MMglNHzkwHqUoK0xDWqs2FtelPsgHqwVpGpI1F8KZ
|
|
|
37
37
|
docent/judges/__init__.py,sha256=aTsQ2mIQnZt8HEMau02KrEA4m5w-lGC3U9Dirkj3to4,500
|
|
38
38
|
docent/judges/analysis.py,sha256=bn7XIT7mj77LjFHMh1PqjALknq3nN-fRXqgg8cfJF8o,2486
|
|
39
39
|
docent/judges/impl.py,sha256=JOq2tEBTqNbWIG2gRuI8OmEW2dHdx7nfnJnHeGwdyOk,24035
|
|
40
|
-
docent/judges/runner.py,sha256=
|
|
40
|
+
docent/judges/runner.py,sha256=k1OyEPEhAUiRiJpOAwbaAqsPHsKfseD7URXGqhVI974,4496
|
|
41
41
|
docent/judges/stats.py,sha256=zejJle583xHG2G3gcYHiWcHoIOkeKwpSkl8lfeKQhFs,7805
|
|
42
42
|
docent/judges/types.py,sha256=goNaKs3PF5wMHWLnFerYCEjUjPR0IVI9cVrxCK2TfjI,11539
|
|
43
43
|
docent/judges/util/forgiving_json.py,sha256=zSh0LF3UVHdSjuMNvEiqUmSxpxPaqK1rSLiI6KCNihg,3549
|
|
@@ -52,8 +52,8 @@ docent/samples/log.eval,sha256=orrW__9WBfANq7NwKsPSq9oTsQRcG6KohG5tMr_X_XY,39770
|
|
|
52
52
|
docent/samples/tb_airline.json,sha256=eR2jFFRtOw06xqbEglh6-dPewjifOk-cuxJq67Dtu5I,47028
|
|
53
53
|
docent/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
54
|
docent/sdk/agent_run_writer.py,sha256=0AWdxejoqZyuj9JSA39WlEwGcMSYTWNqnzIuluySY-M,11043
|
|
55
|
-
docent/sdk/client.py,sha256=
|
|
56
|
-
docent_python-0.1.
|
|
57
|
-
docent_python-0.1.
|
|
58
|
-
docent_python-0.1.
|
|
59
|
-
docent_python-0.1.
|
|
55
|
+
docent/sdk/client.py,sha256=BeW9nMlCVOyLN8o7S81ePX0ngFrmzJHMxa8YbundKgs,24321
|
|
56
|
+
docent_python-0.1.28a0.dist-info/METADATA,sha256=7uIPnlYJFyZpE6xCEXwz4OlGD-_br4B4GY6DZ0uj7i8,1351
|
|
57
|
+
docent_python-0.1.28a0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
58
|
+
docent_python-0.1.28a0.dist-info/licenses/LICENSE.md,sha256=QIMv2UiT6MppRasso4ymaA0w7ltkqmlL0HCt8CLD7Rc,580
|
|
59
|
+
docent_python-0.1.28a0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|