openai-sdk-helpers 0.3.0__py3-none-any.whl → 0.4.1__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 -4
  3. openai_sdk_helpers/agent/base.py +254 -113
  4. openai_sdk_helpers/agent/config.py +91 -37
  5. openai_sdk_helpers/agent/coordination.py +64 -28
  6. openai_sdk_helpers/agent/runner.py +16 -15
  7. openai_sdk_helpers/agent/search/base.py +94 -45
  8. openai_sdk_helpers/agent/search/vector.py +86 -58
  9. openai_sdk_helpers/agent/search/web.py +71 -40
  10. openai_sdk_helpers/agent/summarizer.py +32 -7
  11. openai_sdk_helpers/agent/translator.py +57 -24
  12. openai_sdk_helpers/agent/validation.py +34 -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 +142 -73
  20. openai_sdk_helpers/response/config.py +63 -58
  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/__init__.py +4 -4
  27. openai_sdk_helpers/streamlit_app/app.py +16 -16
  28. openai_sdk_helpers/streamlit_app/config.py +82 -70
  29. openai_sdk_helpers/streamlit_app/streamlit_web_search.py +2 -2
  30. openai_sdk_helpers/structure/__init__.py +6 -2
  31. openai_sdk_helpers/structure/agent_blueprint.py +2 -2
  32. openai_sdk_helpers/structure/base.py +8 -99
  33. openai_sdk_helpers/structure/plan/plan.py +2 -2
  34. openai_sdk_helpers/structure/plan/task.py +9 -9
  35. openai_sdk_helpers/structure/prompt.py +2 -2
  36. openai_sdk_helpers/structure/responses.py +15 -15
  37. openai_sdk_helpers/structure/summary.py +3 -3
  38. openai_sdk_helpers/structure/translation.py +32 -0
  39. openai_sdk_helpers/structure/validation.py +2 -2
  40. openai_sdk_helpers/structure/vector_search.py +7 -7
  41. openai_sdk_helpers/structure/web_search.py +6 -6
  42. openai_sdk_helpers/tools.py +41 -15
  43. openai_sdk_helpers/utils/__init__.py +19 -5
  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} +33 -68
  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 +46 -8
  52. openai_sdk_helpers/vector_storage/storage.py +10 -0
  53. {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/METADATA +7 -7
  54. openai_sdk_helpers-0.4.1.dist-info/RECORD +86 -0
  55. openai_sdk_helpers-0.3.0.dist-info/RECORD +0 -81
  56. {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/WHEEL +0 -0
  57. {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/entry_points.txt +0 -0
  58. {openai_sdk_helpers-0.3.0.dist-info → openai_sdk_helpers-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -33,7 +33,7 @@ from .utils.validation import (
33
33
  validate_url_format,
34
34
  )
35
35
  from .structure import (
36
- BaseStructure,
36
+ StructureBase,
37
37
  SchemaOptions,
38
38
  PlanStructure,
39
39
  TaskStructure,
@@ -54,7 +54,7 @@ from .config import OpenAISettings
54
54
  from .files_api import FilesAPIManager, FilePurpose
55
55
  from .vector_storage import VectorStorage, VectorStorageFileInfo, VectorStorageFileStats
56
56
  from .agent import (
57
- BaseAgent,
57
+ AgentBase,
58
58
  AgentConfiguration,
59
59
  AgentEnum,
60
60
  CoordinatorAgent,
@@ -65,7 +65,7 @@ from .agent import (
65
65
  WebAgentSearch,
66
66
  )
67
67
  from .response import (
68
- BaseResponse,
68
+ ResponseBase,
69
69
  ResponseMessage,
70
70
  ResponseMessages,
71
71
  ResponseToolCall,
@@ -134,7 +134,7 @@ __all__ = [
134
134
  "validate_choice",
135
135
  "validate_safe_path",
136
136
  # Main structure classes
137
- "BaseStructure",
137
+ "StructureBase",
138
138
  "SchemaOptions",
139
139
  "spec_field",
140
140
  "PromptRenderer",
@@ -150,7 +150,7 @@ __all__ = [
150
150
  "TaskStructure",
151
151
  "PlanStructure",
152
152
  "AgentEnum",
153
- "BaseAgent",
153
+ "AgentBase",
154
154
  "AgentConfiguration",
155
155
  "CoordinatorAgent",
156
156
  "SummarizerAgent",
@@ -162,7 +162,7 @@ __all__ = [
162
162
  "WebSearchStructure",
163
163
  "VectorSearchStructure",
164
164
  "ValidationResultStructure",
165
- "BaseResponse",
165
+ "ResponseBase",
166
166
  "ResponseMessage",
167
167
  "ResponseMessages",
168
168
  "ResponseToolCall",
@@ -2,8 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from .base import BaseAgent
6
- from .config import AgentConfiguration, AgentConfigurationRegistry, get_default_registry
5
+ from .base import AgentBase
6
+ from .config import AgentConfiguration, AgentRegistry, get_default_registry
7
7
  from ..structure.plan.enum import AgentEnum
8
8
  from .coordination import CoordinatorAgent
9
9
  from .runner import run_sync, run_async, run_streamed
@@ -16,9 +16,9 @@ from .search.vector import VectorSearch
16
16
  from .search.web import WebAgentSearch
17
17
 
18
18
  __all__ = [
19
- "BaseAgent",
19
+ "AgentBase",
20
20
  "AgentConfiguration",
21
- "AgentConfigurationRegistry",
21
+ "AgentRegistry",
22
22
  "get_default_registry",
23
23
  "AgentEnum",
24
24
  "CoordinatorAgent",
@@ -3,16 +3,15 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
- from typing import Any, Dict, Optional, Protocol
6
+ from typing import Any, Callable, Dict, Optional, Protocol, cast
7
+ import uuid
7
8
 
8
9
  from agents import (
9
10
  Agent,
10
11
  Handoff,
11
12
  InputGuardrail,
12
13
  OutputGuardrail,
13
- RunResult,
14
14
  RunResultStreaming,
15
- Runner,
16
15
  Session,
17
16
  )
18
17
  from agents.model_settings import ModelSettings
@@ -20,11 +19,19 @@ from agents.run_context import RunContextWrapper
20
19
  from agents.tool import Tool
21
20
  from jinja2 import Template
22
21
 
22
+ from ..utils.json.data_class import DataclassJSONSerializable
23
+ from ..structure.base import StructureBase
24
+
25
+ from ..utils import (
26
+ check_filepath,
27
+ log,
28
+ )
29
+
23
30
  from .runner import run_async, run_streamed, run_sync
24
31
 
25
32
 
26
- class AgentConfigurationLike(Protocol):
27
- """Protocol describing the configuration attributes for BaseAgent."""
33
+ class AgentConfigurationProtocol(Protocol):
34
+ """Protocol describing the configuration attributes for AgentBase."""
28
35
 
29
36
  @property
30
37
  def name(self) -> str:
@@ -46,6 +53,10 @@ class AgentConfigurationLike(Protocol):
46
53
  """Template path."""
47
54
  ...
48
55
 
56
+ def resolve_prompt_path(self, prompt_dir: Path | None = None) -> Path | None:
57
+ """Resolve the prompt template path."""
58
+ ...
59
+
49
60
  @property
50
61
  def instructions(self) -> str | Path:
51
62
  """Instructions."""
@@ -57,12 +68,12 @@ class AgentConfigurationLike(Protocol):
57
68
  ...
58
69
 
59
70
  @property
60
- def input_type(self) -> Optional[type]:
71
+ def input_structure(self) -> Optional[type[StructureBase]]:
61
72
  """Input type."""
62
73
  ...
63
74
 
64
75
  @property
65
- def output_type(self) -> Optional[type]:
76
+ def output_structure(self) -> Optional[type[StructureBase]]:
66
77
  """Output type."""
67
78
  ...
68
79
 
@@ -97,10 +108,10 @@ class AgentConfigurationLike(Protocol):
97
108
  ...
98
109
 
99
110
 
100
- class BaseAgent:
111
+ class AgentBase(DataclassJSONSerializable):
101
112
  """Factory for creating and configuring specialized agents.
102
113
 
103
- ``BaseAgent`` provides the foundation for building OpenAI agents with support
114
+ ``AgentBase`` provides the foundation for building OpenAI agents with support
104
115
  for Jinja2 prompt templates, custom tools, handoffs for agent delegation,
105
116
  input and output guardrails for validation, session management for
106
117
  conversation history, and both synchronous and asynchronous execution modes.
@@ -110,13 +121,13 @@ class BaseAgent:
110
121
  --------
111
122
  Create a basic agent from configuration:
112
123
 
113
- >>> from openai_sdk_helpers.agent import BaseAgent, AgentConfiguration
124
+ >>> from openai_sdk_helpers.agent import AgentBase, AgentConfiguration
114
125
  >>> config = AgentConfiguration(
115
126
  ... name="my_agent",
116
127
  ... description="A custom agent",
117
128
  ... model="gpt-4o-mini"
118
129
  ... )
119
- >>> agent = BaseAgent(config=config, default_model="gpt-4o-mini")
130
+ >>> agent = AgentBase(config=config, default_model="gpt-4o-mini")
120
131
  >>> result = agent.run_sync("What is 2+2?")
121
132
 
122
133
  Use absolute path to template:
@@ -126,7 +137,7 @@ class BaseAgent:
126
137
  ... template_path="/absolute/path/to/template.jinja",
127
138
  ... model="gpt-4o-mini"
128
139
  ... )
129
- >>> agent = BaseAgent(config=config, default_model="gpt-4o-mini")
140
+ >>> agent = AgentBase(config=config, default_model="gpt-4o-mini")
130
141
 
131
142
  Use async execution:
132
143
 
@@ -138,19 +149,35 @@ class BaseAgent:
138
149
 
139
150
  Methods
140
151
  -------
141
- from_configuration(config, run_context_wrapper, prompt_dir, default_model)
142
- Instantiate a ``BaseAgent`` from configuration.
143
152
  build_prompt_from_jinja(run_context_wrapper)
144
153
  Render the agent prompt using Jinja and optional context.
145
154
  get_prompt(run_context_wrapper, _)
146
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.
147
174
  get_agent()
148
175
  Construct the configured :class:`agents.Agent` instance.
149
- run_async(input, context, output_type, session)
176
+ run_async(input, context, output_structure, session)
150
177
  Execute the agent asynchronously and optionally cast the result.
151
- run_sync(input, context, output_type, session)
178
+ run_sync(input, context, output_structure, session)
152
179
  Execute the agent synchronously.
153
- run_streamed(input, context, output_type, session)
180
+ run_streamed(input, context, output_structure, session)
154
181
  Return a streaming result for the agent execution.
155
182
  as_tool()
156
183
  Return the agent as a callable tool.
@@ -160,16 +187,18 @@ class BaseAgent:
160
187
 
161
188
  def __init__(
162
189
  self,
163
- config: AgentConfigurationLike,
190
+ *,
191
+ config: AgentConfigurationProtocol,
164
192
  run_context_wrapper: Optional[RunContextWrapper[Dict[str, Any]]] = None,
193
+ data_path: Path | str | None = None,
165
194
  prompt_dir: Optional[Path] = None,
166
195
  default_model: Optional[str] = None,
167
196
  ) -> None:
168
- """Initialize the BaseAgent using a configuration object.
197
+ """Initialize the AgentBase using a configuration object.
169
198
 
170
199
  Parameters
171
200
  ----------
172
- config : AgentConfigurationLike
201
+ config : AgentConfigurationProtocol
173
202
  Configuration describing this agent.
174
203
  run_context_wrapper : RunContextWrapper or None, default=None
175
204
  Optional wrapper providing runtime context for prompt rendering.
@@ -187,45 +216,41 @@ class BaseAgent:
187
216
  if not model:
188
217
  raise ValueError("Model is required to construct the agent.")
189
218
 
190
- prompt_path: Optional[Path]
191
- if config.template_path:
192
- prompt_path = Path(config.template_path)
193
- elif prompt_dir is not None:
194
- prompt_path = prompt_dir / f"{name}.jinja"
195
- else:
196
- prompt_path = None
219
+ prompt_path = config.resolve_prompt_path(prompt_dir)
197
220
 
198
221
  # Build template from file or fall back to instructions
199
222
  if prompt_path is None:
200
- # No template path - use instructions (always available now)
201
- if hasattr(config, "instructions_text"):
202
- # AgentConfiguration has this property - use it for proper resolution
203
- instructions_text = config.instructions_text
204
- else:
205
- # Fall back to resolving instructions ourselves
206
- if isinstance(config.instructions, Path):
207
- try:
208
- instructions_text = config.instructions.read_text(
209
- encoding="utf-8"
210
- )
211
- except OSError:
212
- instructions_text = "" # Leave empty if file can't be read
213
- else:
214
- instructions_text = config.instructions
223
+ instructions_text = config.instructions_text
215
224
  self._template = Template(instructions_text)
225
+ self._instructions = instructions_text
216
226
  elif prompt_path.exists():
217
- self._template = Template(prompt_path.read_text())
227
+ self._template = Template(prompt_path.read_text(encoding="utf-8"))
228
+ self._instructions = None
218
229
  else:
219
230
  raise FileNotFoundError(
220
231
  f"Prompt template for agent '{name}' not found at {prompt_path}."
221
232
  )
222
233
 
223
- self.agent_name = name
234
+ self._name = name
235
+ self.uuid = uuid.uuid4()
224
236
  self.description = description
225
237
  self.model = model
226
238
 
227
- self._input_type = config.input_type
228
- 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
229
254
  self._tools = config.tools
230
255
  self._model_settings = config.model_settings
231
256
  self._handoffs = config.handoffs
@@ -234,46 +259,6 @@ class BaseAgent:
234
259
  self._session = config.session
235
260
  self._run_context_wrapper = run_context_wrapper
236
261
 
237
- # Store instructions if provided directly in config
238
- self._instructions = getattr(config, "instructions", None)
239
-
240
- @classmethod
241
- def from_configuration(
242
- cls,
243
- config: AgentConfigurationLike,
244
- *,
245
- run_context_wrapper: Optional[RunContextWrapper[Dict[str, Any]]] = None,
246
- prompt_dir: Optional[Path] = None,
247
- default_model: Optional[str] = None,
248
- ) -> BaseAgent:
249
- """Create a BaseAgent instance from configuration.
250
-
251
- Parameters
252
- ----------
253
- config : AgentConfigurationLike
254
- Configuration describing the agent.
255
- run_context_wrapper : RunContextWrapper or None, default=None
256
- Optional wrapper providing runtime context.
257
- prompt_dir : Path or None, default=None
258
- Optional directory holding prompt templates. Used when
259
- ``config.template_path`` is not provided or is relative. If
260
- ``config.template_path`` is an absolute path, this parameter is
261
- ignored.
262
- default_model : str or None, default=None
263
- Optional fallback model identifier.
264
-
265
- Returns
266
- -------
267
- BaseAgent
268
- Instantiated agent.
269
- """
270
- return cls(
271
- config=config,
272
- run_context_wrapper=run_context_wrapper,
273
- prompt_dir=prompt_dir,
274
- default_model=default_model,
275
- )
276
-
277
262
  def _build_prompt_from_jinja(self) -> str:
278
263
  """Render the instructions prompt for this agent.
279
264
 
@@ -328,6 +313,107 @@ class BaseAgent:
328
313
  """
329
314
  return self.build_prompt_from_jinja(run_context_wrapper)
330
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
+
331
417
  def get_agent(self) -> Agent:
332
418
  """Construct and return the configured :class:`agents.Agent` instance.
333
419
 
@@ -337,12 +423,12 @@ class BaseAgent:
337
423
  Initialized agent ready for execution.
338
424
  """
339
425
  agent_config: Dict[str, Any] = {
340
- "name": self.agent_name,
426
+ "name": self._name,
341
427
  "instructions": self._build_prompt_from_jinja() or ".",
342
428
  "model": self.model,
343
429
  }
344
- if self._output_type:
345
- agent_config["output_type"] = self._output_type
430
+ if self._output_structure:
431
+ agent_config["output_type"] = self._output_structure
346
432
  if self._tools:
347
433
  agent_config["tools"] = self._tools
348
434
  if self._model_settings:
@@ -361,7 +447,7 @@ class BaseAgent:
361
447
  input: str,
362
448
  *,
363
449
  context: Optional[Dict[str, Any]] = None,
364
- output_type: Optional[Any] = None,
450
+ output_structure: Optional[type[StructureBase]] = None,
365
451
  session: Optional[Any] = None,
366
452
  ) -> Any:
367
453
  """Execute the agent asynchronously.
@@ -372,7 +458,7 @@ class BaseAgent:
372
458
  Prompt or query for the agent.
373
459
  context : dict or None, default=None
374
460
  Optional dictionary passed to the agent.
375
- output_type : type or None, default=None
461
+ output_structure : type[StructureBase] or None, default=None
376
462
  Optional type used to cast the final output.
377
463
  session : Session or None, default=None
378
464
  Optional session for maintaining conversation history across runs.
@@ -381,17 +467,17 @@ class BaseAgent:
381
467
  Returns
382
468
  -------
383
469
  Any
384
- Agent result, optionally converted to ``output_type``.
470
+ Agent result, optionally converted to ``output_structure``.
385
471
  """
386
- if self._output_type is not None and output_type is None:
387
- output_type = self._output_type
472
+ if self._output_structure is not None and output_structure is None:
473
+ output_structure = self._output_structure
388
474
  # Use session from parameter, fall back to config session
389
475
  session_to_use = session if session is not None else self._session
390
476
  return await run_async(
391
477
  agent=self.get_agent(),
392
478
  input=input,
393
479
  context=context,
394
- output_type=output_type,
480
+ output_structure=output_structure,
395
481
  session=session_to_use,
396
482
  )
397
483
 
@@ -400,7 +486,7 @@ class BaseAgent:
400
486
  input: str,
401
487
  *,
402
488
  context: Optional[Dict[str, Any]] = None,
403
- output_type: Optional[Any] = None,
489
+ output_structure: Optional[type[StructureBase]] = None,
404
490
  session: Optional[Any] = None,
405
491
  ) -> Any:
406
492
  """Run the agent synchronously.
@@ -411,7 +497,7 @@ class BaseAgent:
411
497
  Prompt or query for the agent.
412
498
  context : dict or None, default=None
413
499
  Optional dictionary passed to the agent.
414
- output_type : type or None, default=None
500
+ output_structure : type[StructureBase] or None, default=None
415
501
  Optional type used to cast the final output.
416
502
  session : Session or None, default=None
417
503
  Optional session for maintaining conversation history across runs.
@@ -420,15 +506,17 @@ class BaseAgent:
420
506
  Returns
421
507
  -------
422
508
  Any
423
- Agent result, optionally converted to ``output_type``.
509
+ Agent result, optionally converted to ``output_structure``.
424
510
  """
511
+ if self._output_structure is not None and output_structure is None:
512
+ output_structure = self._output_structure
425
513
  # Use session from parameter, fall back to config session
426
514
  session_to_use = session if session is not None else self._session
427
515
  return run_sync(
428
516
  agent=self.get_agent(),
429
517
  input=input,
430
518
  context=context,
431
- output_type=output_type,
519
+ output_structure=output_structure,
432
520
  session=session_to_use,
433
521
  )
434
522
 
@@ -437,9 +525,9 @@ class BaseAgent:
437
525
  input: str,
438
526
  *,
439
527
  context: Optional[Dict[str, Any]] = None,
440
- output_type: Optional[Any] = None,
528
+ output_structure: Optional[type[StructureBase]] = None,
441
529
  session: Optional[Any] = None,
442
- ) -> RunResultStreaming:
530
+ ) -> RunResultStreaming | StructureBase:
443
531
  """Stream the agent execution results.
444
532
 
445
533
  Parameters
@@ -448,7 +536,7 @@ class BaseAgent:
448
536
  Prompt or query for the agent.
449
537
  context : dict or None, default=None
450
538
  Optional dictionary passed to the agent.
451
- output_type : type or None, default=None
539
+ output_structure : type[StructureBase] or None, default=None
452
540
  Optional type used to cast the final output.
453
541
  session : Session or None, default=None
454
542
  Optional session for maintaining conversation history across runs.
@@ -461,16 +549,16 @@ class BaseAgent:
461
549
  """
462
550
  # Use session from parameter, fall back to config session
463
551
  session_to_use = session if session is not None else self._session
552
+ output_structure_to_use = output_structure or self._output_structure
464
553
  result = run_streamed(
465
554
  agent=self.get_agent(),
466
555
  input=input,
467
556
  context=context,
557
+ output_structure=output_structure_to_use,
468
558
  session=session_to_use,
469
559
  )
470
- if self._output_type and not output_type:
471
- output_type = self._output_type
472
- if output_type:
473
- 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)
474
562
  return result
475
563
 
476
564
  def as_tool(self) -> Tool:
@@ -483,16 +571,36 @@ class BaseAgent:
483
571
  """
484
572
  agent = self.get_agent()
485
573
  tool_obj: Tool = agent.as_tool(
486
- tool_name=self.agent_name, tool_description=self.description
574
+ tool_name=self._name, tool_description=self.description
487
575
  )
488
576
  return tool_obj
489
577
 
490
- def __enter__(self) -> BaseAgent:
578
+ def as_response_tool(self) -> tuple[dict[str, Callable[..., Any]], dict[str, Any]]:
579
+ """Return the agent as a callable response tool.
580
+
581
+ Returns
582
+ -------
583
+ Tool
584
+ Tool instance wrapping this agent for response generation.
585
+ """
586
+ tool_handler = {self.name: self.run_sync}
587
+ tool_definition = {
588
+ "type": "function",
589
+ "name": self.name,
590
+ "description": self.description,
591
+ "strict": True,
592
+ "additionalProperties": False,
593
+ }
594
+ if self.output_structure:
595
+ tool_definition["parameters"] = self.output_structure.get_schema()
596
+ return tool_handler, tool_definition
597
+
598
+ def __enter__(self) -> AgentBase:
491
599
  """Enter the context manager for resource management.
492
600
 
493
601
  Returns
494
602
  -------
495
- BaseAgent
603
+ AgentBase
496
604
  Self reference for use in with statements.
497
605
  """
498
606
  return self
@@ -520,14 +628,47 @@ class BaseAgent:
520
628
 
521
629
  Examples
522
630
  --------
523
- >>> agent = BaseAgent(config, default_model="gpt-4o-mini")
631
+ >>> agent = AgentBase(config, default_model="gpt-4o-mini")
524
632
  >>> try:
525
633
  ... result = agent.run_sync("query")
526
634
  ... finally:
527
635
  ... agent.close()
528
636
  """
529
- # Base implementation does nothing, but subclasses can override
530
- pass
637
+ log(f"Closing session {self.uuid} for {self.__class__.__name__}")
638
+ self.save()
639
+
640
+ def __repr__(self) -> str:
641
+ """Return a string representation of the AgentBase.
642
+
643
+ Returns
644
+ -------
645
+ str
646
+ String representation including agent name and model.
647
+ """
648
+ return f"<AgentBase name={self._name!r} model={self.model!r}>"
649
+
650
+ def save(self, filepath: str | Path | None = None) -> None:
651
+ """Serialize the message history to a JSON file.
652
+
653
+ Saves the current message history to a specified file path in JSON format.
654
+ If no file path is provided, it saves to a default location based on
655
+ the agent's UUID.
656
+
657
+ Parameters
658
+ ----------
659
+ filepath : str | Path | None, default=None
660
+ Optional file path to save the serialized history. If None,
661
+ uses a default filename based on the agent name.
662
+ """
663
+ if filepath is not None:
664
+ target = Path(filepath)
665
+ else:
666
+ filename = f"{str(self.uuid).lower()}.json"
667
+ target = self._data_path / self._name / filename
668
+
669
+ checked = check_filepath(filepath=target)
670
+ self.to_json_file(filepath=checked)
671
+ log(f"Saved messages to {target}")
531
672
 
532
673
 
533
- __all__ = ["AgentConfigurationLike", "BaseAgent"]
674
+ __all__ = ["AgentConfigurationProtocol", "AgentBase"]