docent-python 0.1.25a0__py3-none-any.whl → 0.1.26a0__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.
- docent/_llm_util/data_models/llm_output.py +8 -0
- docent/_llm_util/llm_svc.py +3 -3
- docent/trace.py +79 -2
- {docent_python-0.1.25a0.dist-info → docent_python-0.1.26a0.dist-info}/METADATA +1 -1
- {docent_python-0.1.25a0.dist-info → docent_python-0.1.26a0.dist-info}/RECORD +7 -7
- {docent_python-0.1.25a0.dist-info → docent_python-0.1.26a0.dist-info}/WHEEL +0 -0
- {docent_python-0.1.25a0.dist-info → docent_python-0.1.26a0.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
|
@@ -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
|
docent/trace.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import atexit
|
|
2
3
|
import contextvars
|
|
3
4
|
import itertools
|
|
5
|
+
import json
|
|
4
6
|
import logging
|
|
5
7
|
import os
|
|
6
8
|
import sys
|
|
7
9
|
import threading
|
|
8
10
|
import uuid
|
|
9
11
|
from collections import defaultdict
|
|
12
|
+
from concurrent.futures import Future, ThreadPoolExecutor
|
|
10
13
|
from contextlib import asynccontextmanager, contextmanager
|
|
11
14
|
from contextvars import ContextVar, Token
|
|
12
15
|
from datetime import datetime, timezone
|
|
@@ -132,6 +135,10 @@ class DocentTracer:
|
|
|
132
135
|
self._transcript_group_states: dict[str, dict[str, Optional[str]]] = {}
|
|
133
136
|
self._transcript_group_state_lock = threading.Lock()
|
|
134
137
|
self._flush_lock = threading.Lock()
|
|
138
|
+
self._http_executor: Optional[ThreadPoolExecutor] = None
|
|
139
|
+
self._http_executor_lock = threading.Lock()
|
|
140
|
+
self._pending_http_futures: Set[Future[Any]] = set()
|
|
141
|
+
self._pending_http_lock = threading.Lock()
|
|
135
142
|
|
|
136
143
|
def get_current_agent_run_id(self) -> Optional[str]:
|
|
137
144
|
"""
|
|
@@ -441,6 +448,12 @@ class DocentTracer:
|
|
|
441
448
|
try:
|
|
442
449
|
self.flush()
|
|
443
450
|
|
|
451
|
+
if self._http_executor:
|
|
452
|
+
self._http_executor.shutdown(wait=True)
|
|
453
|
+
self._http_executor = None
|
|
454
|
+
with self._pending_http_lock:
|
|
455
|
+
self._pending_http_futures.clear()
|
|
456
|
+
|
|
444
457
|
if self._tracer_provider:
|
|
445
458
|
self._tracer_provider.shutdown()
|
|
446
459
|
self._tracer_provider = None
|
|
@@ -471,6 +484,7 @@ class DocentTracer:
|
|
|
471
484
|
if hasattr(processor, "force_flush"):
|
|
472
485
|
logger.debug(f"Flushing span processor {i}")
|
|
473
486
|
processor.force_flush(timeout_millis=50)
|
|
487
|
+
self._wait_for_http_requests()
|
|
474
488
|
logger.debug("Span flush completed")
|
|
475
489
|
except Exception as e:
|
|
476
490
|
logger.error(f"Error during flush: {e}")
|
|
@@ -617,7 +631,66 @@ class DocentTracer:
|
|
|
617
631
|
|
|
618
632
|
return headers
|
|
619
633
|
|
|
620
|
-
def
|
|
634
|
+
def _get_http_executor(self) -> ThreadPoolExecutor:
|
|
635
|
+
with self._http_executor_lock:
|
|
636
|
+
if self._http_executor is None:
|
|
637
|
+
self._http_executor = ThreadPoolExecutor(
|
|
638
|
+
max_workers=4, thread_name_prefix="docent-http"
|
|
639
|
+
)
|
|
640
|
+
return self._http_executor
|
|
641
|
+
|
|
642
|
+
def _should_run_http_in_background(self) -> bool:
|
|
643
|
+
try:
|
|
644
|
+
loop = asyncio.get_running_loop()
|
|
645
|
+
except RuntimeError:
|
|
646
|
+
return False
|
|
647
|
+
return loop.is_running()
|
|
648
|
+
|
|
649
|
+
def _on_http_future_done(self, future: Future[Any]) -> None:
|
|
650
|
+
with self._pending_http_lock:
|
|
651
|
+
self._pending_http_futures.discard(future)
|
|
652
|
+
try:
|
|
653
|
+
future.result()
|
|
654
|
+
except Exception as exc: # pragma: no cover - defensive logging
|
|
655
|
+
logger.error(f"Background HTTP request failed: {exc}")
|
|
656
|
+
|
|
657
|
+
def _schedule_background_post(self, task: Callable[[], None]) -> None:
|
|
658
|
+
executor = self._get_http_executor()
|
|
659
|
+
future = executor.submit(task)
|
|
660
|
+
with self._pending_http_lock:
|
|
661
|
+
self._pending_http_futures.add(future)
|
|
662
|
+
future.add_done_callback(self._on_http_future_done)
|
|
663
|
+
|
|
664
|
+
def _wait_for_http_requests(self) -> None:
|
|
665
|
+
while True:
|
|
666
|
+
with self._pending_http_lock:
|
|
667
|
+
pending = list(self._pending_http_futures)
|
|
668
|
+
if not pending:
|
|
669
|
+
break
|
|
670
|
+
for future in pending:
|
|
671
|
+
try:
|
|
672
|
+
future.result()
|
|
673
|
+
except Exception as exc: # pragma: no cover - defensive logging
|
|
674
|
+
logger.error(f"Background HTTP request failed: {exc}")
|
|
675
|
+
|
|
676
|
+
def _ensure_json_serializable_metadata(self, metadata: Dict[str, Any], context: str) -> None:
|
|
677
|
+
"""
|
|
678
|
+
Validate that metadata can be serialized to JSON before sending it to the backend.
|
|
679
|
+
"""
|
|
680
|
+
try:
|
|
681
|
+
json.dumps(metadata)
|
|
682
|
+
except (TypeError, ValueError) as exc:
|
|
683
|
+
raise TypeError(f"{context} metadata must be JSON serializable") from exc
|
|
684
|
+
|
|
685
|
+
def _post_json(
|
|
686
|
+
self, path: str, data: Dict[str, Any], *, allow_background: bool = False
|
|
687
|
+
) -> None:
|
|
688
|
+
if allow_background and self._should_run_http_in_background():
|
|
689
|
+
self._schedule_background_post(lambda: self._post_json_sync(path, data))
|
|
690
|
+
return
|
|
691
|
+
self._post_json_sync(path, data)
|
|
692
|
+
|
|
693
|
+
def _post_json_sync(self, path: str, data: Dict[str, Any]) -> None:
|
|
621
694
|
if not self._api_endpoint_base:
|
|
622
695
|
raise RuntimeError("API endpoint base is not configured")
|
|
623
696
|
url = f"{self._api_endpoint_base}{path}"
|
|
@@ -662,6 +735,8 @@ class DocentTracer:
|
|
|
662
735
|
if self._disabled:
|
|
663
736
|
return
|
|
664
737
|
|
|
738
|
+
self._ensure_json_serializable_metadata(metadata, "Agent run")
|
|
739
|
+
|
|
665
740
|
collection_id = self.collection_id
|
|
666
741
|
payload: Dict[str, Any] = {
|
|
667
742
|
"collection_id": collection_id,
|
|
@@ -669,7 +744,7 @@ class DocentTracer:
|
|
|
669
744
|
"metadata": metadata,
|
|
670
745
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
671
746
|
}
|
|
672
|
-
self._post_json("/v1/agent-run-metadata", payload)
|
|
747
|
+
self._post_json("/v1/agent-run-metadata", payload, allow_background=True)
|
|
673
748
|
|
|
674
749
|
def send_transcript_metadata(
|
|
675
750
|
self,
|
|
@@ -707,6 +782,7 @@ class DocentTracer:
|
|
|
707
782
|
if transcript_group_id is not None:
|
|
708
783
|
payload["transcript_group_id"] = transcript_group_id
|
|
709
784
|
if metadata is not None:
|
|
785
|
+
self._ensure_json_serializable_metadata(metadata, "Transcript")
|
|
710
786
|
payload["metadata"] = metadata
|
|
711
787
|
|
|
712
788
|
self._post_json("/v1/transcript-metadata", payload)
|
|
@@ -925,6 +1001,7 @@ class DocentTracer:
|
|
|
925
1001
|
if final_parent_transcript_group_id is not None:
|
|
926
1002
|
payload["parent_transcript_group_id"] = final_parent_transcript_group_id
|
|
927
1003
|
if metadata is not None:
|
|
1004
|
+
self._ensure_json_serializable_metadata(metadata, "Transcript group")
|
|
928
1005
|
payload["metadata"] = metadata
|
|
929
1006
|
|
|
930
1007
|
self._post_json("/v1/transcript-group-metadata", payload)
|
|
@@ -1,14 +1,14 @@
|
|
|
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=3Z6xy4ZsP5S1_xrN6luwSsHfZlQL94qLZazJ3v-fzxQ,72953
|
|
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=
|
|
7
|
+
docent/_llm_util/llm_svc.py,sha256=WbLx8F0BfToL0PUIUKNWVwjAoGPE3AUCbVJLBIWTeWA,18109
|
|
8
8
|
docent/_llm_util/model_registry.py,sha256=8Y4VwrA2f2EX78cG1VBIBHVvT_p4qqBTdu9a9zJpfTo,3382
|
|
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
13
|
docent/_llm_util/providers/anthropic.py,sha256=-1oPd5FB4aFwKSmNvXzG8PVewjhgsogLRX1SCpnCxoA,18720
|
|
14
14
|
docent/_llm_util/providers/common.py,sha256=dgcTuU4XkCKoAaM48UW8zMgRYUzj7TDBhvWqtnxBO7g,1166
|
|
@@ -53,7 +53,7 @@ docent/samples/tb_airline.json,sha256=eR2jFFRtOw06xqbEglh6-dPewjifOk-cuxJq67Dtu5
|
|
|
53
53
|
docent/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
54
|
docent/sdk/agent_run_writer.py,sha256=0AWdxejoqZyuj9JSA39WlEwGcMSYTWNqnzIuluySY-M,11043
|
|
55
55
|
docent/sdk/client.py,sha256=aB_ILmzzK9JAC2kobtnp50stfINpSfNh54siaDlMEKc,19880
|
|
56
|
-
docent_python-0.1.
|
|
57
|
-
docent_python-0.1.
|
|
58
|
-
docent_python-0.1.
|
|
59
|
-
docent_python-0.1.
|
|
56
|
+
docent_python-0.1.26a0.dist-info/METADATA,sha256=TWwg2-VbK4CCWDDx36P4tnbExZw7ljYGoVMhWqfofZk,1351
|
|
57
|
+
docent_python-0.1.26a0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
58
|
+
docent_python-0.1.26a0.dist-info/licenses/LICENSE.md,sha256=QIMv2UiT6MppRasso4ymaA0w7ltkqmlL0HCt8CLD7Rc,580
|
|
59
|
+
docent_python-0.1.26a0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|