openai-sdk-helpers 0.2.0__py3-none-any.whl → 0.4.0__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.
Files changed (58) hide show
  1. openai_sdk_helpers/__init__.py +6 -6
  2. openai_sdk_helpers/agent/__init__.py +4 -2
  3. openai_sdk_helpers/agent/base.py +391 -106
  4. openai_sdk_helpers/agent/config.py +405 -44
  5. openai_sdk_helpers/agent/coordination.py +68 -31
  6. openai_sdk_helpers/agent/runner.py +29 -19
  7. openai_sdk_helpers/agent/search/base.py +103 -54
  8. openai_sdk_helpers/agent/search/vector.py +99 -68
  9. openai_sdk_helpers/agent/search/web.py +84 -50
  10. openai_sdk_helpers/agent/summarizer.py +33 -7
  11. openai_sdk_helpers/agent/translator.py +58 -24
  12. openai_sdk_helpers/agent/validation.py +35 -4
  13. openai_sdk_helpers/cli.py +42 -0
  14. openai_sdk_helpers/config.py +0 -1
  15. openai_sdk_helpers/environment.py +3 -2
  16. openai_sdk_helpers/files_api.py +35 -3
  17. openai_sdk_helpers/prompt/base.py +6 -0
  18. openai_sdk_helpers/response/__init__.py +3 -3
  19. openai_sdk_helpers/response/base.py +161 -22
  20. openai_sdk_helpers/response/config.py +50 -200
  21. openai_sdk_helpers/response/files.py +5 -5
  22. openai_sdk_helpers/response/messages.py +3 -3
  23. openai_sdk_helpers/response/runner.py +7 -7
  24. openai_sdk_helpers/response/tool_call.py +94 -4
  25. openai_sdk_helpers/response/vector_store.py +3 -3
  26. openai_sdk_helpers/streamlit_app/app.py +16 -16
  27. openai_sdk_helpers/streamlit_app/config.py +38 -37
  28. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +2 -2
  29. openai_sdk_helpers/structure/__init__.py +6 -2
  30. openai_sdk_helpers/structure/agent_blueprint.py +2 -2
  31. openai_sdk_helpers/structure/base.py +8 -99
  32. openai_sdk_helpers/structure/plan/plan.py +2 -2
  33. openai_sdk_helpers/structure/plan/task.py +9 -9
  34. openai_sdk_helpers/structure/prompt.py +2 -2
  35. openai_sdk_helpers/structure/responses.py +15 -15
  36. openai_sdk_helpers/structure/summary.py +3 -3
  37. openai_sdk_helpers/structure/translation.py +32 -0
  38. openai_sdk_helpers/structure/validation.py +2 -2
  39. openai_sdk_helpers/structure/vector_search.py +7 -7
  40. openai_sdk_helpers/structure/web_search.py +6 -6
  41. openai_sdk_helpers/tools.py +41 -15
  42. openai_sdk_helpers/utils/__init__.py +19 -5
  43. openai_sdk_helpers/utils/instructions.py +35 -0
  44. openai_sdk_helpers/utils/json/__init__.py +55 -0
  45. openai_sdk_helpers/utils/json/base_model.py +181 -0
  46. openai_sdk_helpers/utils/{json_utils.py → json/data_class.py} +43 -70
  47. openai_sdk_helpers/utils/json/ref.py +113 -0
  48. openai_sdk_helpers/utils/json/utils.py +203 -0
  49. openai_sdk_helpers/utils/output_validation.py +21 -1
  50. openai_sdk_helpers/utils/path_utils.py +34 -1
  51. openai_sdk_helpers/utils/registry.py +194 -0
  52. openai_sdk_helpers/vector_storage/storage.py +10 -0
  53. {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/METADATA +7 -7
  54. openai_sdk_helpers-0.4.0.dist-info/RECORD +86 -0
  55. openai_sdk_helpers-0.2.0.dist-info/RECORD +0 -79
  56. {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/WHEEL +0 -0
  57. {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/entry_points.txt +0 -0
  58. {openai_sdk_helpers-0.2.0.dist-info → openai_sdk_helpers-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,43 +3,126 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
- from typing import Any, Dict, Optional, Protocol
7
-
8
- from agents import Agent, RunResult, RunResultStreaming, Runner
6
+ from typing import Any, Dict, Optional, Protocol, cast
7
+ import uuid
8
+
9
+ from agents import (
10
+ Agent,
11
+ Handoff,
12
+ InputGuardrail,
13
+ OutputGuardrail,
14
+ RunResultStreaming,
15
+ Session,
16
+ )
17
+ from agents.model_settings import ModelSettings
9
18
  from agents.run_context import RunContextWrapper
10
- from agents.tool import FunctionTool
19
+ from agents.tool import Tool
11
20
  from jinja2 import Template
12
21
 
13
- from .runner import run_async, run_streamed, run_sync
22
+ from ..utils.json.data_class import DataclassJSONSerializable
23
+ from ..structure.base import StructureBase
14
24
 
25
+ from ..utils import (
26
+ check_filepath,
27
+ log,
28
+ )
15
29
 
16
- class AgentConfigLike(Protocol):
17
- """Protocol describing the configuration attributes for AgentBase."""
30
+ from .runner import run_async, run_streamed, run_sync
18
31
 
19
- name: str
20
- description: Optional[str]
21
- model: Optional[str]
22
- template_path: Optional[str]
23
- input_type: Optional[Any]
24
- output_type: Optional[Any]
25
- tools: Optional[Any]
26
- model_settings: Optional[Any]
27
32
 
33
+ class AgentConfigurationLike(Protocol):
34
+ """Protocol describing the configuration attributes for AgentBase."""
28
35
 
29
- class AgentBase:
36
+ @property
37
+ def name(self) -> str:
38
+ """Agent name."""
39
+ ...
40
+
41
+ @property
42
+ def description(self) -> Optional[str]:
43
+ """Agent description."""
44
+ ...
45
+
46
+ @property
47
+ def model(self) -> Optional[str]:
48
+ """Model identifier."""
49
+ ...
50
+
51
+ @property
52
+ def template_path(self) -> Optional[str | Path]:
53
+ """Template path."""
54
+ ...
55
+
56
+ def resolve_prompt_path(self, prompt_dir: Path | None = None) -> Path | None:
57
+ """Resolve the prompt template path."""
58
+ ...
59
+
60
+ @property
61
+ def instructions(self) -> str | Path:
62
+ """Instructions."""
63
+ ...
64
+
65
+ @property
66
+ def instructions_text(self) -> str:
67
+ """Resolved instructions text."""
68
+ ...
69
+
70
+ @property
71
+ def input_structure(self) -> Optional[type[StructureBase]]:
72
+ """Input type."""
73
+ ...
74
+
75
+ @property
76
+ def output_structure(self) -> Optional[type[StructureBase]]:
77
+ """Output type."""
78
+ ...
79
+
80
+ @property
81
+ def tools(self) -> Optional[list]:
82
+ """Tools."""
83
+ ...
84
+
85
+ @property
86
+ def model_settings(self) -> Optional[ModelSettings]:
87
+ """Model settings."""
88
+ ...
89
+
90
+ @property
91
+ def handoffs(self) -> Optional[list[Agent | Handoff]]:
92
+ """Handoffs."""
93
+ ...
94
+
95
+ @property
96
+ def input_guardrails(self) -> Optional[list[InputGuardrail]]:
97
+ """Input guardrails."""
98
+ ...
99
+
100
+ @property
101
+ def output_guardrails(self) -> Optional[list[OutputGuardrail]]:
102
+ """Output guardrails."""
103
+ ...
104
+
105
+ @property
106
+ def session(self) -> Optional[Session]:
107
+ """Session."""
108
+ ...
109
+
110
+
111
+ class AgentBase(DataclassJSONSerializable):
30
112
  """Factory for creating and configuring specialized agents.
31
113
 
32
114
  ``AgentBase`` provides the foundation for building OpenAI agents with support
33
- for Jinja2 prompt templates, custom tools, and both synchronous and
34
- asynchronous execution modes. All specialized agents in this package extend
35
- this base class.
115
+ for Jinja2 prompt templates, custom tools, handoffs for agent delegation,
116
+ input and output guardrails for validation, session management for
117
+ conversation history, and both synchronous and asynchronous execution modes.
118
+ All specialized agents in this package extend this base class.
36
119
 
37
120
  Examples
38
121
  --------
39
122
  Create a basic agent from configuration:
40
123
 
41
- >>> from openai_sdk_helpers.agent import AgentBase, AgentConfig
42
- >>> config = AgentConfig(
124
+ >>> from openai_sdk_helpers.agent import AgentBase, AgentConfiguration
125
+ >>> config = AgentConfiguration(
43
126
  ... name="my_agent",
44
127
  ... description="A custom agent",
45
128
  ... model="gpt-4o-mini"
@@ -49,7 +132,7 @@ class AgentBase:
49
132
 
50
133
  Use absolute path to template:
51
134
 
52
- >>> config = AgentConfig(
135
+ >>> config = AgentConfiguration(
53
136
  ... name="my_agent",
54
137
  ... template_path="/absolute/path/to/template.jinja",
55
138
  ... model="gpt-4o-mini"
@@ -66,30 +149,48 @@ class AgentBase:
66
149
 
67
150
  Methods
68
151
  -------
69
- from_config(config, run_context_wrapper)
70
- Instantiate a ``AgentBase`` from configuration.
71
152
  build_prompt_from_jinja(run_context_wrapper)
72
153
  Render the agent prompt using Jinja and optional context.
73
154
  get_prompt(run_context_wrapper, _)
74
155
  Render the agent prompt using the provided run context.
156
+ name
157
+ Return the name of this agent.
158
+ instructions_text
159
+ Return the resolved instructions for this agent.
160
+ tools
161
+ Return the tools configured for this agent.
162
+ output_structure
163
+ Return the output type configured for this agent.
164
+ model_settings
165
+ Return the model settings configured for this agent.
166
+ handoffs
167
+ Return the handoff configurations for this agent.
168
+ input_guardrails
169
+ Return the input guardrails configured for this agent.
170
+ output_guardrails
171
+ Return the output guardrails configured for this agent.
172
+ session
173
+ Return the session configured for this agent.
75
174
  get_agent()
76
175
  Construct the configured :class:`agents.Agent` instance.
77
- run(input, context, output_type)
78
- Execute the agent asynchronously (alias of ``run_async``).
79
- run_async(input, context, output_type)
176
+ run_async(input, context, output_structure, session)
80
177
  Execute the agent asynchronously and optionally cast the result.
81
- run_sync(input, context, output_type)
178
+ run_sync(input, context, output_structure, session)
82
179
  Execute the agent synchronously.
83
- run_streamed(input, context, output_type)
180
+ run_streamed(input, context, output_structure, session)
84
181
  Return a streaming result for the agent execution.
85
182
  as_tool()
86
183
  Return the agent as a callable tool.
184
+ close()
185
+ Clean up agent resources (can be overridden by subclasses).
87
186
  """
88
187
 
89
188
  def __init__(
90
189
  self,
91
- config: AgentConfigLike,
190
+ *,
191
+ config: AgentConfigurationLike,
92
192
  run_context_wrapper: Optional[RunContextWrapper[Dict[str, Any]]] = None,
193
+ data_path: Path | str | None = None,
93
194
  prompt_dir: Optional[Path] = None,
94
195
  default_model: Optional[str] = None,
95
196
  ) -> None:
@@ -97,7 +198,7 @@ class AgentBase:
97
198
 
98
199
  Parameters
99
200
  ----------
100
- config : AgentConfigLike
201
+ config : AgentConfigurationLike
101
202
  Configuration describing this agent.
102
203
  run_context_wrapper : RunContextWrapper or None, default=None
103
204
  Optional wrapper providing runtime context for prompt rendering.
@@ -115,70 +216,49 @@ class AgentBase:
115
216
  if not model:
116
217
  raise ValueError("Model is required to construct the agent.")
117
218
 
118
- prompt_path: Optional[Path]
119
- if config.template_path:
120
- prompt_path = Path(config.template_path)
121
- elif prompt_dir is not None:
122
- prompt_path = prompt_dir / f"{name}.jinja"
123
- else:
124
- prompt_path = None
219
+ prompt_path = config.resolve_prompt_path(prompt_dir)
125
220
 
221
+ # Build template from file or fall back to instructions
126
222
  if prompt_path is None:
127
- self._template = Template("")
223
+ instructions_text = config.instructions_text
224
+ self._template = Template(instructions_text)
225
+ self._instructions = instructions_text
128
226
  elif prompt_path.exists():
129
- self._template = Template(prompt_path.read_text())
227
+ self._template = Template(prompt_path.read_text(encoding="utf-8"))
228
+ self._instructions = None
130
229
  else:
131
230
  raise FileNotFoundError(
132
231
  f"Prompt template for agent '{name}' not found at {prompt_path}."
133
232
  )
134
233
 
135
- self.agent_name = name
234
+ self._name = name
235
+ self.uuid = uuid.uuid4()
136
236
  self.description = description
137
237
  self.model = model
138
238
 
139
- self._input_type = config.input_type
140
- self._output_type = config.output_type or config.input_type
239
+ # Resolve data_path with class name appended
240
+ class_name = self.__class__.__name__
241
+ if data_path is not None:
242
+ data_path_obj = Path(data_path)
243
+ if data_path_obj.name == class_name:
244
+ self._data_path = data_path_obj
245
+ else:
246
+ self._data_path = data_path_obj / class_name
247
+ else:
248
+ from ..environment import get_data_path
249
+
250
+ self._data_path = get_data_path(self.__class__.__name__)
251
+
252
+ self._input_structure = config.input_structure
253
+ self._output_structure = config.output_structure or config.input_structure
141
254
  self._tools = config.tools
142
255
  self._model_settings = config.model_settings
256
+ self._handoffs = config.handoffs
257
+ self._input_guardrails = config.input_guardrails
258
+ self._output_guardrails = config.output_guardrails
259
+ self._session = config.session
143
260
  self._run_context_wrapper = run_context_wrapper
144
261
 
145
- @classmethod
146
- def from_config(
147
- cls,
148
- config: AgentConfigLike,
149
- *,
150
- run_context_wrapper: Optional[RunContextWrapper[Dict[str, Any]]] = None,
151
- prompt_dir: Optional[Path] = None,
152
- default_model: Optional[str] = None,
153
- ) -> AgentBase:
154
- """Create an AgentBase instance from configuration.
155
-
156
- Parameters
157
- ----------
158
- config : AgentConfigLike
159
- Configuration describing the agent.
160
- run_context_wrapper : RunContextWrapper or None, default=None
161
- Optional wrapper providing runtime context.
162
- prompt_dir : Path or None, default=None
163
- Optional directory holding prompt templates. Used when
164
- ``config.template_path`` is not provided or is relative. If
165
- ``config.template_path`` is an absolute path, this parameter is
166
- ignored.
167
- default_model : str or None, default=None
168
- Optional fallback model identifier.
169
-
170
- Returns
171
- -------
172
- AgentBase
173
- Instantiated agent.
174
- """
175
- return cls(
176
- config=config,
177
- run_context_wrapper=run_context_wrapper,
178
- prompt_dir=prompt_dir,
179
- default_model=default_model,
180
- )
181
-
182
262
  def _build_prompt_from_jinja(self) -> str:
183
263
  """Render the instructions prompt for this agent.
184
264
 
@@ -233,6 +313,107 @@ class AgentBase:
233
313
  """
234
314
  return self.build_prompt_from_jinja(run_context_wrapper)
235
315
 
316
+ @property
317
+ def name(self) -> str:
318
+ """Return the name of this agent.
319
+
320
+ Returns
321
+ -------
322
+ str
323
+ Name used to identify the agent.
324
+ """
325
+ return self._name
326
+
327
+ @property
328
+ def instructions_text(self) -> str:
329
+ """Return the resolved instructions for this agent.
330
+
331
+ Returns
332
+ -------
333
+ str
334
+ Rendered instructions text using the current run context.
335
+ """
336
+ if self._instructions is not None:
337
+ return self._instructions
338
+ return self._build_prompt_from_jinja()
339
+
340
+ @property
341
+ def tools(self) -> Optional[list]:
342
+ """Return the tools configured for this agent.
343
+
344
+ Returns
345
+ -------
346
+ list or None
347
+ Tool definitions configured for the agent.
348
+ """
349
+ return self._tools
350
+
351
+ @property
352
+ def output_structure(self) -> Optional[type[StructureBase]]:
353
+ """Return the output type configured for this agent.
354
+
355
+ Returns
356
+ -------
357
+ type[StructureBase] or None
358
+ Output type used to cast responses.
359
+ """
360
+ return self._output_structure
361
+
362
+ @property
363
+ def model_settings(self) -> Optional[ModelSettings]:
364
+ """Return the model settings configured for this agent.
365
+
366
+ Returns
367
+ -------
368
+ ModelSettings or None
369
+ Model settings applied to the agent.
370
+ """
371
+ return self._model_settings
372
+
373
+ @property
374
+ def handoffs(self) -> Optional[list[Agent | Handoff]]:
375
+ """Return the handoff configurations for this agent.
376
+
377
+ Returns
378
+ -------
379
+ list[Agent or Handoff] or None
380
+ Handoff configurations available to the agent.
381
+ """
382
+ return self._handoffs
383
+
384
+ @property
385
+ def input_guardrails(self) -> Optional[list[InputGuardrail]]:
386
+ """Return the input guardrails configured for this agent.
387
+
388
+ Returns
389
+ -------
390
+ list[InputGuardrail] or None
391
+ Input guardrails applied to the agent.
392
+ """
393
+ return self._input_guardrails
394
+
395
+ @property
396
+ def output_guardrails(self) -> Optional[list[OutputGuardrail]]:
397
+ """Return the output guardrails configured for this agent.
398
+
399
+ Returns
400
+ -------
401
+ list[OutputGuardrail] or None
402
+ Output guardrails applied to the agent.
403
+ """
404
+ return self._output_guardrails
405
+
406
+ @property
407
+ def session(self) -> Optional[Session]:
408
+ """Return the session configured for this agent.
409
+
410
+ Returns
411
+ -------
412
+ Session or None
413
+ Session configuration used for maintaining conversation history.
414
+ """
415
+ return self._session
416
+
236
417
  def get_agent(self) -> Agent:
237
418
  """Construct and return the configured :class:`agents.Agent` instance.
238
419
 
@@ -242,16 +423,22 @@ class AgentBase:
242
423
  Initialized agent ready for execution.
243
424
  """
244
425
  agent_config: Dict[str, Any] = {
245
- "name": self.agent_name,
426
+ "name": self._name,
246
427
  "instructions": self._build_prompt_from_jinja() or ".",
247
428
  "model": self.model,
248
429
  }
249
- if self._output_type:
250
- agent_config["output_type"] = self._output_type
430
+ if self._output_structure:
431
+ agent_config["output_structure"] = self._output_structure
251
432
  if self._tools:
252
433
  agent_config["tools"] = self._tools
253
434
  if self._model_settings:
254
435
  agent_config["model_settings"] = self._model_settings
436
+ if self._handoffs:
437
+ agent_config["handoffs"] = self._handoffs
438
+ if self._input_guardrails:
439
+ agent_config["input_guardrails"] = self._input_guardrails
440
+ if self._output_guardrails:
441
+ agent_config["output_guardrails"] = self._output_guardrails
255
442
 
256
443
  return Agent(**agent_config)
257
444
 
@@ -260,7 +447,8 @@ class AgentBase:
260
447
  input: str,
261
448
  *,
262
449
  context: Optional[Dict[str, Any]] = None,
263
- output_type: Optional[Any] = None,
450
+ output_structure: Optional[type[StructureBase]] = None,
451
+ session: Optional[Any] = None,
264
452
  ) -> Any:
265
453
  """Execute the agent asynchronously.
266
454
 
@@ -270,21 +458,27 @@ class AgentBase:
270
458
  Prompt or query for the agent.
271
459
  context : dict or None, default=None
272
460
  Optional dictionary passed to the agent.
273
- output_type : type or None, default=None
461
+ output_structure : type[StructureBase] or None, default=None
274
462
  Optional type used to cast the final output.
463
+ session : Session or None, default=None
464
+ Optional session for maintaining conversation history across runs.
465
+ If not provided, uses the session from config if available.
275
466
 
276
467
  Returns
277
468
  -------
278
469
  Any
279
- Agent result, optionally converted to ``output_type``.
470
+ Agent result, optionally converted to ``output_structure``.
280
471
  """
281
- if self._output_type is not None and output_type is None:
282
- output_type = self._output_type
472
+ if self._output_structure is not None and output_structure is None:
473
+ output_structure = self._output_structure
474
+ # Use session from parameter, fall back to config session
475
+ session_to_use = session if session is not None else self._session
283
476
  return await run_async(
284
477
  agent=self.get_agent(),
285
478
  input=input,
286
479
  context=context,
287
- output_type=output_type,
480
+ output_structure=output_structure,
481
+ session=session_to_use,
288
482
  )
289
483
 
290
484
  def run_sync(
@@ -292,7 +486,8 @@ class AgentBase:
292
486
  input: str,
293
487
  *,
294
488
  context: Optional[Dict[str, Any]] = None,
295
- output_type: Optional[Any] = None,
489
+ output_structure: Optional[type[StructureBase]] = None,
490
+ session: Optional[Any] = None,
296
491
  ) -> Any:
297
492
  """Run the agent synchronously.
298
493
 
@@ -302,19 +497,27 @@ class AgentBase:
302
497
  Prompt or query for the agent.
303
498
  context : dict or None, default=None
304
499
  Optional dictionary passed to the agent.
305
- output_type : type or None, default=None
500
+ output_structure : type[StructureBase] or None, default=None
306
501
  Optional type used to cast the final output.
502
+ session : Session or None, default=None
503
+ Optional session for maintaining conversation history across runs.
504
+ If not provided, uses the session from config if available.
307
505
 
308
506
  Returns
309
507
  -------
310
508
  Any
311
- Agent result, optionally converted to ``output_type``.
509
+ Agent result, optionally converted to ``output_structure``.
312
510
  """
511
+ if self._output_structure is not None and output_structure is None:
512
+ output_structure = self._output_structure
513
+ # Use session from parameter, fall back to config session
514
+ session_to_use = session if session is not None else self._session
313
515
  return run_sync(
314
516
  agent=self.get_agent(),
315
517
  input=input,
316
518
  context=context,
317
- output_type=output_type,
519
+ output_structure=output_structure,
520
+ session=session_to_use,
318
521
  )
319
522
 
320
523
  def run_streamed(
@@ -322,8 +525,9 @@ class AgentBase:
322
525
  input: str,
323
526
  *,
324
527
  context: Optional[Dict[str, Any]] = None,
325
- output_type: Optional[Any] = None,
326
- ) -> RunResultStreaming:
528
+ output_structure: Optional[type[StructureBase]] = None,
529
+ session: Optional[Any] = None,
530
+ ) -> RunResultStreaming | StructureBase:
327
531
  """Stream the agent execution results.
328
532
 
329
533
  Parameters
@@ -332,38 +536,119 @@ class AgentBase:
332
536
  Prompt or query for the agent.
333
537
  context : dict or None, default=None
334
538
  Optional dictionary passed to the agent.
335
- output_type : type or None, default=None
539
+ output_structure : type[StructureBase] or None, default=None
336
540
  Optional type used to cast the final output.
541
+ session : Session or None, default=None
542
+ Optional session for maintaining conversation history across runs.
543
+ If not provided, uses the session from config if available.
337
544
 
338
545
  Returns
339
546
  -------
340
547
  RunResultStreaming
341
548
  Streaming output wrapper from the agent execution.
342
549
  """
550
+ # Use session from parameter, fall back to config session
551
+ session_to_use = session if session is not None else self._session
552
+ output_structure_to_use = output_structure or self._output_structure
343
553
  result = run_streamed(
344
554
  agent=self.get_agent(),
345
555
  input=input,
346
556
  context=context,
557
+ output_structure=output_structure_to_use,
558
+ session=session_to_use,
347
559
  )
348
- if self._output_type and not output_type:
349
- output_type = self._output_type
350
- if output_type:
351
- return result.final_output_as(output_type)
560
+ if output_structure_to_use and hasattr(result, "final_output_as"):
561
+ return cast(Any, result).final_output_as(output_structure_to_use)
352
562
  return result
353
563
 
354
- def as_tool(self) -> FunctionTool:
564
+ def as_tool(self) -> Tool:
355
565
  """Return the agent as a callable tool.
356
566
 
357
567
  Returns
358
568
  -------
359
- FunctionTool
569
+ Tool
360
570
  Tool instance wrapping this agent.
361
571
  """
362
572
  agent = self.get_agent()
363
- tool_obj: FunctionTool = agent.as_tool(
364
- tool_name=self.agent_name, tool_description=self.description
365
- ) # type: ignore
573
+ tool_obj: Tool = agent.as_tool(
574
+ tool_name=self._name, tool_description=self.description
575
+ )
366
576
  return tool_obj
367
577
 
578
+ def __enter__(self) -> AgentBase:
579
+ """Enter the context manager for resource management.
580
+
581
+ Returns
582
+ -------
583
+ AgentBase
584
+ Self reference for use in with statements.
585
+ """
586
+ return self
587
+
588
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
589
+ """Exit the context manager and clean up resources.
590
+
591
+ Parameters
592
+ ----------
593
+ exc_type : type or None
594
+ Exception type if an exception occurred, otherwise None.
595
+ exc_val : Exception or None
596
+ Exception instance if an exception occurred, otherwise None.
597
+ exc_tb : traceback or None
598
+ Traceback object if an exception occurred, otherwise None.
599
+ """
600
+ self.close()
601
+
602
+ def close(self) -> None:
603
+ """Clean up agent resources.
604
+
605
+ This method is called automatically when using the agent as a
606
+ context manager. Override in subclasses to implement custom
607
+ cleanup logic.
608
+
609
+ Examples
610
+ --------
611
+ >>> agent = AgentBase(config, default_model="gpt-4o-mini")
612
+ >>> try:
613
+ ... result = agent.run_sync("query")
614
+ ... finally:
615
+ ... agent.close()
616
+ """
617
+ log(f"Closing session {self.uuid} for {self.__class__.__name__}")
618
+ self.save()
619
+
620
+ def __repr__(self) -> str:
621
+ """Return a string representation of the AgentBase.
622
+
623
+ Returns
624
+ -------
625
+ str
626
+ String representation including agent name and model.
627
+ """
628
+ return f"<AgentBase name={self._name!r} model={self.model!r}>"
629
+
630
+ def save(self, filepath: str | Path | None = None) -> None:
631
+ """Serialize the message history to a JSON file.
632
+
633
+ Saves the current message history to a specified file path in JSON format.
634
+ If no file path is provided, it saves to a default location based on
635
+ the agent's UUID.
636
+
637
+ Parameters
638
+ ----------
639
+ filepath : str | Path | None, default=None
640
+ Optional file path to save the serialized history. If None,
641
+ uses a default filename based on the agent name.
642
+ """
643
+ if filepath is not None:
644
+ target = Path(filepath)
645
+ else:
646
+ filename = f"{str(self.uuid).lower()}.json"
647
+ target = self._data_path / self._name / filename
648
+
649
+ checked = check_filepath(filepath=target)
650
+ self.to_json_file(filepath=checked)
651
+ log(f"Saved messages to {target}")
652
+
368
653
 
369
- __all__ = ["AgentConfigLike", "AgentBase"]
654
+ __all__ = ["AgentConfigurationLike", "AgentBase"]