docent-python 0.1.16a0__py3-none-any.whl → 0.1.18a0__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/data_models/agent_run.py +11 -24
- docent/data_models/chat/message.py +3 -1
- docent/data_models/transcript.py +8 -67
- docent/sdk/agent_run_writer.py +47 -17
- {docent_python-0.1.16a0.dist-info → docent_python-0.1.18a0.dist-info}/METADATA +1 -1
- {docent_python-0.1.16a0.dist-info → docent_python-0.1.18a0.dist-info}/RECORD +8 -8
- {docent_python-0.1.16a0.dist-info → docent_python-0.1.18a0.dist-info}/WHEEL +0 -0
- {docent_python-0.1.16a0.dist-info → docent_python-0.1.18a0.dist-info}/licenses/LICENSE.md +0 -0
docent/data_models/agent_run.py
CHANGED
|
@@ -147,18 +147,12 @@ class AgentRun(BaseModel):
|
|
|
147
147
|
# Generate transcript strings using appropriate method
|
|
148
148
|
transcript_strs: list[str] = []
|
|
149
149
|
for i, t in enumerate(self.transcripts):
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
else:
|
|
157
|
-
transcript_content = t.to_str_with_token_limit(
|
|
158
|
-
token_limit=sys.maxsize,
|
|
159
|
-
transcript_idx=i,
|
|
160
|
-
agent_run_idx=None,
|
|
161
|
-
)[0]
|
|
150
|
+
transcript_content = t.to_str(
|
|
151
|
+
token_limit=sys.maxsize,
|
|
152
|
+
transcript_idx=i,
|
|
153
|
+
agent_run_idx=None,
|
|
154
|
+
use_action_units=not use_blocks,
|
|
155
|
+
)[0]
|
|
162
156
|
transcript_strs.append(f"<transcript>\n{transcript_content}\n</transcript>")
|
|
163
157
|
|
|
164
158
|
transcripts_str = "\n\n".join(transcript_strs)
|
|
@@ -207,23 +201,16 @@ class AgentRun(BaseModel):
|
|
|
207
201
|
), "Ranges without metadata should be a single message"
|
|
208
202
|
t = self.transcripts[msg_range.start]
|
|
209
203
|
if msg_range.num_tokens < token_limit - 50:
|
|
210
|
-
|
|
211
|
-
transcript = f"<transcript>\n{t.to_str_blocks_with_token_limit(token_limit=sys.maxsize)[0]}\n</transcript>"
|
|
212
|
-
else:
|
|
213
|
-
transcript = f"<transcript>\n{t.to_str_with_token_limit(token_limit=sys.maxsize)[0]}\n</transcript>"
|
|
204
|
+
transcript = f"<transcript>\n{t.to_str(token_limit=sys.maxsize, use_action_units=not use_blocks)[0]}\n</transcript>"
|
|
214
205
|
result = (
|
|
215
206
|
f"Here is a partial agent run for analysis purposes only:\n{transcript}"
|
|
216
207
|
)
|
|
217
208
|
results.append(result)
|
|
218
209
|
else:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
else:
|
|
224
|
-
transcript_fragments = t.to_str_with_token_limit(
|
|
225
|
-
token_limit=token_limit - 50,
|
|
226
|
-
)
|
|
210
|
+
transcript_fragments = t.to_str(
|
|
211
|
+
token_limit=token_limit - 50,
|
|
212
|
+
use_action_units=not use_blocks,
|
|
213
|
+
)
|
|
227
214
|
for fragment in transcript_fragments:
|
|
228
215
|
result = f"<transcript>\n{fragment}\n</transcript>"
|
|
229
216
|
result = (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from logging import getLogger
|
|
2
2
|
from typing import Annotated, Any, Literal
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, Discriminator
|
|
4
|
+
from pydantic import BaseModel, Discriminator, Field
|
|
5
5
|
|
|
6
6
|
from docent.data_models.chat.content import Content
|
|
7
7
|
from docent.data_models.chat.tool import ToolCall
|
|
@@ -17,11 +17,13 @@ class BaseChatMessage(BaseModel):
|
|
|
17
17
|
id: Optional unique identifier for the message.
|
|
18
18
|
content: The message content, either as a string or list of Content objects.
|
|
19
19
|
role: The role of the message sender (system, user, assistant, tool).
|
|
20
|
+
metadata: Additional structured metadata about the message.
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
id: str | None = None
|
|
23
24
|
content: str | list[Content]
|
|
24
25
|
role: Literal["system", "user", "assistant", "tool"]
|
|
26
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
25
27
|
|
|
26
28
|
@property
|
|
27
29
|
def text(self) -> str:
|
docent/data_models/transcript.py
CHANGED
|
@@ -72,6 +72,11 @@ def format_chat_message(
|
|
|
72
72
|
args = ", ".join([f"{k}={v}" for k, v in tool_call.arguments.items()])
|
|
73
73
|
cur_content += f"\n<tool call>\n{tool_call.function}({args})\n</tool call>"
|
|
74
74
|
|
|
75
|
+
if message.metadata:
|
|
76
|
+
metadata_yaml = yaml_dump_metadata(message.metadata)
|
|
77
|
+
if metadata_yaml is not None:
|
|
78
|
+
cur_content += f"\n<|message metadata|>\n{metadata_yaml}\n</|message metadata|>"
|
|
79
|
+
|
|
75
80
|
return TRANSCRIPT_BLOCK_TEMPLATE.format(
|
|
76
81
|
index_label=index_label, role=message.role, content=cur_content
|
|
77
82
|
)
|
|
@@ -301,20 +306,6 @@ class Transcript(BaseModel):
|
|
|
301
306
|
self.messages = messages
|
|
302
307
|
self._units_of_action = self._compute_units_of_action()
|
|
303
308
|
|
|
304
|
-
def to_str(
|
|
305
|
-
self,
|
|
306
|
-
transcript_idx: int = 0,
|
|
307
|
-
agent_run_idx: int | None = None,
|
|
308
|
-
highlight_action_unit: int | None = None,
|
|
309
|
-
) -> str:
|
|
310
|
-
return self._to_str_with_token_limit_impl(
|
|
311
|
-
token_limit=sys.maxsize,
|
|
312
|
-
transcript_idx=transcript_idx,
|
|
313
|
-
agent_run_idx=agent_run_idx,
|
|
314
|
-
use_action_units=True,
|
|
315
|
-
highlight_action_unit=highlight_action_unit,
|
|
316
|
-
)[0]
|
|
317
|
-
|
|
318
309
|
def _generate_formatted_blocks(
|
|
319
310
|
self,
|
|
320
311
|
transcript_idx: int = 0,
|
|
@@ -379,9 +370,9 @@ class Transcript(BaseModel):
|
|
|
379
370
|
|
|
380
371
|
return blocks
|
|
381
372
|
|
|
382
|
-
def
|
|
373
|
+
def to_str(
|
|
383
374
|
self,
|
|
384
|
-
token_limit: int,
|
|
375
|
+
token_limit: int = sys.maxsize,
|
|
385
376
|
transcript_idx: int = 0,
|
|
386
377
|
agent_run_idx: int | None = None,
|
|
387
378
|
use_action_units: bool = True,
|
|
@@ -408,7 +399,7 @@ class Transcript(BaseModel):
|
|
|
408
399
|
metadata_obj = to_jsonable_python(self.metadata)
|
|
409
400
|
yaml_width = float("inf")
|
|
410
401
|
block_str = f"<blocks>\n{blocks_str}\n</blocks>\n"
|
|
411
|
-
metadata_str = f"
|
|
402
|
+
metadata_str = f"<|transcript metadata|>\n{yaml.dump(metadata_obj, width=yaml_width)}\n</|transcript metadata|>"
|
|
412
403
|
|
|
413
404
|
if token_limit == sys.maxsize:
|
|
414
405
|
return [f"{block_str}" f"{metadata_str}"]
|
|
@@ -439,56 +430,6 @@ class Transcript(BaseModel):
|
|
|
439
430
|
|
|
440
431
|
return results
|
|
441
432
|
|
|
442
|
-
def to_str_blocks(
|
|
443
|
-
self,
|
|
444
|
-
transcript_idx: int = 0,
|
|
445
|
-
agent_run_idx: int | None = None,
|
|
446
|
-
) -> str:
|
|
447
|
-
"""Represents the transcript as a string using individual message blocks.
|
|
448
|
-
|
|
449
|
-
Unlike to_str() which groups messages into action units, this method
|
|
450
|
-
formats each message as an individual block.
|
|
451
|
-
|
|
452
|
-
Returns:
|
|
453
|
-
str: A string representation with individual message blocks.
|
|
454
|
-
"""
|
|
455
|
-
return self._to_str_with_token_limit_impl(
|
|
456
|
-
token_limit=sys.maxsize,
|
|
457
|
-
transcript_idx=transcript_idx,
|
|
458
|
-
agent_run_idx=agent_run_idx,
|
|
459
|
-
use_action_units=False,
|
|
460
|
-
)[0]
|
|
461
|
-
|
|
462
|
-
def to_str_with_token_limit(
|
|
463
|
-
self,
|
|
464
|
-
token_limit: int,
|
|
465
|
-
transcript_idx: int = 0,
|
|
466
|
-
agent_run_idx: int | None = None,
|
|
467
|
-
highlight_action_unit: int | None = None,
|
|
468
|
-
) -> list[str]:
|
|
469
|
-
"""Represents the transcript as a list of strings using action units with token limit handling."""
|
|
470
|
-
return self._to_str_with_token_limit_impl(
|
|
471
|
-
token_limit=token_limit,
|
|
472
|
-
transcript_idx=transcript_idx,
|
|
473
|
-
agent_run_idx=agent_run_idx,
|
|
474
|
-
use_action_units=True,
|
|
475
|
-
highlight_action_unit=highlight_action_unit,
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
def to_str_blocks_with_token_limit(
|
|
479
|
-
self,
|
|
480
|
-
token_limit: int,
|
|
481
|
-
transcript_idx: int = 0,
|
|
482
|
-
agent_run_idx: int | None = None,
|
|
483
|
-
) -> list[str]:
|
|
484
|
-
"""Represents the transcript as individual blocks with token limit handling."""
|
|
485
|
-
return self._to_str_with_token_limit_impl(
|
|
486
|
-
token_limit=token_limit,
|
|
487
|
-
transcript_idx=transcript_idx,
|
|
488
|
-
agent_run_idx=agent_run_idx,
|
|
489
|
-
use_action_units=False,
|
|
490
|
-
)
|
|
491
|
-
|
|
492
433
|
##############################
|
|
493
434
|
# New text rendering methods #
|
|
494
435
|
##############################
|
docent/sdk/agent_run_writer.py
CHANGED
|
@@ -19,11 +19,16 @@ logger = get_logger(__name__)
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _giveup(exc: BaseException) -> bool:
|
|
22
|
-
"""Give up on client errors."""
|
|
22
|
+
"""Give up on timeouts and client errors (4xx except 429). Retry others."""
|
|
23
|
+
|
|
24
|
+
# Give up immediately on any timeout (connect/read/write/pool)
|
|
25
|
+
if isinstance(exc, httpx.TimeoutException):
|
|
26
|
+
return True
|
|
23
27
|
|
|
24
28
|
if isinstance(exc, httpx.HTTPStatusError):
|
|
25
29
|
status = exc.response.status_code
|
|
26
30
|
return status < 500 and status != 429
|
|
31
|
+
|
|
27
32
|
return False
|
|
28
33
|
|
|
29
34
|
|
|
@@ -92,7 +97,6 @@ class AgentRunWriter:
|
|
|
92
97
|
self._thread = threading.Thread(
|
|
93
98
|
target=lambda: anyio.run(self._async_main),
|
|
94
99
|
name="AgentRunWriterThread",
|
|
95
|
-
daemon=True,
|
|
96
100
|
)
|
|
97
101
|
self._thread.start()
|
|
98
102
|
logger.info("AgentRunWriter thread started")
|
|
@@ -179,7 +183,7 @@ class AgentRunWriter:
|
|
|
179
183
|
|
|
180
184
|
def get_post_batch_fcn(
|
|
181
185
|
self, client: httpx.AsyncClient
|
|
182
|
-
) -> Callable[[list[AgentRun]
|
|
186
|
+
) -> Callable[[list[AgentRun]], Coroutine[Any, Any, None]]:
|
|
183
187
|
"""Return a function that will post a batch of agent runs to the API."""
|
|
184
188
|
|
|
185
189
|
@backoff.on_exception(
|
|
@@ -189,34 +193,34 @@ class AgentRunWriter:
|
|
|
189
193
|
max_tries=self._max_retries,
|
|
190
194
|
on_backoff=_print_backoff_message,
|
|
191
195
|
)
|
|
192
|
-
async def _post_batch(batch: list[AgentRun]
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
self._endpoint, json=payload, timeout=self._request_timeout
|
|
197
|
-
)
|
|
198
|
-
resp.raise_for_status()
|
|
196
|
+
async def _post_batch(batch: list[AgentRun]) -> None:
|
|
197
|
+
payload = {"agent_runs": [ar.model_dump(mode="json") for ar in batch]}
|
|
198
|
+
resp = await client.post(self._endpoint, json=payload, timeout=self._request_timeout)
|
|
199
|
+
resp.raise_for_status()
|
|
199
200
|
|
|
200
201
|
return _post_batch
|
|
201
202
|
|
|
202
203
|
async def _async_main(self) -> None:
|
|
203
204
|
"""Main async function for the AgentRunWriter thread."""
|
|
204
205
|
|
|
205
|
-
limiter = anyio.CapacityLimiter(self._num_workers)
|
|
206
|
-
|
|
207
206
|
async with httpx.AsyncClient(base_url=self._base_url, headers=self._headers) as client:
|
|
207
|
+
_post_batch = self.get_post_batch_fcn(client)
|
|
208
208
|
async with anyio.create_task_group() as tg:
|
|
209
|
-
_post_batch = self.get_post_batch_fcn(client)
|
|
210
209
|
|
|
211
|
-
async def
|
|
210
|
+
async def worker():
|
|
212
211
|
while not self._cancel_event.is_set():
|
|
213
212
|
batch = await self._gather_next_batch_from_queue()
|
|
214
213
|
if not batch:
|
|
215
214
|
continue
|
|
215
|
+
try:
|
|
216
|
+
await _post_batch(batch)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(
|
|
219
|
+
f"Failed to post batch of {len(batch)} agent runs: {e.__class__.__name__}: {e}"
|
|
220
|
+
)
|
|
216
221
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
tg.start_soon(batch_loop)
|
|
222
|
+
for _ in range(self._num_workers):
|
|
223
|
+
tg.start_soon(worker)
|
|
220
224
|
|
|
221
225
|
async def _gather_next_batch_from_queue(self) -> list[AgentRun]:
|
|
222
226
|
"""Gather a batch of agent runs from the queue.
|
|
@@ -241,6 +245,14 @@ def init(
|
|
|
241
245
|
server_url: str = "https://api.docent.transluce.org",
|
|
242
246
|
web_url: str = "https://docent.transluce.org",
|
|
243
247
|
api_key: str | None = None,
|
|
248
|
+
# Writer arguments
|
|
249
|
+
num_workers: int = 2,
|
|
250
|
+
queue_maxsize: int = 20_000,
|
|
251
|
+
request_timeout: float = 30.0,
|
|
252
|
+
flush_interval: float = 1.0,
|
|
253
|
+
batch_size: int = 1_000,
|
|
254
|
+
max_retries: int = 5,
|
|
255
|
+
shutdown_timeout: int = 60,
|
|
244
256
|
):
|
|
245
257
|
"""Initialize the AgentRunWriter thread.
|
|
246
258
|
|
|
@@ -250,6 +262,16 @@ def init(
|
|
|
250
262
|
server_url (str): URL of the Docent server.
|
|
251
263
|
web_url (str): URL of the Docent web UI.
|
|
252
264
|
api_key (str): API key for the Docent API.
|
|
265
|
+
num_workers (int): Max number of concurrent tasks to run,
|
|
266
|
+
managed by anyio.CapacityLimiter.
|
|
267
|
+
queue_maxsize (int): Maximum size of the queue.
|
|
268
|
+
If maxsize is <= 0, the queue size is infinite.
|
|
269
|
+
request_timeout (float): Timeout for the HTTP request.
|
|
270
|
+
flush_interval (float): Interval to flush the queue.
|
|
271
|
+
batch_size (int): Number of agent runs to batch together.
|
|
272
|
+
max_retries (int): Maximum number of retries for the HTTP request.
|
|
273
|
+
shutdown_timeout (int): Timeout to wait for the background thread to finish
|
|
274
|
+
after the main thread has requested shutdown.
|
|
253
275
|
"""
|
|
254
276
|
api_key = api_key or os.getenv("DOCENT_API_KEY")
|
|
255
277
|
|
|
@@ -271,4 +293,12 @@ def init(
|
|
|
271
293
|
api_key=api_key,
|
|
272
294
|
collection_id=collection_id,
|
|
273
295
|
server_url=server_url,
|
|
296
|
+
# Writer arguments
|
|
297
|
+
num_workers=num_workers,
|
|
298
|
+
queue_maxsize=queue_maxsize,
|
|
299
|
+
request_timeout=request_timeout,
|
|
300
|
+
flush_interval=flush_interval,
|
|
301
|
+
batch_size=batch_size,
|
|
302
|
+
max_retries=max_retries,
|
|
303
|
+
shutdown_timeout=shutdown_timeout,
|
|
274
304
|
)
|
|
@@ -6,17 +6,17 @@ docent/_log_util/__init__.py,sha256=3HXXrxrSm8PxwG4llotrCnSnp7GuroK1FNHsdg6f7aE,
|
|
|
6
6
|
docent/_log_util/logger.py,sha256=kwM0yRW1IJd6-XTorjWn48B4l8qvD2ZM6VDjY5eskQI,4422
|
|
7
7
|
docent/data_models/__init__.py,sha256=4JbTDVzRhS5VZgo8MALwd_YI17GaN7X9E3rOc4Xl7kw,327
|
|
8
8
|
docent/data_models/_tiktoken_util.py,sha256=hC0EDDWItv5-0cONBnHWgZtQOflDU7ZNEhXPFo4DvPc,3057
|
|
9
|
-
docent/data_models/agent_run.py,sha256=
|
|
9
|
+
docent/data_models/agent_run.py,sha256=F5oGsKVpWc-IUCX3QxChkgw6UR30MGTlP4YS2KeaUkQ,19315
|
|
10
10
|
docent/data_models/citation.py,sha256=zpF9WuvVEfktltw1M9P3hwpg5yywizFUKF5zROBR2cY,5062
|
|
11
11
|
docent/data_models/metadata.py,sha256=r0SYC4i2x096dXMLfw_rAMtcJQCsoV6EOMPZuEngbGA,9062
|
|
12
12
|
docent/data_models/regex.py,sha256=0ciIerkrNwb91bY5mTcyO5nDWH67xx2tZYObV52fmBo,1684
|
|
13
13
|
docent/data_models/remove_invalid_citation_ranges.py,sha256=U-aIzRL-SuWFQZr1MqEGqXMNyIKQs7VQLxHDoFrMJwI,5658
|
|
14
14
|
docent/data_models/shared_types.py,sha256=jjm-Dh5S6v7UKInW7SEqoziOsx6Z7Uu4e3VzgCbTWvc,225
|
|
15
|
-
docent/data_models/transcript.py,sha256=
|
|
15
|
+
docent/data_models/transcript.py,sha256=eT48m8rZ3STGdElH-B1aOlCuwdaCc673GNVdQTBgAt0,19429
|
|
16
16
|
docent/data_models/yaml_util.py,sha256=6GrPWqbTZrryZh71cnSsiqbHkWVCd-8V3-6GeiEchUg,325
|
|
17
17
|
docent/data_models/chat/__init__.py,sha256=GleyRzYqKRkwwSRm_tQJw5BudCbgu9WRSa71Fntz0L0,610
|
|
18
18
|
docent/data_models/chat/content.py,sha256=Co-jO8frQa_DSP11wJuhPX0s-GpJk8yqtKqPeiAIZ_U,1672
|
|
19
|
-
docent/data_models/chat/message.py,sha256=
|
|
19
|
+
docent/data_models/chat/message.py,sha256=_72xeTdgv8ogQd4WLl1P3yXfIDkIEQrHlWgdvObeQxY,4291
|
|
20
20
|
docent/data_models/chat/tool.py,sha256=MMglNHzkwHqUoK0xDWqs2FtelPsgHqwVpGpI1F8KZyw,3049
|
|
21
21
|
docent/loaders/load_inspect.py,sha256=VLrtpvcVZ44n2DIPMwUivXqbvOWjaooGw6moY8UQ0VE,6789
|
|
22
22
|
docent/samples/__init__.py,sha256=roDFnU6515l9Q8v17Es_SpWyY9jbm5d6X9lV01V0MZo,143
|
|
@@ -24,9 +24,9 @@ docent/samples/load.py,sha256=ZGE07r83GBNO4A0QBh5aQ18WAu3mTWA1vxUoHd90nrM,207
|
|
|
24
24
|
docent/samples/log.eval,sha256=orrW__9WBfANq7NwKsPSq9oTsQRcG6KohG5tMr_X_XY,397708
|
|
25
25
|
docent/samples/tb_airline.json,sha256=eR2jFFRtOw06xqbEglh6-dPewjifOk-cuxJq67Dtu5I,47028
|
|
26
26
|
docent/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
docent/sdk/agent_run_writer.py,sha256=
|
|
27
|
+
docent/sdk/agent_run_writer.py,sha256=nxLBwCjkalDI33XNEUEWdPinio5MN2xGnch5UbvAJtU,10740
|
|
28
28
|
docent/sdk/client.py,sha256=fuJrTF87OtUojULFY7acZuqg5xmE8F-4HgEeEV8_gq0,14781
|
|
29
|
-
docent_python-0.1.
|
|
30
|
-
docent_python-0.1.
|
|
31
|
-
docent_python-0.1.
|
|
32
|
-
docent_python-0.1.
|
|
29
|
+
docent_python-0.1.18a0.dist-info/METADATA,sha256=_PsOAABH2m3ycl1PjRNhdnFNC1o5e_j0O5-OHHh1diA,1110
|
|
30
|
+
docent_python-0.1.18a0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
31
|
+
docent_python-0.1.18a0.dist-info/licenses/LICENSE.md,sha256=QIMv2UiT6MppRasso4ymaA0w7ltkqmlL0HCt8CLD7Rc,580
|
|
32
|
+
docent_python-0.1.18a0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|