langfun 0.1.2.dev202508250805__py3-none-any.whl → 0.1.2.dev202511110805__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/__init__.py +1 -1
- langfun/core/__init__.py +6 -1
- langfun/core/agentic/__init__.py +4 -0
- langfun/core/agentic/action.py +412 -103
- langfun/core/agentic/action_eval.py +9 -2
- langfun/core/agentic/action_test.py +68 -6
- langfun/core/async_support.py +104 -5
- langfun/core/async_support_test.py +23 -0
- 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 +9 -2
- 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 +4 -4
- 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 +30 -4
- langfun/core/eval/v2/eval_test_helper.py +1 -1
- langfun/core/eval/v2/evaluation.py +60 -14
- langfun/core/eval/v2/example.py +22 -11
- langfun/core/eval/v2/experiment.py +51 -8
- 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 +39 -4
- langfun/core/eval/v2/metrics_test.py +14 -0
- langfun/core/eval/v2/progress.py +30 -1
- langfun/core/eval/v2/progress_test.py +27 -0
- langfun/core/eval/v2/progress_tracking_test.py +6 -0
- langfun/core/eval/v2/reporting.py +90 -71
- langfun/core/eval/v2/reporting_test.py +20 -6
- langfun/core/eval/v2/runners.py +27 -7
- langfun/core/eval/v2/runners_test.py +3 -0
- langfun/core/langfunc.py +45 -130
- langfun/core/langfunc_test.py +6 -4
- langfun/core/language_model.py +151 -31
- langfun/core/language_model_test.py +9 -3
- langfun/core/llms/__init__.py +12 -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 +39 -1
- langfun/core/llms/fake_test.py +9 -0
- langfun/core/llms/gemini.py +43 -7
- 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 +93 -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 +16 -1
- langfun/core/llms/vertexai.py +59 -8
- langfun/core/logging.py +1 -1
- langfun/core/mcp/__init__.py +10 -0
- langfun/core/mcp/client.py +177 -0
- langfun/core/mcp/client_test.py +71 -0
- langfun/core/mcp/session.py +241 -0
- langfun/core/mcp/session_test.py +54 -0
- langfun/core/mcp/testing/simple_mcp_client.py +33 -0
- langfun/core/mcp/testing/simple_mcp_server.py +33 -0
- langfun/core/mcp/tool.py +256 -0
- 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 +62 -3
- 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/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 +74 -28
- langfun/core/structured/parsing.py +90 -74
- langfun/core/structured/parsing_test.py +0 -3
- langfun/core/structured/querying.py +242 -156
- langfun/core/structured/querying_test.py +95 -64
- langfun/core/structured/schema.py +70 -10
- langfun/core/structured/schema_generation.py +33 -14
- langfun/core/structured/scoring.py +45 -34
- langfun/core/structured/tokenization.py +24 -9
- langfun/core/subscription.py +2 -2
- langfun/core/template.py +175 -50
- langfun/core/template_test.py +123 -17
- langfun/env/__init__.py +43 -0
- langfun/env/base_environment.py +827 -0
- langfun/env/base_environment_test.py +473 -0
- langfun/env/base_feature.py +304 -0
- langfun/env/base_feature_test.py +228 -0
- langfun/env/base_sandbox.py +842 -0
- langfun/env/base_sandbox_test.py +1235 -0
- langfun/env/event_handlers/__init__.py +14 -0
- langfun/env/event_handlers/chain.py +233 -0
- langfun/env/event_handlers/chain_test.py +253 -0
- langfun/env/event_handlers/event_logger.py +472 -0
- langfun/env/event_handlers/event_logger_test.py +304 -0
- langfun/env/event_handlers/metric_writer.py +726 -0
- langfun/env/event_handlers/metric_writer_test.py +214 -0
- langfun/env/interface.py +1640 -0
- langfun/env/interface_test.py +151 -0
- langfun/env/load_balancers.py +59 -0
- langfun/env/load_balancers_test.py +139 -0
- langfun/env/test_utils.py +497 -0
- {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/METADATA +7 -3
- langfun-0.1.2.dev202511110805.dist-info/RECORD +200 -0
- langfun-0.1.2.dev202508250805.dist-info/RECORD +0 -172
- {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/top_level.txt +0 -0
langfun/core/agentic/action.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import abc
|
|
17
17
|
import contextlib
|
|
18
|
+
import dataclasses
|
|
18
19
|
import functools
|
|
19
20
|
import threading
|
|
20
21
|
import time
|
|
@@ -35,7 +36,12 @@ class ActionTimeoutError(ActionError):
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class Action(pg.Object):
|
|
38
|
-
"""Base class for
|
|
39
|
+
"""Base class for agentic actions.
|
|
40
|
+
|
|
41
|
+
An `Action` represents a single, executable step or task that an agent can
|
|
42
|
+
perform, such as calling a tool, querying a language model, or returning a
|
|
43
|
+
final answer. Actions are designed to be composable and trackable within a
|
|
44
|
+
`Session`.
|
|
39
45
|
|
|
40
46
|
# Developing Actions
|
|
41
47
|
|
|
@@ -148,7 +154,7 @@ class Action(pg.Object):
|
|
|
148
154
|
|
|
149
155
|
# Explicitly create and pass a session.
|
|
150
156
|
with lf.Session(id='my_agent_session') as session:
|
|
151
|
-
result = calc(session=session)
|
|
157
|
+
result = calc(session=session) # Pass the session explicitly
|
|
152
158
|
print(result)
|
|
153
159
|
```
|
|
154
160
|
|
|
@@ -187,11 +193,23 @@ class Action(pg.Object):
|
|
|
187
193
|
self._session = None
|
|
188
194
|
self._invocation: ActionInvocation | None = None
|
|
189
195
|
|
|
196
|
+
# NOTE(daiyip): Users could use `self._state` to keep track of the state
|
|
197
|
+
# during the execution of the action.
|
|
198
|
+
# Strictly speaking action state should better fit into
|
|
199
|
+
# ActionInvocation, we make it a property of Action as it's usually
|
|
200
|
+
# initialized before `__call__` is called.
|
|
201
|
+
self._state: Any = None
|
|
202
|
+
|
|
190
203
|
@property
|
|
191
204
|
def session(self) -> Optional['Session']:
|
|
192
205
|
"""Returns the session started by this action."""
|
|
193
206
|
return self._session
|
|
194
207
|
|
|
208
|
+
@property
|
|
209
|
+
def state(self) -> Any:
|
|
210
|
+
"""Returns the state of the action."""
|
|
211
|
+
return self._state
|
|
212
|
+
|
|
195
213
|
@property
|
|
196
214
|
def result(self) -> Any:
|
|
197
215
|
"""Returns the result of the action."""
|
|
@@ -307,7 +325,14 @@ TracedItem = Union[
|
|
|
307
325
|
|
|
308
326
|
|
|
309
327
|
class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
310
|
-
"""Trace of
|
|
328
|
+
"""Trace of an execution, containing queries, logs, and sub-actions.
|
|
329
|
+
|
|
330
|
+
`ExecutionTrace` records the sequence of operations performed during an
|
|
331
|
+
action's execution or within a specific phase of execution (demarcated by
|
|
332
|
+
`session.track_phase`). It captures `lf.query` calls, log entries, and
|
|
333
|
+
nested `ActionInvocation` objects in the order they occurred. It also
|
|
334
|
+
aggregates LLM usage summaries from its child items.
|
|
335
|
+
"""
|
|
311
336
|
|
|
312
337
|
name: Annotated[
|
|
313
338
|
str | None,
|
|
@@ -315,7 +340,7 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
315
340
|
'The name of the execution trace. If None, the trace is unnamed, '
|
|
316
341
|
'which is the case for the top-level trace of an action. An '
|
|
317
342
|
'execution trace could have sub-traces, called phases, which are '
|
|
318
|
-
'created and named by `session.
|
|
343
|
+
'created and named by `session.track_phase()` context manager.'
|
|
319
344
|
)
|
|
320
345
|
] = None
|
|
321
346
|
|
|
@@ -349,7 +374,7 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
349
374
|
self.__dict__.pop('id', None)
|
|
350
375
|
|
|
351
376
|
def indexof(self, item: TracedItem, count_item_cls: Type[Any]) -> int:
|
|
352
|
-
"""Returns the index of the child
|
|
377
|
+
"""Returns the index of the child item of given type."""
|
|
353
378
|
pos = 0
|
|
354
379
|
for x in self._iter_children(count_item_cls):
|
|
355
380
|
if x is item:
|
|
@@ -525,6 +550,18 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
525
550
|
remove_class=['not-started'],
|
|
526
551
|
)
|
|
527
552
|
|
|
553
|
+
def remove(self, item: TracedItem) -> None:
|
|
554
|
+
"""Removes an item from the sequence."""
|
|
555
|
+
index = self.items.index(item)
|
|
556
|
+
if index == -1:
|
|
557
|
+
raise ValueError(f'Item not found in execution trace: {item!r}')
|
|
558
|
+
|
|
559
|
+
with pg.notify_on_change(False):
|
|
560
|
+
self.items.pop(index)
|
|
561
|
+
|
|
562
|
+
if self._tab_control is not None:
|
|
563
|
+
self._tab_control.remove(index)
|
|
564
|
+
|
|
528
565
|
def extend(self, items: Iterable[TracedItem]) -> None:
|
|
529
566
|
"""Extends the sequence with a list of items."""
|
|
530
567
|
for item in items:
|
|
@@ -762,7 +799,12 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
762
799
|
|
|
763
800
|
|
|
764
801
|
class ParallelExecutions(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
765
|
-
"""A
|
|
802
|
+
"""A container for multiple parallel execution traces.
|
|
803
|
+
|
|
804
|
+
When `session.concurrent_map` is used, it creates a `ParallelExecutions`
|
|
805
|
+
object to hold an `ExecutionTrace` for each parallel branch of execution,
|
|
806
|
+
allowing inspection of parallel workflows.
|
|
807
|
+
"""
|
|
766
808
|
|
|
767
809
|
name: Annotated[
|
|
768
810
|
str | None,
|
|
@@ -851,8 +893,19 @@ class ParallelExecutions(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
851
893
|
|
|
852
894
|
|
|
853
895
|
class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
854
|
-
"""
|
|
855
|
-
|
|
896
|
+
"""An invocation of an action, capturing its execution and result.
|
|
897
|
+
|
|
898
|
+
`ActionInvocation` represents a single call to an `Action`. It contains
|
|
899
|
+
the `Action` object itself, its result or error, associated metadata,
|
|
900
|
+
and an `ExecutionTrace` detailing the steps taken during its execution
|
|
901
|
+
(queries, logs, sub-actions). Invocations form a tree structure within a
|
|
902
|
+
`Session`, reflecting the hierarchy of agentic operations.
|
|
903
|
+
"""
|
|
904
|
+
|
|
905
|
+
action: Annotated[
|
|
906
|
+
Action,
|
|
907
|
+
'The action being invoked.'
|
|
908
|
+
]
|
|
856
909
|
|
|
857
910
|
result: Annotated[
|
|
858
911
|
Any,
|
|
@@ -931,6 +984,11 @@ class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
931
984
|
"""Returns True if the action invocation has an error."""
|
|
932
985
|
return self.error is not None
|
|
933
986
|
|
|
987
|
+
@property
|
|
988
|
+
def state(self) -> Any:
|
|
989
|
+
"""Returns the state of the action."""
|
|
990
|
+
return self.action.state
|
|
991
|
+
|
|
934
992
|
@property
|
|
935
993
|
def logs(self) -> list[lf.logging.LogEntry]:
|
|
936
994
|
"""Returns immediate child logs from execution sequence."""
|
|
@@ -1140,26 +1198,314 @@ class RootAction(Action):
|
|
|
1140
1198
|
raise NotImplementedError('Shall not be called.')
|
|
1141
1199
|
|
|
1142
1200
|
|
|
1201
|
+
class SessionEventHandler:
|
|
1202
|
+
"""Interface for handling session events."""
|
|
1203
|
+
|
|
1204
|
+
def on_session_start(
|
|
1205
|
+
self,
|
|
1206
|
+
session: 'Session'
|
|
1207
|
+
) -> None:
|
|
1208
|
+
"""Called when a session starts."""
|
|
1209
|
+
|
|
1210
|
+
def on_session_end(
|
|
1211
|
+
self,
|
|
1212
|
+
session: 'Session'
|
|
1213
|
+
) -> None:
|
|
1214
|
+
"""Called when a session ends."""
|
|
1215
|
+
|
|
1216
|
+
def on_action_start(
|
|
1217
|
+
self,
|
|
1218
|
+
session: 'Session',
|
|
1219
|
+
action: ActionInvocation
|
|
1220
|
+
) -> None:
|
|
1221
|
+
"""Called when an action starts."""
|
|
1222
|
+
|
|
1223
|
+
def on_action_end(
|
|
1224
|
+
self,
|
|
1225
|
+
session: 'Session',
|
|
1226
|
+
action: ActionInvocation
|
|
1227
|
+
) -> None:
|
|
1228
|
+
"""Called when an action ends."""
|
|
1229
|
+
|
|
1230
|
+
def on_action_progress(
|
|
1231
|
+
self,
|
|
1232
|
+
session: 'Session',
|
|
1233
|
+
action: ActionInvocation,
|
|
1234
|
+
title: str,
|
|
1235
|
+
**kwargs
|
|
1236
|
+
) -> None:
|
|
1237
|
+
"""Called when an action progress is updated."""
|
|
1238
|
+
|
|
1239
|
+
def on_query_start(
|
|
1240
|
+
self,
|
|
1241
|
+
session: 'Session',
|
|
1242
|
+
action: ActionInvocation,
|
|
1243
|
+
query: lf_structured.QueryInvocation,
|
|
1244
|
+
) -> None:
|
|
1245
|
+
"""Called when a query starts."""
|
|
1246
|
+
|
|
1247
|
+
def on_query_end(
|
|
1248
|
+
self,
|
|
1249
|
+
session: 'Session',
|
|
1250
|
+
action: ActionInvocation,
|
|
1251
|
+
query: lf_structured.QueryInvocation,
|
|
1252
|
+
) -> None:
|
|
1253
|
+
"""Called when a query ends."""
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
@dataclasses.dataclass
|
|
1257
|
+
class SessionEventHandlerChain(SessionEventHandler):
|
|
1258
|
+
"""A session event handler that chains multiple event handlers."""
|
|
1259
|
+
|
|
1260
|
+
handlers: list[SessionEventHandler]
|
|
1261
|
+
|
|
1262
|
+
def on_session_start(self, session: 'Session') -> None:
|
|
1263
|
+
"""Called when a session starts."""
|
|
1264
|
+
for handler in self.handlers:
|
|
1265
|
+
handler.on_session_start(session)
|
|
1266
|
+
|
|
1267
|
+
def on_session_end(self, session: 'Session') -> None:
|
|
1268
|
+
"""Called when a session ends."""
|
|
1269
|
+
for handler in self.handlers:
|
|
1270
|
+
handler.on_session_end(session)
|
|
1271
|
+
|
|
1272
|
+
def on_action_start(
|
|
1273
|
+
self,
|
|
1274
|
+
session: 'Session',
|
|
1275
|
+
action: ActionInvocation) -> None:
|
|
1276
|
+
"""Called when an action starts."""
|
|
1277
|
+
for handler in self.handlers:
|
|
1278
|
+
handler.on_action_start(session, action)
|
|
1279
|
+
|
|
1280
|
+
def on_action_end(
|
|
1281
|
+
self,
|
|
1282
|
+
session: 'Session',
|
|
1283
|
+
action: ActionInvocation) -> None:
|
|
1284
|
+
"""Called when an action ends."""
|
|
1285
|
+
for handler in self.handlers:
|
|
1286
|
+
handler.on_action_end(session, action)
|
|
1287
|
+
|
|
1288
|
+
def on_action_progress(
|
|
1289
|
+
self,
|
|
1290
|
+
session: 'Session',
|
|
1291
|
+
action: ActionInvocation,
|
|
1292
|
+
title: str,
|
|
1293
|
+
**kwargs
|
|
1294
|
+
) -> None:
|
|
1295
|
+
"""Called when an action progress is updated."""
|
|
1296
|
+
for handler in self.handlers:
|
|
1297
|
+
handler.on_action_progress(session, action, title, **kwargs)
|
|
1298
|
+
|
|
1299
|
+
def on_query_start(
|
|
1300
|
+
self,
|
|
1301
|
+
session: 'Session',
|
|
1302
|
+
action: ActionInvocation,
|
|
1303
|
+
query: lf_structured.QueryInvocation,
|
|
1304
|
+
) -> None:
|
|
1305
|
+
"""Called when a query starts."""
|
|
1306
|
+
for handler in self.handlers:
|
|
1307
|
+
handler.on_query_start(session, action, query)
|
|
1308
|
+
|
|
1309
|
+
def on_query_end(
|
|
1310
|
+
self,
|
|
1311
|
+
session: 'Session',
|
|
1312
|
+
action: ActionInvocation,
|
|
1313
|
+
query: lf_structured.QueryInvocation,
|
|
1314
|
+
) -> None:
|
|
1315
|
+
"""Called when a query ends."""
|
|
1316
|
+
for handler in self.handlers:
|
|
1317
|
+
handler.on_query_end(session, action, query)
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
@dataclasses.dataclass
|
|
1321
|
+
class SessionLogging(SessionEventHandler):
|
|
1322
|
+
"""An event handler that logs Session events."""
|
|
1323
|
+
|
|
1324
|
+
verbose: bool = False
|
|
1325
|
+
|
|
1326
|
+
def on_session_end(self, session: 'Session'):
|
|
1327
|
+
if session.has_error:
|
|
1328
|
+
session.error(
|
|
1329
|
+
f'Trajectory failed in {session.elapse:.2f} seconds.',
|
|
1330
|
+
error=session.final_error,
|
|
1331
|
+
metadata=session.root.metadata,
|
|
1332
|
+
keep=True,
|
|
1333
|
+
)
|
|
1334
|
+
elif self.verbose:
|
|
1335
|
+
session.info(
|
|
1336
|
+
f'Trajectory succeeded in {session.elapse:.2f} seconds.',
|
|
1337
|
+
result=session.final_result,
|
|
1338
|
+
metadata=session.root.metadata,
|
|
1339
|
+
keep=False,
|
|
1340
|
+
)
|
|
1341
|
+
|
|
1342
|
+
def on_action_start(
|
|
1343
|
+
self,
|
|
1344
|
+
session: 'Session',
|
|
1345
|
+
action: ActionInvocation
|
|
1346
|
+
) -> None:
|
|
1347
|
+
if self.verbose:
|
|
1348
|
+
session.info(
|
|
1349
|
+
'Action execution started.',
|
|
1350
|
+
action=action.action,
|
|
1351
|
+
keep=False,
|
|
1352
|
+
)
|
|
1353
|
+
|
|
1354
|
+
def on_action_end(
|
|
1355
|
+
self,
|
|
1356
|
+
session: 'Session',
|
|
1357
|
+
action: ActionInvocation
|
|
1358
|
+
) -> None:
|
|
1359
|
+
if action.has_error:
|
|
1360
|
+
session.warning(
|
|
1361
|
+
(
|
|
1362
|
+
f'Action execution failed in '
|
|
1363
|
+
f'{action.execution.elapse:.2f} seconds.'
|
|
1364
|
+
),
|
|
1365
|
+
action=action.action,
|
|
1366
|
+
error=action.error,
|
|
1367
|
+
keep=True,
|
|
1368
|
+
)
|
|
1369
|
+
elif self.verbose:
|
|
1370
|
+
session.info(
|
|
1371
|
+
(
|
|
1372
|
+
f'Action execution succeeded in '
|
|
1373
|
+
f'{action.execution.elapse:.2f} seconds.'
|
|
1374
|
+
),
|
|
1375
|
+
action=action.action,
|
|
1376
|
+
result=action.result,
|
|
1377
|
+
keep=False,
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
def on_query_start(
|
|
1381
|
+
self,
|
|
1382
|
+
session: 'Session',
|
|
1383
|
+
action: ActionInvocation,
|
|
1384
|
+
query: lf_structured.QueryInvocation,
|
|
1385
|
+
) -> None:
|
|
1386
|
+
if self.verbose:
|
|
1387
|
+
session.info(
|
|
1388
|
+
'Querying LLM started.',
|
|
1389
|
+
lm=query.lm.model_id,
|
|
1390
|
+
output_type=(
|
|
1391
|
+
lf_structured.annotation(query.schema.spec)
|
|
1392
|
+
if query.schema is not None else None
|
|
1393
|
+
),
|
|
1394
|
+
keep=False,
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
def on_query_end(
|
|
1398
|
+
self,
|
|
1399
|
+
session: 'Session',
|
|
1400
|
+
action: ActionInvocation,
|
|
1401
|
+
query: lf_structured.QueryInvocation,
|
|
1402
|
+
) -> None:
|
|
1403
|
+
if query.has_error:
|
|
1404
|
+
session.warning(
|
|
1405
|
+
(
|
|
1406
|
+
f'Querying LLM failed in '
|
|
1407
|
+
f'{time.time() - query.start_time:.2f} seconds.'
|
|
1408
|
+
),
|
|
1409
|
+
lm=query.lm.model_id,
|
|
1410
|
+
output_type=(
|
|
1411
|
+
lf_structured.annotation(query.schema.spec)
|
|
1412
|
+
if query.schema is not None else None
|
|
1413
|
+
),
|
|
1414
|
+
error=query.error,
|
|
1415
|
+
keep=True,
|
|
1416
|
+
)
|
|
1417
|
+
elif self.verbose:
|
|
1418
|
+
session.info(
|
|
1419
|
+
(
|
|
1420
|
+
f'Querying LLM succeeded in '
|
|
1421
|
+
f'{time.time() - query.start_time:.2f} seconds.'
|
|
1422
|
+
),
|
|
1423
|
+
lm=query.lm.model_id,
|
|
1424
|
+
output_type=(
|
|
1425
|
+
lf_structured.annotation(query.schema.spec)
|
|
1426
|
+
if query.schema is not None else None
|
|
1427
|
+
),
|
|
1428
|
+
keep=False,
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1431
|
+
|
|
1143
1432
|
class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
1144
|
-
"""
|
|
1433
|
+
"""Manages the execution trajectory of agentic actions.
|
|
1434
|
+
|
|
1435
|
+
A `Session` tracks the execution of a root `Action` and all its
|
|
1436
|
+
sub-actions, including LLM queries (`lf.query`), logging messages,
|
|
1437
|
+
and nested actions. It provides a complete, hierarchical trace of an
|
|
1438
|
+
agent's workflow, which is important for debugging, analysis, and
|
|
1439
|
+
visualization.
|
|
1440
|
+
|
|
1441
|
+
Sessions can be created implicitly when an action is called without an
|
|
1442
|
+
active session, or explicitly for more control.
|
|
1443
|
+
|
|
1444
|
+
**1. Implicit Session:**
|
|
1445
|
+
When an action is called without a session, Langfun creates one automatically.
|
|
1446
|
+
|
|
1447
|
+
```python
|
|
1448
|
+
action = MyAction()
|
|
1449
|
+
action()
|
|
1450
|
+
session = action.session # Access the implicit session
|
|
1451
|
+
```
|
|
1452
|
+
|
|
1453
|
+
**2. Explicit Session:**
|
|
1454
|
+
Use a `with` statement to manage a session explicitly. This is useful for
|
|
1455
|
+
setting session IDs or capturing the trajectory of multiple top-level actions.
|
|
1456
|
+
|
|
1457
|
+
```python
|
|
1458
|
+
with lf.Session(id='my-session') as session:
|
|
1459
|
+
action1()
|
|
1460
|
+
action2()
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
**3. Accessing Trajectory:**
|
|
1464
|
+
The `session.root` attribute provides access to the `ActionInvocation` tree.
|
|
1465
|
+
|
|
1466
|
+
```python
|
|
1467
|
+
with lf.Session() as session:
|
|
1468
|
+
my_action()
|
|
1469
|
+
|
|
1470
|
+
# Get all queries in the session
|
|
1471
|
+
print(session.all_queries)
|
|
1472
|
+
|
|
1473
|
+
# Get all top-level action calls in the session
|
|
1474
|
+
print(session.root.actions)
|
|
1475
|
+
```
|
|
1476
|
+
"""
|
|
1145
1477
|
|
|
1146
1478
|
root: Annotated[
|
|
1147
1479
|
ActionInvocation,
|
|
1148
1480
|
'The root action invocation of the session.'
|
|
1149
|
-
]
|
|
1481
|
+
]
|
|
1150
1482
|
|
|
1151
1483
|
id: Annotated[
|
|
1152
1484
|
str | None,
|
|
1153
|
-
'An optional identifier for the
|
|
1154
|
-
]
|
|
1485
|
+
'An optional identifier for the session, which will be used for logging.'
|
|
1486
|
+
]
|
|
1155
1487
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1488
|
+
event_handler: Annotated[
|
|
1489
|
+
SessionEventHandler,
|
|
1490
|
+
'Event handler for the session.'
|
|
1491
|
+
]
|
|
1492
|
+
|
|
1493
|
+
@pg.explicit_method_override
|
|
1494
|
+
def __init__(
|
|
1495
|
+
self,
|
|
1496
|
+
id: str | None = None, # pylint: disable=redefined-builtin
|
|
1497
|
+
*,
|
|
1498
|
+
verbose: bool = False,
|
|
1499
|
+
event_handler: SessionEventHandler | None = None,
|
|
1500
|
+
root: ActionInvocation | None = None,
|
|
1501
|
+
**kwargs
|
|
1502
|
+
):
|
|
1503
|
+
super().__init__(
|
|
1504
|
+
id=id,
|
|
1505
|
+
root=root or ActionInvocation(RootAction()),
|
|
1506
|
+
event_handler=event_handler or SessionLogging(verbose=verbose),
|
|
1507
|
+
**kwargs
|
|
1508
|
+
)
|
|
1163
1509
|
|
|
1164
1510
|
#
|
|
1165
1511
|
# Shortcut methods for accessing the root action invocation.
|
|
@@ -1250,6 +1596,7 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1250
1596
|
def start(self) -> None:
|
|
1251
1597
|
"""Starts the session."""
|
|
1252
1598
|
self.root.execution.start()
|
|
1599
|
+
self.event_handler.on_session_start(self)
|
|
1253
1600
|
|
|
1254
1601
|
def end(
|
|
1255
1602
|
self,
|
|
@@ -1258,21 +1605,8 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1258
1605
|
metadata: dict[str, Any] | None = None,
|
|
1259
1606
|
) -> None:
|
|
1260
1607
|
"""Ends the session."""
|
|
1261
|
-
if error is not None:
|
|
1262
|
-
self.error(
|
|
1263
|
-
f'Trajectory failed in {self.elapse:.2f} seconds.',
|
|
1264
|
-
error=error,
|
|
1265
|
-
metadata=metadata,
|
|
1266
|
-
keep=True,
|
|
1267
|
-
)
|
|
1268
|
-
elif self.verbose:
|
|
1269
|
-
self.info(
|
|
1270
|
-
f'Trajectory succeeded in {self.elapse:.2f} seconds.',
|
|
1271
|
-
result=result,
|
|
1272
|
-
metadata=metadata,
|
|
1273
|
-
keep=False,
|
|
1274
|
-
)
|
|
1275
1608
|
self.root.end(result, error, metadata)
|
|
1609
|
+
self.event_handler.on_session_end(self)
|
|
1276
1610
|
|
|
1277
1611
|
def check_execution_time(self) -> None:
|
|
1278
1612
|
"""Checks the execution time of the current action."""
|
|
@@ -1291,6 +1625,20 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1291
1625
|
'seconds.'
|
|
1292
1626
|
)
|
|
1293
1627
|
|
|
1628
|
+
def update_progress(self, title: str, **kwargs: Any) -> None:
|
|
1629
|
+
"""Updates the progress of current action's execution.
|
|
1630
|
+
|
|
1631
|
+
Args:
|
|
1632
|
+
title: The title of the progress update.
|
|
1633
|
+
**kwargs: Additional keyword arguments to pass to the event handler.
|
|
1634
|
+
"""
|
|
1635
|
+
self.event_handler.on_action_progress(
|
|
1636
|
+
self,
|
|
1637
|
+
self._current_action,
|
|
1638
|
+
title,
|
|
1639
|
+
**kwargs
|
|
1640
|
+
)
|
|
1641
|
+
|
|
1294
1642
|
def __enter__(self):
|
|
1295
1643
|
"""Enters the session."""
|
|
1296
1644
|
self.start()
|
|
@@ -1350,34 +1698,10 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1350
1698
|
self._current_execution = invocation.execution
|
|
1351
1699
|
# Start the execution of the current action.
|
|
1352
1700
|
self._current_action.start()
|
|
1353
|
-
|
|
1354
|
-
self.info(
|
|
1355
|
-
'Action execution started.',
|
|
1356
|
-
action=invocation.action,
|
|
1357
|
-
keep=False,
|
|
1358
|
-
)
|
|
1701
|
+
self.event_handler.on_action_start(self, self._current_action)
|
|
1359
1702
|
yield invocation
|
|
1360
1703
|
finally:
|
|
1361
|
-
|
|
1362
|
-
self.warning(
|
|
1363
|
-
(
|
|
1364
|
-
f'Action execution failed in '
|
|
1365
|
-
f'{invocation.execution.elapse:.2f} seconds.'
|
|
1366
|
-
),
|
|
1367
|
-
action=invocation.action,
|
|
1368
|
-
error=invocation.error,
|
|
1369
|
-
keep=True,
|
|
1370
|
-
)
|
|
1371
|
-
elif self.verbose:
|
|
1372
|
-
self.info(
|
|
1373
|
-
(
|
|
1374
|
-
f'Action execution succeeded in '
|
|
1375
|
-
f'{invocation.execution.elapse:.2f} seconds.'
|
|
1376
|
-
),
|
|
1377
|
-
action=invocation.action,
|
|
1378
|
-
result=invocation.result,
|
|
1379
|
-
keep=False,
|
|
1380
|
-
)
|
|
1704
|
+
self.event_handler.on_action_end(self, self._current_action)
|
|
1381
1705
|
self._current_execution = parent_execution
|
|
1382
1706
|
self._current_action = parent_action
|
|
1383
1707
|
|
|
@@ -1403,13 +1727,20 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1403
1727
|
@contextlib.contextmanager
|
|
1404
1728
|
def track_queries(
|
|
1405
1729
|
self,
|
|
1406
|
-
phase: str | None = None
|
|
1730
|
+
phase: str | None = None,
|
|
1731
|
+
track_if: Callable[
|
|
1732
|
+
[lf_structured.QueryInvocation],
|
|
1733
|
+
bool
|
|
1734
|
+
] | None = None,
|
|
1407
1735
|
) -> Iterator[list[lf_structured.QueryInvocation]]:
|
|
1408
1736
|
"""Tracks `lf.query` made within the context.
|
|
1409
1737
|
|
|
1410
1738
|
Args:
|
|
1411
1739
|
phase: The name of a new phase to track the queries in. If not provided,
|
|
1412
1740
|
the queries will be tracked in the parent phase.
|
|
1741
|
+
track_if: A function that takes a `lf_structured.QueryInvocation` and
|
|
1742
|
+
returns True if the query should be included in the result. If None,
|
|
1743
|
+
all queries (including failed queries) will be included.
|
|
1413
1744
|
|
|
1414
1745
|
Yields:
|
|
1415
1746
|
A list of `lf.QueryInvocation` objects, each for a single `lf.query`
|
|
@@ -1425,51 +1756,21 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1425
1756
|
skip_notification=False, raise_on_no_change=False
|
|
1426
1757
|
)
|
|
1427
1758
|
execution.append(invocation)
|
|
1428
|
-
|
|
1429
|
-
self.info(
|
|
1430
|
-
'Querying LLM started.',
|
|
1431
|
-
lm=invocation.lm.model_id,
|
|
1432
|
-
output_type=(
|
|
1433
|
-
lf_structured.annotation(invocation.schema.spec)
|
|
1434
|
-
if invocation.schema is not None else None
|
|
1435
|
-
),
|
|
1436
|
-
keep=False,
|
|
1437
|
-
)
|
|
1759
|
+
self.event_handler.on_query_start(self, self._current_action, invocation)
|
|
1438
1760
|
|
|
1439
1761
|
def _query_end(invocation: lf_structured.QueryInvocation):
|
|
1762
|
+
if track_if is not None and not track_if(invocation):
|
|
1763
|
+
self._current_execution.remove(invocation)
|
|
1764
|
+
# Even if the query is not included in the execution trace, we still
|
|
1765
|
+
# count the usage summary to the current execution and trigger the
|
|
1766
|
+
# event handler to log the query.
|
|
1440
1767
|
self._current_execution.merge_usage_summary(invocation.usage_summary)
|
|
1441
|
-
|
|
1442
|
-
self.warning(
|
|
1443
|
-
(
|
|
1444
|
-
f'Querying LLM failed in '
|
|
1445
|
-
f'{time.time() - invocation.start_time:.2f} seconds.'
|
|
1446
|
-
),
|
|
1447
|
-
lm=invocation.lm.model_id,
|
|
1448
|
-
output_type=(
|
|
1449
|
-
lf_structured.annotation(invocation.schema.spec)
|
|
1450
|
-
if invocation.schema is not None else None
|
|
1451
|
-
),
|
|
1452
|
-
error=invocation.error,
|
|
1453
|
-
keep=True,
|
|
1454
|
-
)
|
|
1455
|
-
elif self.verbose:
|
|
1456
|
-
self.info(
|
|
1457
|
-
(
|
|
1458
|
-
f'Querying LLM succeeded in '
|
|
1459
|
-
f'{time.time() - invocation.start_time:.2f} seconds.'
|
|
1460
|
-
),
|
|
1461
|
-
lm=invocation.lm.model_id,
|
|
1462
|
-
output_type=(
|
|
1463
|
-
lf_structured.annotation(invocation.schema.spec)
|
|
1464
|
-
if invocation.schema is not None else None
|
|
1465
|
-
),
|
|
1466
|
-
keep=False,
|
|
1467
|
-
)
|
|
1768
|
+
self.event_handler.on_query_end(self, self._current_action, invocation)
|
|
1468
1769
|
|
|
1469
1770
|
with self.track_phase(phase), lf_structured.track_queries(
|
|
1470
1771
|
include_child_scopes=False,
|
|
1471
|
-
|
|
1472
|
-
|
|
1772
|
+
start_callback=_query_start,
|
|
1773
|
+
end_callback=_query_end,
|
|
1473
1774
|
) as queries:
|
|
1474
1775
|
try:
|
|
1475
1776
|
yield queries
|
|
@@ -1495,8 +1796,9 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1495
1796
|
*,
|
|
1496
1797
|
lm: lf.LanguageModel,
|
|
1497
1798
|
examples: list[lf_structured.MappingExample] | None = None,
|
|
1799
|
+
track_if: Callable[[lf_structured.QueryInvocation], bool] | None = None,
|
|
1498
1800
|
**kwargs
|
|
1499
|
-
|
|
1801
|
+
) -> Any:
|
|
1500
1802
|
"""Calls `lf.query` and associates it with the current invocation.
|
|
1501
1803
|
|
|
1502
1804
|
The following code are equivalent:
|
|
@@ -1521,12 +1823,15 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1521
1823
|
default: The default value to return if the query fails.
|
|
1522
1824
|
lm: The language model to use for the query.
|
|
1523
1825
|
examples: The examples to use for the query.
|
|
1826
|
+
track_if: A function that takes a `lf_structured.QueryInvocation`
|
|
1827
|
+
and returns True if the query should be tracked.
|
|
1828
|
+
If None, all queries (including failed queries) will be tracked.
|
|
1524
1829
|
**kwargs: Additional keyword arguments to pass to `lf.query`.
|
|
1525
1830
|
|
|
1526
1831
|
Returns:
|
|
1527
1832
|
The result of the query.
|
|
1528
1833
|
"""
|
|
1529
|
-
with self.track_queries():
|
|
1834
|
+
with self.track_queries(track_if=track_if):
|
|
1530
1835
|
return lf_structured.query(
|
|
1531
1836
|
prompt,
|
|
1532
1837
|
schema=schema,
|
|
@@ -1546,7 +1851,9 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1546
1851
|
timeout: int | None = None,
|
|
1547
1852
|
silence_on_errors: Union[
|
|
1548
1853
|
Type[BaseException], tuple[Type[BaseException], ...], None
|
|
1549
|
-
] = Exception
|
|
1854
|
+
] = Exception,
|
|
1855
|
+
ordered: bool = False,
|
|
1856
|
+
show_progress: bool | int = False,
|
|
1550
1857
|
) -> Iterator[Any]:
|
|
1551
1858
|
"""Starts and tracks parallel execution with `lf.concurrent_map`."""
|
|
1552
1859
|
self.check_execution_time()
|
|
@@ -1574,7 +1881,9 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
|
|
|
1574
1881
|
parallel_inputs,
|
|
1575
1882
|
max_workers=max_workers,
|
|
1576
1883
|
timeout=self._child_max_execution_time(timeout),
|
|
1577
|
-
silence_on_errors=silence_on_errors
|
|
1884
|
+
silence_on_errors=silence_on_errors,
|
|
1885
|
+
ordered=ordered,
|
|
1886
|
+
show_progress=show_progress,
|
|
1578
1887
|
):
|
|
1579
1888
|
yield input_value, result, error
|
|
1580
1889
|
|