langfun 0.1.2.dev202510230805__py3-none-any.whl → 0.1.2.dev202511270805__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 langfun might be problematic. Click here for more details.
- langfun/core/__init__.py +2 -0
- langfun/core/agentic/__init__.py +4 -1
- langfun/core/agentic/action.py +447 -29
- langfun/core/agentic/action_eval.py +9 -2
- langfun/core/agentic/action_test.py +149 -21
- langfun/core/async_support.py +32 -3
- langfun/core/coding/python/correction.py +19 -9
- langfun/core/coding/python/execution.py +14 -12
- langfun/core/coding/python/generation.py +21 -16
- langfun/core/coding/python/sandboxing.py +23 -3
- langfun/core/component.py +42 -3
- langfun/core/concurrent.py +70 -6
- langfun/core/concurrent_test.py +1 -0
- langfun/core/console.py +1 -1
- langfun/core/data/conversion/anthropic.py +12 -3
- langfun/core/data/conversion/anthropic_test.py +8 -6
- langfun/core/data/conversion/gemini.py +9 -2
- langfun/core/data/conversion/gemini_test.py +12 -9
- langfun/core/data/conversion/openai.py +145 -31
- langfun/core/data/conversion/openai_test.py +161 -17
- langfun/core/eval/base.py +47 -43
- langfun/core/eval/base_test.py +5 -5
- langfun/core/eval/matching.py +5 -2
- langfun/core/eval/patching.py +3 -3
- langfun/core/eval/scoring.py +4 -3
- langfun/core/eval/v2/__init__.py +1 -0
- langfun/core/eval/v2/checkpointing.py +64 -6
- langfun/core/eval/v2/checkpointing_test.py +9 -2
- langfun/core/eval/v2/eval_test_helper.py +103 -2
- langfun/core/eval/v2/evaluation.py +91 -16
- langfun/core/eval/v2/evaluation_test.py +9 -3
- langfun/core/eval/v2/example.py +50 -40
- langfun/core/eval/v2/example_test.py +16 -8
- langfun/core/eval/v2/experiment.py +74 -8
- langfun/core/eval/v2/experiment_test.py +19 -0
- langfun/core/eval/v2/metric_values.py +31 -3
- langfun/core/eval/v2/metric_values_test.py +32 -0
- langfun/core/eval/v2/metrics.py +157 -44
- langfun/core/eval/v2/metrics_test.py +39 -18
- langfun/core/eval/v2/progress.py +30 -1
- langfun/core/eval/v2/progress_test.py +27 -0
- langfun/core/eval/v2/progress_tracking.py +12 -3
- langfun/core/eval/v2/progress_tracking_test.py +6 -1
- langfun/core/eval/v2/reporting.py +90 -71
- langfun/core/eval/v2/reporting_test.py +24 -6
- langfun/core/eval/v2/runners/__init__.py +30 -0
- langfun/core/eval/v2/{runners.py → runners/base.py} +59 -142
- langfun/core/eval/v2/runners/beam.py +341 -0
- langfun/core/eval/v2/runners/beam_test.py +131 -0
- langfun/core/eval/v2/runners/ckpt_monitor.py +294 -0
- langfun/core/eval/v2/runners/ckpt_monitor_test.py +162 -0
- langfun/core/eval/v2/runners/debug.py +40 -0
- langfun/core/eval/v2/runners/debug_test.py +76 -0
- langfun/core/eval/v2/runners/parallel.py +100 -0
- langfun/core/eval/v2/runners/parallel_test.py +95 -0
- langfun/core/eval/v2/runners/sequential.py +47 -0
- langfun/core/eval/v2/runners/sequential_test.py +172 -0
- langfun/core/langfunc.py +45 -130
- langfun/core/langfunc_test.py +7 -5
- langfun/core/language_model.py +141 -21
- langfun/core/language_model_test.py +54 -3
- langfun/core/llms/__init__.py +9 -1
- langfun/core/llms/anthropic.py +157 -2
- langfun/core/llms/azure_openai.py +29 -17
- langfun/core/llms/cache/base.py +25 -3
- langfun/core/llms/cache/in_memory.py +48 -7
- langfun/core/llms/cache/in_memory_test.py +14 -4
- langfun/core/llms/compositional.py +25 -1
- langfun/core/llms/deepseek.py +30 -2
- langfun/core/llms/fake.py +32 -1
- langfun/core/llms/gemini.py +55 -17
- langfun/core/llms/gemini_test.py +84 -0
- langfun/core/llms/google_genai.py +34 -1
- langfun/core/llms/groq.py +28 -3
- langfun/core/llms/llama_cpp.py +23 -4
- langfun/core/llms/openai.py +36 -3
- langfun/core/llms/openai_compatible.py +148 -27
- langfun/core/llms/openai_compatible_test.py +207 -20
- langfun/core/llms/openai_test.py +0 -2
- langfun/core/llms/rest.py +12 -1
- langfun/core/llms/vertexai.py +58 -8
- langfun/core/logging.py +1 -1
- langfun/core/mcp/client.py +77 -22
- langfun/core/mcp/client_test.py +8 -35
- langfun/core/mcp/session.py +94 -29
- langfun/core/mcp/session_test.py +54 -0
- langfun/core/mcp/tool.py +151 -22
- langfun/core/mcp/tool_test.py +197 -0
- langfun/core/memory.py +1 -0
- langfun/core/message.py +160 -55
- langfun/core/message_test.py +65 -81
- langfun/core/modalities/__init__.py +8 -0
- langfun/core/modalities/audio.py +21 -1
- langfun/core/modalities/image.py +19 -1
- langfun/core/modalities/mime.py +64 -3
- langfun/core/modalities/mime_test.py +11 -0
- langfun/core/modalities/pdf.py +19 -1
- langfun/core/modalities/video.py +21 -1
- langfun/core/modality.py +167 -29
- langfun/core/modality_test.py +42 -12
- langfun/core/natural_language.py +1 -1
- langfun/core/sampling.py +4 -4
- langfun/core/sampling_test.py +20 -4
- langfun/core/structured/__init__.py +2 -24
- langfun/core/structured/completion.py +34 -44
- langfun/core/structured/completion_test.py +23 -43
- langfun/core/structured/description.py +54 -50
- langfun/core/structured/function_generation.py +29 -12
- langfun/core/structured/mapping.py +81 -37
- langfun/core/structured/parsing.py +95 -79
- langfun/core/structured/parsing_test.py +0 -3
- langfun/core/structured/querying.py +215 -142
- langfun/core/structured/querying_test.py +65 -29
- langfun/core/structured/schema/__init__.py +49 -0
- langfun/core/structured/schema/base.py +664 -0
- langfun/core/structured/schema/base_test.py +531 -0
- langfun/core/structured/schema/json.py +174 -0
- langfun/core/structured/schema/json_test.py +121 -0
- langfun/core/structured/schema/python.py +316 -0
- langfun/core/structured/schema/python_test.py +410 -0
- langfun/core/structured/schema_generation.py +33 -14
- langfun/core/structured/scoring.py +47 -36
- langfun/core/structured/tokenization.py +26 -11
- langfun/core/subscription.py +2 -2
- langfun/core/template.py +174 -49
- langfun/core/template_test.py +123 -17
- langfun/env/__init__.py +8 -2
- langfun/env/base_environment.py +320 -128
- langfun/env/base_environment_test.py +473 -0
- langfun/env/base_feature.py +92 -15
- langfun/env/base_feature_test.py +228 -0
- langfun/env/base_sandbox.py +84 -361
- langfun/env/base_sandbox_test.py +1235 -0
- langfun/env/event_handlers/__init__.py +1 -1
- langfun/env/event_handlers/chain.py +233 -0
- langfun/env/event_handlers/chain_test.py +253 -0
- langfun/env/event_handlers/event_logger.py +95 -98
- langfun/env/event_handlers/event_logger_test.py +21 -21
- langfun/env/event_handlers/metric_writer.py +225 -140
- langfun/env/event_handlers/metric_writer_test.py +23 -6
- langfun/env/interface.py +854 -40
- langfun/env/interface_test.py +112 -2
- langfun/env/load_balancers_test.py +23 -2
- langfun/env/test_utils.py +126 -84
- {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202511270805.dist-info}/METADATA +1 -1
- langfun-0.1.2.dev202511270805.dist-info/RECORD +215 -0
- langfun/core/eval/v2/runners_test.py +0 -343
- langfun/core/structured/schema.py +0 -987
- langfun/core/structured/schema_test.py +0 -982
- langfun/env/base_test.py +0 -1481
- langfun/env/event_handlers/base.py +0 -350
- langfun-0.1.2.dev202510230805.dist-info/RECORD +0 -195
- {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202511270805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202511270805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202511270805.dist-info}/top_level.txt +0 -0
langfun/core/agentic/action.py
CHANGED
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
"""Base classes for agentic actions."""
|
|
15
15
|
|
|
16
16
|
import abc
|
|
17
|
+
import collections
|
|
17
18
|
import contextlib
|
|
18
19
|
import dataclasses
|
|
19
20
|
import functools
|
|
21
|
+
import itertools
|
|
20
22
|
import threading
|
|
21
23
|
import time
|
|
22
24
|
import typing
|
|
@@ -36,7 +38,12 @@ class ActionTimeoutError(ActionError):
|
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
class Action(pg.Object):
|
|
39
|
-
"""Base class for
|
|
41
|
+
"""Base class for agentic actions.
|
|
42
|
+
|
|
43
|
+
An `Action` represents a single, executable step or task that an agent can
|
|
44
|
+
perform, such as calling a tool, querying a language model, or returning a
|
|
45
|
+
final answer. Actions are designed to be composable and trackable within a
|
|
46
|
+
`Session`.
|
|
40
47
|
|
|
41
48
|
# Developing Actions
|
|
42
49
|
|
|
@@ -149,7 +156,7 @@ class Action(pg.Object):
|
|
|
149
156
|
|
|
150
157
|
# Explicitly create and pass a session.
|
|
151
158
|
with lf.Session(id='my_agent_session') as session:
|
|
152
|
-
result = calc(session=session)
|
|
159
|
+
result = calc(session=session) # Pass the session explicitly
|
|
153
160
|
print(result)
|
|
154
161
|
```
|
|
155
162
|
|
|
@@ -308,19 +315,190 @@ class Action(pg.Object):
|
|
|
308
315
|
"""
|
|
309
316
|
|
|
310
317
|
|
|
318
|
+
#
|
|
319
|
+
# Execution tracking.
|
|
320
|
+
#
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class ExecutionUnit(pg.Object):
|
|
324
|
+
"""Base class for execution units in an agentic trajectory.
|
|
325
|
+
|
|
326
|
+
An `ExecutionUnit` represents a logical step or container in the agent's
|
|
327
|
+
execution flow. It serves as the common interface for top-level executable
|
|
328
|
+
items.
|
|
329
|
+
|
|
330
|
+
The concrete subclasses of `ExecutionUnit` are typically:
|
|
331
|
+
* **`ActionInvocation`**: Represents a single, specific action executed by
|
|
332
|
+
the agent.
|
|
333
|
+
* **`ParallelExecutions`**: Represents a container for a group of
|
|
334
|
+
`ExecutionUnits` that were executed concurrently.
|
|
335
|
+
|
|
336
|
+
Users can retrieve the immediate child execution units from an `ExecutionUnit`
|
|
337
|
+
object. To access the leaf nodes of the execution tree, use `all_actions`,
|
|
338
|
+
`all_queries`, and `all_logs` instead.
|
|
339
|
+
|
|
340
|
+
Each unit exposes a **`position`** property to reveal its specific location
|
|
341
|
+
within the execution hierarchy (e.g., '1.2.3').
|
|
342
|
+
|
|
343
|
+
Users could use **`parent_execution_unit`** to get the parent execution unit
|
|
344
|
+
of the current execution unit.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
@dataclasses.dataclass
|
|
348
|
+
class Position:
|
|
349
|
+
"""The position of an executed unit under current session."""
|
|
350
|
+
|
|
351
|
+
parent: Optional['ExecutionUnit.Position'] = None
|
|
352
|
+
index: int = 0
|
|
353
|
+
|
|
354
|
+
def indices(self) -> tuple[int, ...]:
|
|
355
|
+
"""Returns the indices from root to current execution unit."""
|
|
356
|
+
# A deque is efficient for adding items to the front.
|
|
357
|
+
path = collections.deque()
|
|
358
|
+
current_pos = self
|
|
359
|
+
|
|
360
|
+
# Traverse up from the current position to the root.
|
|
361
|
+
while current_pos.parent is not None:
|
|
362
|
+
path.appendleft(current_pos.index)
|
|
363
|
+
current_pos = current_pos.parent
|
|
364
|
+
|
|
365
|
+
path.appendleft(current_pos.index)
|
|
366
|
+
return tuple(path)
|
|
367
|
+
|
|
368
|
+
def to_str(
|
|
369
|
+
self,
|
|
370
|
+
*,
|
|
371
|
+
index_base: int = 1,
|
|
372
|
+
separator: str = '.',
|
|
373
|
+
**kwargs
|
|
374
|
+
) -> str:
|
|
375
|
+
"""Returns a string description of the position."""
|
|
376
|
+
# For root action, we return empty string as it's position descriptor.
|
|
377
|
+
if self.parent is None:
|
|
378
|
+
return ''
|
|
379
|
+
parent_descriptor = self.parent.to_str(
|
|
380
|
+
index_base=index_base,
|
|
381
|
+
separator=separator,
|
|
382
|
+
**kwargs
|
|
383
|
+
)
|
|
384
|
+
if not parent_descriptor:
|
|
385
|
+
return f'{self.index + index_base}'
|
|
386
|
+
return parent_descriptor + separator + f'{self.index + index_base}'
|
|
387
|
+
|
|
388
|
+
def __repr__(self) -> str:
|
|
389
|
+
return f'Position({", ".join(str(x) for x in self.indices())})'
|
|
390
|
+
|
|
391
|
+
def __str__(self) -> str:
|
|
392
|
+
return self.to_str()
|
|
393
|
+
|
|
394
|
+
def __eq__(self, other: 'ExecutionUnit.Position') -> bool:
|
|
395
|
+
if isinstance(other, ExecutionUnit.Position):
|
|
396
|
+
return self.indices() == other.indices()
|
|
397
|
+
if isinstance(other, tuple):
|
|
398
|
+
return self.indices() == other
|
|
399
|
+
if isinstance(other, str):
|
|
400
|
+
return str(self) == other
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
def __ne__(self, other: 'ExecutionUnit.Position') -> bool:
|
|
404
|
+
return not self == other
|
|
405
|
+
|
|
406
|
+
def __hash__(self) -> int:
|
|
407
|
+
return hash(self.indices())
|
|
408
|
+
|
|
409
|
+
def __lt__(self, other: 'ExecutionUnit.Position') -> bool:
|
|
410
|
+
return self.indices() < other.indices()
|
|
411
|
+
|
|
412
|
+
def __gt__(self, other: 'ExecutionUnit.Position') -> bool:
|
|
413
|
+
return self.indices() > other.indices()
|
|
414
|
+
|
|
415
|
+
def _on_parent_change(self, *args, **kwargs):
|
|
416
|
+
super()._on_parent_change(*args, **kwargs)
|
|
417
|
+
self.__dict__.pop('parent_execution_unit', None)
|
|
418
|
+
self.__dict__.pop('position', None)
|
|
419
|
+
|
|
420
|
+
@functools.cached_property
|
|
421
|
+
def parent_execution_unit(self) -> Optional['ExecutionUnit']:
|
|
422
|
+
"""Returns the parent execution unit of the current execution unit."""
|
|
423
|
+
parent_trace = self.sym_ancestor(lambda x: isinstance(x, ExecutionTrace))
|
|
424
|
+
assert isinstance(parent_trace, ExecutionTrace), (
|
|
425
|
+
'Execution unit is not associated with any `ExecutionTrace`: '
|
|
426
|
+
f'{self}'
|
|
427
|
+
)
|
|
428
|
+
return parent_trace.parent_execution_unit
|
|
429
|
+
|
|
430
|
+
@functools.cached_property
|
|
431
|
+
def position(self) -> Position:
|
|
432
|
+
"""Returns the execution position of the action."""
|
|
433
|
+
parent_trace = self.sym_ancestor(lambda x: isinstance(x, ExecutionTrace))
|
|
434
|
+
while parent_trace is not None:
|
|
435
|
+
parent_position = parent_trace.position
|
|
436
|
+
if parent_position is not None:
|
|
437
|
+
return ExecutionUnit.Position(
|
|
438
|
+
parent_position, parent_trace.indexof(self, ExecutionUnit)
|
|
439
|
+
)
|
|
440
|
+
parent_trace = parent_trace.sym_ancestor(
|
|
441
|
+
lambda x: isinstance(x, ExecutionTrace)
|
|
442
|
+
)
|
|
443
|
+
return ExecutionUnit.Position(None, 0)
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
@abc.abstractmethod
|
|
447
|
+
def execution_units(
|
|
448
|
+
self,
|
|
449
|
+
) -> list['ExecutionUnit']:
|
|
450
|
+
"""Returns immediate child execution items."""
|
|
451
|
+
|
|
452
|
+
@property
|
|
453
|
+
@abc.abstractmethod
|
|
454
|
+
def queries(self) -> list[lf_structured.QueryInvocation]:
|
|
455
|
+
"""Returns queries issued by the execution item."""
|
|
456
|
+
|
|
457
|
+
@property
|
|
458
|
+
@abc.abstractmethod
|
|
459
|
+
def actions(self) -> list['ActionInvocation']:
|
|
460
|
+
"""Returns immediate child action invocations."""
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
@abc.abstractmethod
|
|
464
|
+
def logs(self) -> list[lf.logging.LogEntry]:
|
|
465
|
+
"""Returns immediate logs under current execution item."""
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
@abc.abstractmethod
|
|
469
|
+
def all_queries(self) -> list[lf_structured.QueryInvocation]:
|
|
470
|
+
"""Returns all queries from the subtree."""
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
@abc.abstractmethod
|
|
474
|
+
def all_actions(self) -> list['ActionInvocation']:
|
|
475
|
+
"""Returns all action invocations from the subtree."""
|
|
476
|
+
|
|
477
|
+
@property
|
|
478
|
+
@abc.abstractmethod
|
|
479
|
+
def all_logs(self) -> list[lf.logging.LogEntry]:
|
|
480
|
+
"""Returns all logs from the subtree."""
|
|
481
|
+
|
|
482
|
+
|
|
311
483
|
# Type definition for traced item during execution.
|
|
312
484
|
TracedItem = Union[
|
|
485
|
+
ExecutionUnit,
|
|
313
486
|
lf_structured.QueryInvocation,
|
|
314
|
-
'ActionInvocation',
|
|
315
487
|
'ExecutionTrace',
|
|
316
|
-
'ParallelExecutions',
|
|
317
488
|
# NOTE(daiyip): Consider remove log entry once we migrate existing agents.
|
|
318
489
|
lf.logging.LogEntry,
|
|
319
490
|
]
|
|
320
491
|
|
|
321
492
|
|
|
322
493
|
class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
323
|
-
"""Trace of
|
|
494
|
+
"""Trace of an execution, containing queries, logs, and sub-actions.
|
|
495
|
+
|
|
496
|
+
`ExecutionTrace` records the sequence of operations performed during an
|
|
497
|
+
action's execution or within a specific phase of execution (demarcated by
|
|
498
|
+
`session.track_phase`). It captures `lf.query` calls, log entries, and
|
|
499
|
+
nested `ActionInvocation` objects in the order they occurred. It also
|
|
500
|
+
aggregates LLM usage summaries from its child items.
|
|
501
|
+
"""
|
|
324
502
|
|
|
325
503
|
name: Annotated[
|
|
326
504
|
str | None,
|
|
@@ -328,7 +506,7 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
328
506
|
'The name of the execution trace. If None, the trace is unnamed, '
|
|
329
507
|
'which is the case for the top-level trace of an action. An '
|
|
330
508
|
'execution trace could have sub-traces, called phases, which are '
|
|
331
|
-
'created and named by `session.
|
|
509
|
+
'created and named by `session.track_phase()` context manager.'
|
|
332
510
|
)
|
|
333
511
|
] = None
|
|
334
512
|
|
|
@@ -360,9 +538,15 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
360
538
|
def _on_parent_change(self, *args, **kwargs):
|
|
361
539
|
super()._on_parent_change(*args, **kwargs)
|
|
362
540
|
self.__dict__.pop('id', None)
|
|
541
|
+
self.__dict__.pop('parent_execution_unit', None)
|
|
542
|
+
self.__dict__.pop('position', None)
|
|
363
543
|
|
|
364
|
-
def indexof(
|
|
365
|
-
|
|
544
|
+
def indexof(
|
|
545
|
+
self,
|
|
546
|
+
item: TracedItem,
|
|
547
|
+
count_item_cls: Type[Any] | tuple[Type[Any], ...]
|
|
548
|
+
) -> int:
|
|
549
|
+
"""Returns the index of the child item of given type."""
|
|
366
550
|
pos = 0
|
|
367
551
|
for x in self._iter_children(count_item_cls):
|
|
368
552
|
if x is item:
|
|
@@ -370,6 +554,52 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
370
554
|
pos += 1
|
|
371
555
|
return -1
|
|
372
556
|
|
|
557
|
+
@functools.cached_property
|
|
558
|
+
def parent_execution_unit(self) -> ExecutionUnit:
|
|
559
|
+
"""Returns the parent execution unit of the current execution trace."""
|
|
560
|
+
parent = self.sym_parent
|
|
561
|
+
if isinstance(parent, ActionInvocation):
|
|
562
|
+
# Current execution trace is the body of an action.
|
|
563
|
+
return parent
|
|
564
|
+
elif isinstance(parent, pg.List):
|
|
565
|
+
container = parent.sym_parent
|
|
566
|
+
if isinstance(container, ParallelExecutions):
|
|
567
|
+
return container
|
|
568
|
+
elif isinstance(container, ExecutionTrace):
|
|
569
|
+
return container.parent_execution_unit
|
|
570
|
+
assert False, (
|
|
571
|
+
'Execution trace is not associated with any `ActionInvocation` or '
|
|
572
|
+
f'`ParallelExecutions`: {self}'
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
@functools.cached_property
|
|
576
|
+
def position(self) -> ExecutionUnit.Position | None:
|
|
577
|
+
"""Returns the execution position of the execution trace.
|
|
578
|
+
|
|
579
|
+
Returns:
|
|
580
|
+
The execution position of the execution trace, or None if the execution
|
|
581
|
+
trace is either not associated with an execution unit or the execution
|
|
582
|
+
trace is a phase under another execution trace.
|
|
583
|
+
"""
|
|
584
|
+
parent = self.sym_parent
|
|
585
|
+
if isinstance(parent, ActionInvocation):
|
|
586
|
+
# Current execution trace is the body of an action.
|
|
587
|
+
return parent.position
|
|
588
|
+
elif isinstance(parent, pg.List):
|
|
589
|
+
container = parent.sym_parent
|
|
590
|
+
if isinstance(container, ParallelExecutions):
|
|
591
|
+
return ExecutionUnit.Position(
|
|
592
|
+
container.position, self.sym_path.key
|
|
593
|
+
)
|
|
594
|
+
elif isinstance(container, ExecutionTrace):
|
|
595
|
+
# When execution trace is a phase under another execution trace,
|
|
596
|
+
# we return None as the position.
|
|
597
|
+
return None
|
|
598
|
+
assert False, (
|
|
599
|
+
'Execution trace is not associated with any `ActionInvocation` or '
|
|
600
|
+
f'`ParallelExecutions`: {self}'
|
|
601
|
+
)
|
|
602
|
+
|
|
373
603
|
@functools.cached_property
|
|
374
604
|
def id(self) -> str:
|
|
375
605
|
parent = self.sym_parent
|
|
@@ -441,6 +671,11 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
441
671
|
"""Returns action invocations from the sequence."""
|
|
442
672
|
return list(self._iter_children(ActionInvocation))
|
|
443
673
|
|
|
674
|
+
@property
|
|
675
|
+
def execution_units(self) -> list[ExecutionUnit]:
|
|
676
|
+
"""Returns parallel executions from the sequence."""
|
|
677
|
+
return list(self._iter_children(ExecutionUnit))
|
|
678
|
+
|
|
444
679
|
@property
|
|
445
680
|
def logs(self) -> list[lf.logging.LogEntry]:
|
|
446
681
|
"""Returns logs from the sequence."""
|
|
@@ -461,7 +696,9 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
461
696
|
"""Returns all logs from current trace and its child execution items."""
|
|
462
697
|
return list(self._iter_subtree(lf.logging.LogEntry))
|
|
463
698
|
|
|
464
|
-
def _iter_children(
|
|
699
|
+
def _iter_children(
|
|
700
|
+
self, item_cls: Type[Any] | tuple[Type[Any], ...]
|
|
701
|
+
) -> Iterator[TracedItem]:
|
|
465
702
|
for item in self.items:
|
|
466
703
|
if isinstance(item, item_cls):
|
|
467
704
|
yield item
|
|
@@ -473,7 +710,10 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
473
710
|
for x in branch._iter_children(item_cls): # pylint: disable=protected-access
|
|
474
711
|
yield x
|
|
475
712
|
|
|
476
|
-
def _iter_subtree(
|
|
713
|
+
def _iter_subtree(
|
|
714
|
+
self,
|
|
715
|
+
item_cls: Type[Any] | tuple[Type[Any], ...]
|
|
716
|
+
) -> Iterator[TracedItem]:
|
|
477
717
|
for item in self.items:
|
|
478
718
|
if isinstance(item, item_cls):
|
|
479
719
|
yield item
|
|
@@ -538,6 +778,18 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
538
778
|
remove_class=['not-started'],
|
|
539
779
|
)
|
|
540
780
|
|
|
781
|
+
def remove(self, item: TracedItem) -> None:
|
|
782
|
+
"""Removes an item from the sequence."""
|
|
783
|
+
index = self.items.index(item)
|
|
784
|
+
if index == -1:
|
|
785
|
+
raise ValueError(f'Item not found in execution trace: {item!r}')
|
|
786
|
+
|
|
787
|
+
with pg.notify_on_change(False):
|
|
788
|
+
self.items.pop(index)
|
|
789
|
+
|
|
790
|
+
if self._tab_control is not None:
|
|
791
|
+
self._tab_control.remove(index)
|
|
792
|
+
|
|
541
793
|
def extend(self, items: Iterable[TracedItem]) -> None:
|
|
542
794
|
"""Extends the sequence with a list of items."""
|
|
543
795
|
for item in items:
|
|
@@ -774,8 +1026,13 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
774
1026
|
]
|
|
775
1027
|
|
|
776
1028
|
|
|
777
|
-
class ParallelExecutions(
|
|
778
|
-
"""A
|
|
1029
|
+
class ParallelExecutions(ExecutionUnit, pg.views.html.HtmlTreeView.Extension):
|
|
1030
|
+
"""A container for multiple parallel execution traces.
|
|
1031
|
+
|
|
1032
|
+
When `session.concurrent_map` is used, it creates a `ParallelExecutions`
|
|
1033
|
+
object to hold an `ExecutionTrace` for each parallel branch of execution,
|
|
1034
|
+
allowing inspection of parallel workflows.
|
|
1035
|
+
"""
|
|
779
1036
|
|
|
780
1037
|
name: Annotated[
|
|
781
1038
|
str | None,
|
|
@@ -821,8 +1078,64 @@ class ParallelExecutions(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
821
1078
|
self.branches.append(branch)
|
|
822
1079
|
if self._tab_control is not None:
|
|
823
1080
|
self._tab_control.append(self._branch_tab(branch))
|
|
1081
|
+
|
|
1082
|
+
# Invalidate cached properties.
|
|
1083
|
+
self.__dict__.pop('all_queries', None)
|
|
1084
|
+
self.__dict__.pop('all_actions', None)
|
|
1085
|
+
self.__dict__.pop('all_logs', None)
|
|
824
1086
|
return branch
|
|
825
1087
|
|
|
1088
|
+
#
|
|
1089
|
+
# ExecutionUnit interface.
|
|
1090
|
+
#
|
|
1091
|
+
|
|
1092
|
+
@property
|
|
1093
|
+
def execution_units(self) -> list[ExecutionUnit]:
|
|
1094
|
+
"""Returns immediate child execution items from execution sequence."""
|
|
1095
|
+
return []
|
|
1096
|
+
|
|
1097
|
+
@property
|
|
1098
|
+
def queries(self) -> list[lf_structured.QueryInvocation]:
|
|
1099
|
+
"""Returns immediate queries made by the parallel execution."""
|
|
1100
|
+
return []
|
|
1101
|
+
|
|
1102
|
+
@property
|
|
1103
|
+
def actions(self) -> list['ActionInvocation']:
|
|
1104
|
+
"""Returns immediate child action invocations."""
|
|
1105
|
+
return []
|
|
1106
|
+
|
|
1107
|
+
@property
|
|
1108
|
+
def logs(self) -> list[lf.logging.LogEntry]:
|
|
1109
|
+
"""Returns immediate child logs from execution sequence."""
|
|
1110
|
+
return []
|
|
1111
|
+
|
|
1112
|
+
@functools.cached_property
|
|
1113
|
+
def all_queries(self) -> list[lf_structured.QueryInvocation]:
|
|
1114
|
+
"""Returns all queries made by the action and its child execution items."""
|
|
1115
|
+
return list(
|
|
1116
|
+
itertools.chain.from_iterable(
|
|
1117
|
+
branch.all_queries for branch in self.branches
|
|
1118
|
+
)
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
@functools.cached_property
|
|
1122
|
+
def all_actions(self) -> list['ActionInvocation']:
|
|
1123
|
+
"""Returns all actions made by the action and its child execution items."""
|
|
1124
|
+
return list(
|
|
1125
|
+
itertools.chain.from_iterable(
|
|
1126
|
+
branch.all_actions for branch in self.branches
|
|
1127
|
+
)
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
@property
|
|
1131
|
+
def all_logs(self) -> list[lf.logging.LogEntry]:
|
|
1132
|
+
"""Returns all logs made by the action and its child execution items."""
|
|
1133
|
+
return list(
|
|
1134
|
+
itertools.chain.from_iterable(
|
|
1135
|
+
branch.all_logs for branch in self.branches
|
|
1136
|
+
)
|
|
1137
|
+
)
|
|
1138
|
+
|
|
826
1139
|
#
|
|
827
1140
|
# HTML views.
|
|
828
1141
|
#
|
|
@@ -863,8 +1176,15 @@ class ParallelExecutions(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
863
1176
|
)
|
|
864
1177
|
|
|
865
1178
|
|
|
866
|
-
class ActionInvocation(
|
|
867
|
-
"""
|
|
1179
|
+
class ActionInvocation(ExecutionUnit, pg.views.html.HtmlTreeView.Extension):
|
|
1180
|
+
"""An invocation of an action, capturing its execution and result.
|
|
1181
|
+
|
|
1182
|
+
`ActionInvocation` represents a single call to an `Action`. It contains
|
|
1183
|
+
the `Action` object itself, its result or error, associated metadata,
|
|
1184
|
+
and an `ExecutionTrace` detailing the steps taken during its execution
|
|
1185
|
+
(queries, logs, sub-actions). Invocations form a tree structure within a
|
|
1186
|
+
`Session`, reflecting the hierarchy of agentic operations.
|
|
1187
|
+
"""
|
|
868
1188
|
|
|
869
1189
|
action: Annotated[
|
|
870
1190
|
Action,
|
|
@@ -917,6 +1237,7 @@ class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
917
1237
|
def _on_parent_change(self, *args, **kwargs):
|
|
918
1238
|
super()._on_parent_change(*args, **kwargs)
|
|
919
1239
|
self.__dict__.pop('id', None)
|
|
1240
|
+
self.__dict__.pop('position', None)
|
|
920
1241
|
|
|
921
1242
|
@property
|
|
922
1243
|
def parent_action(self) -> Optional['ActionInvocation']:
|
|
@@ -953,6 +1274,15 @@ class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
953
1274
|
"""Returns the state of the action."""
|
|
954
1275
|
return self.action.state
|
|
955
1276
|
|
|
1277
|
+
#
|
|
1278
|
+
# Implement `ExecutionUnit` interface.
|
|
1279
|
+
#
|
|
1280
|
+
|
|
1281
|
+
@property
|
|
1282
|
+
def execution_units(self) -> list[ExecutionUnit]:
|
|
1283
|
+
"""Returns immediate child execution items from execution sequence."""
|
|
1284
|
+
return self.execution.execution_units
|
|
1285
|
+
|
|
956
1286
|
@property
|
|
957
1287
|
def logs(self) -> list[lf.logging.LogEntry]:
|
|
958
1288
|
"""Returns immediate child logs from execution sequence."""
|
|
@@ -1004,10 +1334,12 @@ class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1004
1334
|
metadata: dict[str, Any] | None = None,
|
|
1005
1335
|
) -> None:
|
|
1006
1336
|
"""Ends the execution of the action with result and metadata."""
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1337
|
+
with pg.notify_on_change(False):
|
|
1338
|
+
self.result = result
|
|
1339
|
+
self.error = error
|
|
1340
|
+
if metadata:
|
|
1341
|
+
self.metadata.update(metadata)
|
|
1342
|
+
|
|
1011
1343
|
self.execution.stop()
|
|
1012
1344
|
if self._tab_control is not None:
|
|
1013
1345
|
if self.metadata:
|
|
@@ -1165,6 +1497,19 @@ class RootAction(Action):
|
|
|
1165
1497
|
class SessionEventHandler:
|
|
1166
1498
|
"""Interface for handling session events."""
|
|
1167
1499
|
|
|
1500
|
+
def get(
|
|
1501
|
+
self,
|
|
1502
|
+
session_cls: type['SessionEventHandler']
|
|
1503
|
+
) -> Optional['SessionEventHandler']:
|
|
1504
|
+
"""Returns this or a child event handler for the given session class."""
|
|
1505
|
+
if isinstance(self, session_cls):
|
|
1506
|
+
return self
|
|
1507
|
+
elif isinstance(self, SessionEventHandlerChain):
|
|
1508
|
+
for handler in self.handlers:
|
|
1509
|
+
if v := handler.get(session_cls):
|
|
1510
|
+
return v
|
|
1511
|
+
return None
|
|
1512
|
+
|
|
1168
1513
|
def on_session_start(
|
|
1169
1514
|
self,
|
|
1170
1515
|
session: 'Session'
|
|
@@ -1394,7 +1739,50 @@ class SessionLogging(SessionEventHandler):
|
|
|
1394
1739
|
|
|
1395
1740
|
|
|
1396
1741
|
class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
1397
|
-
"""
|
|
1742
|
+
"""Manages the execution trajectory of agentic actions.
|
|
1743
|
+
|
|
1744
|
+
A `Session` tracks the execution of a root `Action` and all its
|
|
1745
|
+
sub-actions, including LLM queries (`lf.query`), logging messages,
|
|
1746
|
+
and nested actions. It provides a complete, hierarchical trace of an
|
|
1747
|
+
agent's workflow, which is important for debugging, analysis, and
|
|
1748
|
+
visualization.
|
|
1749
|
+
|
|
1750
|
+
Sessions can be created implicitly when an action is called without an
|
|
1751
|
+
active session, or explicitly for more control.
|
|
1752
|
+
|
|
1753
|
+
**1. Implicit Session:**
|
|
1754
|
+
When an action is called without a session, Langfun creates one automatically.
|
|
1755
|
+
|
|
1756
|
+
```python
|
|
1757
|
+
action = MyAction()
|
|
1758
|
+
action()
|
|
1759
|
+
session = action.session # Access the implicit session
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
**2. Explicit Session:**
|
|
1763
|
+
Use a `with` statement to manage a session explicitly. This is useful for
|
|
1764
|
+
setting session IDs or capturing the trajectory of multiple top-level actions.
|
|
1765
|
+
|
|
1766
|
+
```python
|
|
1767
|
+
with lf.Session(id='my-session') as session:
|
|
1768
|
+
action1()
|
|
1769
|
+
action2()
|
|
1770
|
+
```
|
|
1771
|
+
|
|
1772
|
+
**3. Accessing Trajectory:**
|
|
1773
|
+
The `session.root` attribute provides access to the `ActionInvocation` tree.
|
|
1774
|
+
|
|
1775
|
+
```python
|
|
1776
|
+
with lf.Session() as session:
|
|
1777
|
+
my_action()
|
|
1778
|
+
|
|
1779
|
+
# Get all queries in the session
|
|
1780
|
+
print(session.all_queries)
|
|
1781
|
+
|
|
1782
|
+
# Get all top-level action calls in the session
|
|
1783
|
+
print(session.root.actions)
|
|
1784
|
+
```
|
|
1785
|
+
"""
|
|
1398
1786
|
|
|
1399
1787
|
root: Annotated[
|
|
1400
1788
|
ActionInvocation,
|
|
@@ -1406,11 +1794,6 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1406
1794
|
'An optional identifier for the session, which will be used for logging.'
|
|
1407
1795
|
]
|
|
1408
1796
|
|
|
1409
|
-
event_handler: Annotated[
|
|
1410
|
-
SessionEventHandler,
|
|
1411
|
-
'Event handler for the session.'
|
|
1412
|
-
]
|
|
1413
|
-
|
|
1414
1797
|
@pg.explicit_method_override
|
|
1415
1798
|
def __init__(
|
|
1416
1799
|
self,
|
|
@@ -1424,14 +1807,33 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1424
1807
|
super().__init__(
|
|
1425
1808
|
id=id,
|
|
1426
1809
|
root=root or ActionInvocation(RootAction()),
|
|
1427
|
-
event_handler=event_handler or SessionLogging(verbose=verbose),
|
|
1428
1810
|
**kwargs
|
|
1429
1811
|
)
|
|
1812
|
+
self._event_handler = event_handler or SessionLogging(verbose=verbose)
|
|
1813
|
+
|
|
1814
|
+
@property
|
|
1815
|
+
def event_handler(self) -> SessionEventHandler:
|
|
1816
|
+
"""Returns the event handler for the session."""
|
|
1817
|
+
return self._event_handler
|
|
1818
|
+
|
|
1819
|
+
def _sym_clone(self, deep: bool, memo: Any = None) -> 'Session':
|
|
1820
|
+
other = super()._sym_clone(deep=deep, memo=memo)
|
|
1821
|
+
if deep:
|
|
1822
|
+
event_handler = pg.clone(self.event_handler, deep=deep, memo=memo)
|
|
1823
|
+
else:
|
|
1824
|
+
event_handler = self.event_handler
|
|
1825
|
+
other._event_handler = event_handler # pylint: disable=protected-access
|
|
1826
|
+
return other
|
|
1430
1827
|
|
|
1431
1828
|
#
|
|
1432
1829
|
# Shortcut methods for accessing the root action invocation.
|
|
1433
1830
|
#
|
|
1434
1831
|
|
|
1832
|
+
@property
|
|
1833
|
+
def metadata(self) -> dict[str, Any]:
|
|
1834
|
+
"""Returns metadata associated with the root of the session."""
|
|
1835
|
+
return self.root.metadata
|
|
1836
|
+
|
|
1435
1837
|
@property
|
|
1436
1838
|
def all_queries(self) -> list[lf_structured.QueryInvocation]:
|
|
1437
1839
|
"""Returns all queries made by the session."""
|
|
@@ -1547,7 +1949,7 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1547
1949
|
)
|
|
1548
1950
|
|
|
1549
1951
|
def update_progress(self, title: str, **kwargs: Any) -> None:
|
|
1550
|
-
"""
|
|
1952
|
+
"""Updates the progress of current action's execution.
|
|
1551
1953
|
|
|
1552
1954
|
Args:
|
|
1553
1955
|
title: The title of the progress update.
|
|
@@ -1648,13 +2050,20 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1648
2050
|
@contextlib.contextmanager
|
|
1649
2051
|
def track_queries(
|
|
1650
2052
|
self,
|
|
1651
|
-
phase: str | None = None
|
|
2053
|
+
phase: str | None = None,
|
|
2054
|
+
track_if: Callable[
|
|
2055
|
+
[lf_structured.QueryInvocation],
|
|
2056
|
+
bool
|
|
2057
|
+
] | None = None,
|
|
1652
2058
|
) -> Iterator[list[lf_structured.QueryInvocation]]:
|
|
1653
2059
|
"""Tracks `lf.query` made within the context.
|
|
1654
2060
|
|
|
1655
2061
|
Args:
|
|
1656
2062
|
phase: The name of a new phase to track the queries in. If not provided,
|
|
1657
2063
|
the queries will be tracked in the parent phase.
|
|
2064
|
+
track_if: A function that takes a `lf_structured.QueryInvocation` and
|
|
2065
|
+
returns True if the query should be included in the result. If None,
|
|
2066
|
+
all queries (including failed queries) will be included.
|
|
1658
2067
|
|
|
1659
2068
|
Yields:
|
|
1660
2069
|
A list of `lf.QueryInvocation` objects, each for a single `lf.query`
|
|
@@ -1673,6 +2082,11 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1673
2082
|
self.event_handler.on_query_start(self, self._current_action, invocation)
|
|
1674
2083
|
|
|
1675
2084
|
def _query_end(invocation: lf_structured.QueryInvocation):
|
|
2085
|
+
if track_if is not None and not track_if(invocation):
|
|
2086
|
+
self._current_execution.remove(invocation)
|
|
2087
|
+
# Even if the query is not included in the execution trace, we still
|
|
2088
|
+
# count the usage summary to the current execution and trigger the
|
|
2089
|
+
# event handler to log the query.
|
|
1676
2090
|
self._current_execution.merge_usage_summary(invocation.usage_summary)
|
|
1677
2091
|
self.event_handler.on_query_end(self, self._current_action, invocation)
|
|
1678
2092
|
|
|
@@ -1705,8 +2119,9 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1705
2119
|
*,
|
|
1706
2120
|
lm: lf.LanguageModel,
|
|
1707
2121
|
examples: list[lf_structured.MappingExample] | None = None,
|
|
2122
|
+
track_if: Callable[[lf_structured.QueryInvocation], bool] | None = None,
|
|
1708
2123
|
**kwargs
|
|
1709
|
-
|
|
2124
|
+
) -> Any:
|
|
1710
2125
|
"""Calls `lf.query` and associates it with the current invocation.
|
|
1711
2126
|
|
|
1712
2127
|
The following code are equivalent:
|
|
@@ -1731,12 +2146,15 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1731
2146
|
default: The default value to return if the query fails.
|
|
1732
2147
|
lm: The language model to use for the query.
|
|
1733
2148
|
examples: The examples to use for the query.
|
|
2149
|
+
track_if: A function that takes a `lf_structured.QueryInvocation`
|
|
2150
|
+
and returns True if the query should be tracked.
|
|
2151
|
+
If None, all queries (including failed queries) will be tracked.
|
|
1734
2152
|
**kwargs: Additional keyword arguments to pass to `lf.query`.
|
|
1735
2153
|
|
|
1736
2154
|
Returns:
|
|
1737
2155
|
The result of the query.
|
|
1738
2156
|
"""
|
|
1739
|
-
with self.track_queries():
|
|
2157
|
+
with self.track_queries(track_if=track_if):
|
|
1740
2158
|
return lf_structured.query(
|
|
1741
2159
|
prompt,
|
|
1742
2160
|
schema=schema,
|