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.

Files changed (133) hide show
  1. langfun/__init__.py +1 -1
  2. langfun/core/__init__.py +6 -1
  3. langfun/core/agentic/__init__.py +4 -0
  4. langfun/core/agentic/action.py +412 -103
  5. langfun/core/agentic/action_eval.py +9 -2
  6. langfun/core/agentic/action_test.py +68 -6
  7. langfun/core/async_support.py +104 -5
  8. langfun/core/async_support_test.py +23 -0
  9. langfun/core/coding/python/correction.py +19 -9
  10. langfun/core/coding/python/execution.py +14 -12
  11. langfun/core/coding/python/generation.py +21 -16
  12. langfun/core/coding/python/sandboxing.py +23 -3
  13. langfun/core/component.py +42 -3
  14. langfun/core/concurrent.py +70 -6
  15. langfun/core/concurrent_test.py +9 -2
  16. langfun/core/console.py +1 -1
  17. langfun/core/data/conversion/anthropic.py +12 -3
  18. langfun/core/data/conversion/anthropic_test.py +8 -6
  19. langfun/core/data/conversion/gemini.py +9 -2
  20. langfun/core/data/conversion/gemini_test.py +12 -9
  21. langfun/core/data/conversion/openai.py +145 -31
  22. langfun/core/data/conversion/openai_test.py +161 -17
  23. langfun/core/eval/base.py +47 -43
  24. langfun/core/eval/base_test.py +4 -4
  25. langfun/core/eval/matching.py +5 -2
  26. langfun/core/eval/patching.py +3 -3
  27. langfun/core/eval/scoring.py +4 -3
  28. langfun/core/eval/v2/__init__.py +1 -0
  29. langfun/core/eval/v2/checkpointing.py +30 -4
  30. langfun/core/eval/v2/eval_test_helper.py +1 -1
  31. langfun/core/eval/v2/evaluation.py +60 -14
  32. langfun/core/eval/v2/example.py +22 -11
  33. langfun/core/eval/v2/experiment.py +51 -8
  34. langfun/core/eval/v2/metric_values.py +31 -3
  35. langfun/core/eval/v2/metric_values_test.py +32 -0
  36. langfun/core/eval/v2/metrics.py +39 -4
  37. langfun/core/eval/v2/metrics_test.py +14 -0
  38. langfun/core/eval/v2/progress.py +30 -1
  39. langfun/core/eval/v2/progress_test.py +27 -0
  40. langfun/core/eval/v2/progress_tracking_test.py +6 -0
  41. langfun/core/eval/v2/reporting.py +90 -71
  42. langfun/core/eval/v2/reporting_test.py +20 -6
  43. langfun/core/eval/v2/runners.py +27 -7
  44. langfun/core/eval/v2/runners_test.py +3 -0
  45. langfun/core/langfunc.py +45 -130
  46. langfun/core/langfunc_test.py +6 -4
  47. langfun/core/language_model.py +151 -31
  48. langfun/core/language_model_test.py +9 -3
  49. langfun/core/llms/__init__.py +12 -1
  50. langfun/core/llms/anthropic.py +157 -2
  51. langfun/core/llms/azure_openai.py +29 -17
  52. langfun/core/llms/cache/base.py +25 -3
  53. langfun/core/llms/cache/in_memory.py +48 -7
  54. langfun/core/llms/cache/in_memory_test.py +14 -4
  55. langfun/core/llms/compositional.py +25 -1
  56. langfun/core/llms/deepseek.py +30 -2
  57. langfun/core/llms/fake.py +39 -1
  58. langfun/core/llms/fake_test.py +9 -0
  59. langfun/core/llms/gemini.py +43 -7
  60. langfun/core/llms/google_genai.py +34 -1
  61. langfun/core/llms/groq.py +28 -3
  62. langfun/core/llms/llama_cpp.py +23 -4
  63. langfun/core/llms/openai.py +93 -3
  64. langfun/core/llms/openai_compatible.py +148 -27
  65. langfun/core/llms/openai_compatible_test.py +207 -20
  66. langfun/core/llms/openai_test.py +0 -2
  67. langfun/core/llms/rest.py +16 -1
  68. langfun/core/llms/vertexai.py +59 -8
  69. langfun/core/logging.py +1 -1
  70. langfun/core/mcp/__init__.py +10 -0
  71. langfun/core/mcp/client.py +177 -0
  72. langfun/core/mcp/client_test.py +71 -0
  73. langfun/core/mcp/session.py +241 -0
  74. langfun/core/mcp/session_test.py +54 -0
  75. langfun/core/mcp/testing/simple_mcp_client.py +33 -0
  76. langfun/core/mcp/testing/simple_mcp_server.py +33 -0
  77. langfun/core/mcp/tool.py +256 -0
  78. langfun/core/mcp/tool_test.py +197 -0
  79. langfun/core/memory.py +1 -0
  80. langfun/core/message.py +160 -55
  81. langfun/core/message_test.py +65 -81
  82. langfun/core/modalities/__init__.py +8 -0
  83. langfun/core/modalities/audio.py +21 -1
  84. langfun/core/modalities/image.py +19 -1
  85. langfun/core/modalities/mime.py +62 -3
  86. langfun/core/modalities/pdf.py +19 -1
  87. langfun/core/modalities/video.py +21 -1
  88. langfun/core/modality.py +167 -29
  89. langfun/core/modality_test.py +42 -12
  90. langfun/core/natural_language.py +1 -1
  91. langfun/core/sampling.py +4 -4
  92. langfun/core/sampling_test.py +20 -4
  93. langfun/core/structured/completion.py +34 -44
  94. langfun/core/structured/completion_test.py +23 -43
  95. langfun/core/structured/description.py +54 -50
  96. langfun/core/structured/function_generation.py +29 -12
  97. langfun/core/structured/mapping.py +74 -28
  98. langfun/core/structured/parsing.py +90 -74
  99. langfun/core/structured/parsing_test.py +0 -3
  100. langfun/core/structured/querying.py +242 -156
  101. langfun/core/structured/querying_test.py +95 -64
  102. langfun/core/structured/schema.py +70 -10
  103. langfun/core/structured/schema_generation.py +33 -14
  104. langfun/core/structured/scoring.py +45 -34
  105. langfun/core/structured/tokenization.py +24 -9
  106. langfun/core/subscription.py +2 -2
  107. langfun/core/template.py +175 -50
  108. langfun/core/template_test.py +123 -17
  109. langfun/env/__init__.py +43 -0
  110. langfun/env/base_environment.py +827 -0
  111. langfun/env/base_environment_test.py +473 -0
  112. langfun/env/base_feature.py +304 -0
  113. langfun/env/base_feature_test.py +228 -0
  114. langfun/env/base_sandbox.py +842 -0
  115. langfun/env/base_sandbox_test.py +1235 -0
  116. langfun/env/event_handlers/__init__.py +14 -0
  117. langfun/env/event_handlers/chain.py +233 -0
  118. langfun/env/event_handlers/chain_test.py +253 -0
  119. langfun/env/event_handlers/event_logger.py +472 -0
  120. langfun/env/event_handlers/event_logger_test.py +304 -0
  121. langfun/env/event_handlers/metric_writer.py +726 -0
  122. langfun/env/event_handlers/metric_writer_test.py +214 -0
  123. langfun/env/interface.py +1640 -0
  124. langfun/env/interface_test.py +151 -0
  125. langfun/env/load_balancers.py +59 -0
  126. langfun/env/load_balancers_test.py +139 -0
  127. langfun/env/test_utils.py +497 -0
  128. {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/METADATA +7 -3
  129. langfun-0.1.2.dev202511110805.dist-info/RECORD +200 -0
  130. langfun-0.1.2.dev202508250805.dist-info/RECORD +0 -172
  131. {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/WHEEL +0 -0
  132. {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/licenses/LICENSE +0 -0
  133. {langfun-0.1.2.dev202508250805.dist-info → langfun-0.1.2.dev202511110805.dist-info}/top_level.txt +0 -0
@@ -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 Langfun's agentic actions.
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) # Pass the session explicitly
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 the execution of an action."""
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.phase()` context manager.'
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 items of given type."""
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 class for encapsulating parallel execution traces."""
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
- """A class for capturing the invocation of an action."""
855
- action: Action
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
- """Session for performing an agentic task."""
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
- ] = ActionInvocation(RootAction())
1481
+ ]
1150
1482
 
1151
1483
  id: Annotated[
1152
1484
  str | None,
1153
- 'An optional identifier for the sessin, which will be used for logging.'
1154
- ] = None
1485
+ 'An optional identifier for the session, which will be used for logging.'
1486
+ ]
1155
1487
 
1156
- verbose: Annotated[
1157
- bool,
1158
- (
1159
- 'If True, the session will be logged with verbose action and query '
1160
- 'activities.'
1161
- )
1162
- ] = False
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
- if self.verbose:
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
- if invocation.has_error:
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
- if self.verbose:
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
- if invocation.has_error:
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
- start_callabck=_query_start,
1472
- end_callabck=_query_end,
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
- ) -> Any:
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