hammad-python 0.0.29__py3-none-any.whl → 0.0.31__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 (137) hide show
  1. ham/__init__.py +10 -0
  2. {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/METADATA +6 -32
  3. hammad_python-0.0.31.dist-info/RECORD +6 -0
  4. hammad/__init__.py +0 -84
  5. hammad/_internal.py +0 -256
  6. hammad/_main.py +0 -226
  7. hammad/cache/__init__.py +0 -40
  8. hammad/cache/base_cache.py +0 -181
  9. hammad/cache/cache.py +0 -169
  10. hammad/cache/decorators.py +0 -261
  11. hammad/cache/file_cache.py +0 -80
  12. hammad/cache/ttl_cache.py +0 -74
  13. hammad/cli/__init__.py +0 -33
  14. hammad/cli/animations.py +0 -573
  15. hammad/cli/plugins.py +0 -867
  16. hammad/cli/styles/__init__.py +0 -55
  17. hammad/cli/styles/settings.py +0 -139
  18. hammad/cli/styles/types.py +0 -358
  19. hammad/cli/styles/utils.py +0 -634
  20. hammad/data/__init__.py +0 -90
  21. hammad/data/collections/__init__.py +0 -49
  22. hammad/data/collections/collection.py +0 -326
  23. hammad/data/collections/indexes/__init__.py +0 -37
  24. hammad/data/collections/indexes/qdrant/__init__.py +0 -1
  25. hammad/data/collections/indexes/qdrant/index.py +0 -723
  26. hammad/data/collections/indexes/qdrant/settings.py +0 -94
  27. hammad/data/collections/indexes/qdrant/utils.py +0 -210
  28. hammad/data/collections/indexes/tantivy/__init__.py +0 -1
  29. hammad/data/collections/indexes/tantivy/index.py +0 -426
  30. hammad/data/collections/indexes/tantivy/settings.py +0 -40
  31. hammad/data/collections/indexes/tantivy/utils.py +0 -176
  32. hammad/data/configurations/__init__.py +0 -35
  33. hammad/data/configurations/configuration.py +0 -564
  34. hammad/data/models/__init__.py +0 -50
  35. hammad/data/models/extensions/__init__.py +0 -4
  36. hammad/data/models/extensions/pydantic/__init__.py +0 -42
  37. hammad/data/models/extensions/pydantic/converters.py +0 -759
  38. hammad/data/models/fields.py +0 -546
  39. hammad/data/models/model.py +0 -1078
  40. hammad/data/models/utils.py +0 -280
  41. hammad/data/sql/__init__.py +0 -24
  42. hammad/data/sql/database.py +0 -576
  43. hammad/data/sql/types.py +0 -127
  44. hammad/data/types/__init__.py +0 -75
  45. hammad/data/types/file.py +0 -431
  46. hammad/data/types/multimodal/__init__.py +0 -36
  47. hammad/data/types/multimodal/audio.py +0 -200
  48. hammad/data/types/multimodal/image.py +0 -182
  49. hammad/data/types/text.py +0 -1308
  50. hammad/formatting/__init__.py +0 -33
  51. hammad/formatting/json/__init__.py +0 -27
  52. hammad/formatting/json/converters.py +0 -158
  53. hammad/formatting/text/__init__.py +0 -63
  54. hammad/formatting/text/converters.py +0 -723
  55. hammad/formatting/text/markdown.py +0 -131
  56. hammad/formatting/yaml/__init__.py +0 -26
  57. hammad/formatting/yaml/converters.py +0 -5
  58. hammad/genai/__init__.py +0 -217
  59. hammad/genai/a2a/__init__.py +0 -32
  60. hammad/genai/a2a/workers.py +0 -552
  61. hammad/genai/agents/__init__.py +0 -59
  62. hammad/genai/agents/agent.py +0 -1973
  63. hammad/genai/agents/run.py +0 -1024
  64. hammad/genai/agents/types/__init__.py +0 -42
  65. hammad/genai/agents/types/agent_context.py +0 -13
  66. hammad/genai/agents/types/agent_event.py +0 -128
  67. hammad/genai/agents/types/agent_hooks.py +0 -220
  68. hammad/genai/agents/types/agent_messages.py +0 -31
  69. hammad/genai/agents/types/agent_response.py +0 -125
  70. hammad/genai/agents/types/agent_stream.py +0 -327
  71. hammad/genai/graphs/__init__.py +0 -125
  72. hammad/genai/graphs/_utils.py +0 -190
  73. hammad/genai/graphs/base.py +0 -1828
  74. hammad/genai/graphs/plugins.py +0 -316
  75. hammad/genai/graphs/types.py +0 -638
  76. hammad/genai/models/__init__.py +0 -1
  77. hammad/genai/models/embeddings/__init__.py +0 -43
  78. hammad/genai/models/embeddings/model.py +0 -226
  79. hammad/genai/models/embeddings/run.py +0 -163
  80. hammad/genai/models/embeddings/types/__init__.py +0 -37
  81. hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
  82. hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
  83. hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
  84. hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
  85. hammad/genai/models/language/__init__.py +0 -57
  86. hammad/genai/models/language/model.py +0 -1098
  87. hammad/genai/models/language/run.py +0 -878
  88. hammad/genai/models/language/types/__init__.py +0 -40
  89. hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
  90. hammad/genai/models/language/types/language_model_messages.py +0 -28
  91. hammad/genai/models/language/types/language_model_name.py +0 -239
  92. hammad/genai/models/language/types/language_model_request.py +0 -127
  93. hammad/genai/models/language/types/language_model_response.py +0 -217
  94. hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
  95. hammad/genai/models/language/types/language_model_settings.py +0 -89
  96. hammad/genai/models/language/types/language_model_stream.py +0 -600
  97. hammad/genai/models/language/utils/__init__.py +0 -28
  98. hammad/genai/models/language/utils/requests.py +0 -421
  99. hammad/genai/models/language/utils/structured_outputs.py +0 -135
  100. hammad/genai/models/model_provider.py +0 -4
  101. hammad/genai/models/multimodal.py +0 -47
  102. hammad/genai/models/reranking.py +0 -26
  103. hammad/genai/types/__init__.py +0 -1
  104. hammad/genai/types/base.py +0 -215
  105. hammad/genai/types/history.py +0 -290
  106. hammad/genai/types/tools.py +0 -507
  107. hammad/logging/__init__.py +0 -35
  108. hammad/logging/decorators.py +0 -834
  109. hammad/logging/logger.py +0 -1018
  110. hammad/mcp/__init__.py +0 -53
  111. hammad/mcp/client/__init__.py +0 -35
  112. hammad/mcp/client/client.py +0 -624
  113. hammad/mcp/client/client_service.py +0 -400
  114. hammad/mcp/client/settings.py +0 -178
  115. hammad/mcp/servers/__init__.py +0 -26
  116. hammad/mcp/servers/launcher.py +0 -1161
  117. hammad/runtime/__init__.py +0 -32
  118. hammad/runtime/decorators.py +0 -142
  119. hammad/runtime/run.py +0 -299
  120. hammad/service/__init__.py +0 -49
  121. hammad/service/create.py +0 -527
  122. hammad/service/decorators.py +0 -283
  123. hammad/types.py +0 -288
  124. hammad/typing/__init__.py +0 -435
  125. hammad/web/__init__.py +0 -43
  126. hammad/web/http/__init__.py +0 -1
  127. hammad/web/http/client.py +0 -944
  128. hammad/web/models.py +0 -275
  129. hammad/web/openapi/__init__.py +0 -1
  130. hammad/web/openapi/client.py +0 -740
  131. hammad/web/search/__init__.py +0 -1
  132. hammad/web/search/client.py +0 -1023
  133. hammad/web/utils.py +0 -472
  134. hammad_python-0.0.29.dist-info/RECORD +0 -135
  135. {hammad → ham}/py.typed +0 -0
  136. {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/WHEEL +0 -0
  137. {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/licenses/LICENSE +0 -0
@@ -1,1973 +0,0 @@
1
- """hammad.genai.agents.agent"""
2
-
3
- from typing import (
4
- Any,
5
- Callable,
6
- Generic,
7
- Literal,
8
- List,
9
- Type,
10
- TypeVar,
11
- Optional,
12
- Union,
13
- Dict,
14
- TypeAlias,
15
- overload,
16
- TYPE_CHECKING,
17
- )
18
- from pydantic import BaseModel, Field, create_model
19
- from dataclasses import dataclass, field
20
- from enum import Enum
21
- import json
22
-
23
- from ...logging.logger import _get_internal_logger
24
-
25
- from ..types.base import BaseGenAIModel, BaseGenAIModelSettings
26
- from ..models.language.model import LanguageModel
27
- from ..models.language.types import (
28
- LanguageModelResponse,
29
- LanguageModelName,
30
- LanguageModelInstructorMode,
31
- )
32
- from ..types.tools import (
33
- Tool,
34
- define_tool,
35
- execute_tools_from_language_model_response,
36
- )
37
- from ..models.language.utils.requests import (
38
- parse_messages_input as parse_messages,
39
- consolidate_system_messages,
40
- )
41
- from ...formatting.text.converters import convert_to_text
42
- from .types.agent_response import (
43
- AgentResponse,
44
- _create_agent_response_from_language_model_response,
45
- )
46
- from .types.agent_stream import AgentStream
47
- from .types.agent_context import AgentContext
48
- from .types.agent_event import AgentEvent
49
- from .types.agent_hooks import HookManager, HookDecorator
50
- from .types.agent_messages import AgentMessages
51
-
52
- if TYPE_CHECKING:
53
- try:
54
- from fasta2a import FastA2A
55
- except ImportError:
56
- FastA2A: TypeAlias = Any
57
-
58
-
59
- T = TypeVar("T")
60
-
61
-
62
- logger = _get_internal_logger(__name__)
63
-
64
-
65
- @dataclass
66
- class AgentSettings:
67
- """Settings object that controls the default behavior of an agent's run."""
68
-
69
- max_steps: int = field(default=10)
70
- """The maximum amount of steps the agent can take before stopping."""
71
-
72
- add_name_to_instructions: bool = field(default=True)
73
- """Whether to add the agent name to the instructions."""
74
-
75
- context_format: Literal["json", "python", "markdown"] = field(default="json")
76
- """Format for context in instructions."""
77
-
78
- # Context management settings
79
- context_updates: Optional[
80
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
81
- ] = field(default=None)
82
- """When to update context ('before', 'after', or both)."""
83
-
84
- context_confirm: bool = field(default=False)
85
- """Whether to confirm context updates."""
86
-
87
- context_strategy: Literal["selective", "all"] = field(default="all")
88
- """Strategy for context updates."""
89
-
90
- context_max_retries: int = field(default=3)
91
- """Maximum retries for context updates."""
92
-
93
- context_confirm_instructions: Optional[str] = field(default=None)
94
- """Custom instructions for context confirmation."""
95
-
96
- context_selection_instructions: Optional[str] = field(default=None)
97
- """Custom instructions for context selection."""
98
-
99
- context_update_instructions: Optional[str] = field(default=None)
100
- """Custom instructions for context updates."""
101
-
102
-
103
- class AgentModelSettings(BaseGenAIModelSettings):
104
- """Agent-specific model settings that extend the base model settings."""
105
-
106
- instructor_mode: Optional[LanguageModelInstructorMode] = None
107
- """Instructor mode for structured outputs."""
108
-
109
- max_steps: int = 10
110
- """Maximum number of steps the agent can take."""
111
-
112
- add_name_to_instructions: bool = True
113
- """Whether to add the agent name to the instructions."""
114
-
115
- context_format: Literal["json", "python", "markdown"] = "json"
116
- """Format for context in instructions."""
117
-
118
- # Context management settings
119
- context_updates: Optional[
120
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
121
- ] = None
122
- """When to update context ('before', 'after', or both)."""
123
-
124
- context_confirm: bool = False
125
- """Whether to confirm context updates."""
126
-
127
- context_strategy: Literal["selective", "all"] = "all"
128
- """Strategy for context updates."""
129
-
130
- context_max_retries: int = 3
131
- """Maximum retries for context updates."""
132
-
133
- context_confirm_instructions: Optional[str] = None
134
- """Custom instructions for context confirmation."""
135
-
136
- context_selection_instructions: Optional[str] = None
137
- """Custom instructions for context selection."""
138
-
139
- context_update_instructions: Optional[str] = None
140
- """Custom instructions for context updates."""
141
-
142
-
143
- def _build_tools(tools: List[Tool] | Callable | None) -> List[Tool]:
144
- """Builds a list of tools from a list of tools or a callable that returns a list of tools."""
145
- if tools is None:
146
- return []
147
- if callable(tools):
148
- return [define_tool(tools)]
149
-
150
- processed_tools = []
151
- for tool in tools:
152
- if not isinstance(tool, Tool):
153
- tool = define_tool(tool)
154
- processed_tools.append(tool)
155
-
156
- return processed_tools
157
-
158
-
159
- def _get_instructions(
160
- name: str,
161
- instructions: Optional[str],
162
- add_name_to_instructions: bool,
163
- ) -> Optional[str]:
164
- """Gets the instructions for an agent."""
165
- if add_name_to_instructions and name:
166
- base_instructions = instructions or ""
167
- return f"You are {name}.\n\n{base_instructions}".strip()
168
- return instructions
169
-
170
-
171
- def _format_context_for_instructions(
172
- context: AgentContext | None,
173
- context_format: Literal["json", "python", "markdown"] = "json",
174
- ) -> str:
175
- """Format context object for inclusion in instructions."""
176
- if context is None:
177
- return ""
178
-
179
- if context_format == "json":
180
- if isinstance(context, BaseModel):
181
- return context.model_dump_json(indent=2)
182
- elif isinstance(context, dict):
183
- return json.dumps(context, indent=2)
184
- else:
185
- return json.dumps(str(context), indent=2)
186
-
187
- elif context_format == "python":
188
- if hasattr(context, "__repr__"):
189
- return repr(context)
190
- elif hasattr(context, "__str__"):
191
- return str(context)
192
- else:
193
- return str(context)
194
-
195
- elif context_format == "markdown":
196
- return convert_to_text(context)
197
-
198
- return str(context)
199
-
200
-
201
- def _update_context_object(
202
- context: AgentContext, updates: Dict[str, Any]
203
- ) -> AgentContext:
204
- """Update a context object with new values."""
205
- if isinstance(context, BaseModel):
206
- # For Pydantic models, create a copy with updated values
207
- return context.model_copy(update=updates)
208
- elif isinstance(context, dict):
209
- # For dictionaries, update in place
210
- updated_context = context.copy()
211
- updated_context.update(updates)
212
- return updated_context
213
- else:
214
- raise ValueError(f"Cannot update context of type {type(context)}")
215
-
216
-
217
- def mark_complete() -> None:
218
- """If you feel you are ready to respond to the user, or have completed
219
- the task given to you, call this function to mark your response as
220
- complete."""
221
- return "complete"
222
-
223
-
224
- class Agent(BaseGenAIModel, Generic[T]):
225
- """A generative AI agent that can execute tools, generate structured outputs,
226
- and maintain context across multiple conversation steps.
227
- """
228
-
229
- model: LanguageModelName = "openai/gpt-4o-mini"
230
- """The language model to use for the agent."""
231
-
232
- name: str = "agent"
233
- """The name of the agent."""
234
-
235
- description: Optional[str] = None
236
- """A description of the agent."""
237
-
238
- instructions: Optional[str] = None
239
- """System instructions for the agent."""
240
-
241
- tools: List[Tool] = Field(default_factory=list)
242
- """List of tools available to the agent."""
243
-
244
- settings: AgentSettings = Field(default_factory=AgentSettings)
245
- """Agent-specific settings."""
246
-
247
- instructor_mode: Optional[LanguageModelInstructorMode] = None
248
- """Instructor mode for structured outputs."""
249
-
250
- def __init__(
251
- self,
252
- name: str = "agent",
253
- instructions: Optional[str] = None,
254
- model: Union[LanguageModel, LanguageModelName] = "openai/gpt-4o-mini",
255
- description: Optional[str] = None,
256
- tools: Union[List[Tool], Callable, None] = None,
257
- settings: Optional[AgentSettings] = None,
258
- instructor_mode: Optional[LanguageModelInstructorMode] = None,
259
- # Defaults
260
- max_steps: int = 10,
261
- # End Strategy
262
- end_strategy: Literal["tool"] | None = None,
263
- end_tool: Callable = mark_complete,
264
- # Context management parameters
265
- context_updates: Optional[
266
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
267
- ] = None,
268
- context_confirm: bool = False,
269
- context_strategy: Literal["selective", "all"] = "all",
270
- context_max_retries: int = 3,
271
- context_confirm_instructions: Optional[str] = None,
272
- context_selection_instructions: Optional[str] = None,
273
- context_update_instructions: Optional[str] = None,
274
- context_format: Literal["json", "python", "markdown"] = "json",
275
- verbose: bool = False,
276
- debug: bool = False,
277
- **kwargs: Any,
278
- ):
279
- """Create a new AI agent with specified capabilities and behavior.
280
-
281
- An agent is an intelligent assistant that can use tools, follow instructions,
282
- and maintain context across conversations. It combines a language model with
283
- additional capabilities like tool execution and structured output generation.
284
-
285
- Args:
286
- name: A human-readable name for the agent (default: "agent")
287
- instructions: System instructions that define the agent's behavior and personality
288
- model: The language model to use - either a LanguageModel instance or model name string
289
- description: Optional description of what the agent does
290
- tools: List of tools/functions the agent can call, or a single callable
291
- settings: AgentSettings object to customize default behavior
292
- instructor_mode: Mode for structured output generation
293
- max_steps: Default ,aximum number of steps the agent can take before stopping
294
- end_strategy: Optional alternative strategy to provide an end tool for determining agent's final
295
- response.
296
- end_tool: The tool the agent will call to determine if it should stop.
297
- This is only used if end_strategy is set to "tool".
298
- context_updates: When to update context - "before", "after", or both
299
- context_confirm: Whether to confirm context updates with the user
300
- context_strategy: How to select context updates - "selective" or "all"
301
- context_max_retries: Maximum attempts for context update operations
302
- context_confirm_instructions: Custom instructions for context confirmation
303
- context_selection_instructions: Custom instructions for context selection
304
- context_update_instructions: Custom instructions for context updates
305
- context_format: Format for context display - "json", "python", or "markdown"
306
- verbose: If True, set logger to INFO level for detailed output
307
- debug: If True, set logger to DEBUG level for maximum verbosity
308
- **kwargs: Additional parameters passed to the underlying language model
309
-
310
- Example:
311
- Basic agent:
312
- >>> agent = Agent(name="assistant", instructions="You are helpful")
313
-
314
- Agent with tools:
315
- >>> def calculator(x: int, y: int) -> int:
316
- ... return x + y
317
- >>> agent = Agent(tools=[calculator])
318
-
319
- Agent with custom settings:
320
- >>> settings = AgentSettings(max_steps=5)
321
- >>> agent = Agent(settings=settings, model="gpt-4")
322
- """
323
- # Initialize BaseGenAIModel with basic parameters
324
- super().__init__(
325
- model=model if isinstance(model, str) else model.model, **kwargs
326
- )
327
-
328
- # Agent-specific initialization
329
- self.name = name
330
- self.description = description
331
- self.tools = _build_tools(tools)
332
- self.settings = settings or AgentSettings()
333
- self.instructor_mode = instructor_mode
334
-
335
- # Store max_steps as instance variable (overrides settings if provided)
336
- self.max_steps = max_steps if max_steps is not None else self.settings.max_steps
337
-
338
- # Store end strategy parameters
339
- self.end_strategy = end_strategy
340
- self.end_tool = end_tool if end_tool is not None else mark_complete
341
-
342
- # Add end_tool to tools if end_strategy is 'tool'
343
- if self.end_strategy == "tool":
344
- self.tools.append(define_tool(self.end_tool))
345
-
346
- # Process instructions
347
- self.instructions = _get_instructions(
348
- name=name,
349
- instructions=instructions,
350
- add_name_to_instructions=self.settings.add_name_to_instructions,
351
- )
352
-
353
- # Store verbose/debug settings
354
- self.verbose = verbose
355
- self.debug = debug
356
-
357
- # Set logger level based on verbose/debug flags
358
- if debug:
359
- logger.level = "debug"
360
- elif verbose:
361
- logger.level = "info"
362
-
363
- # Initialize the language model
364
- if isinstance(model, LanguageModel):
365
- self._language_model = model
366
- else:
367
- self._language_model = LanguageModel(
368
- model=model, verbose=verbose, debug=debug, **kwargs
369
- )
370
-
371
- # Context management settings
372
- self.context_updates = context_updates
373
- self.context_confirm = context_confirm
374
- self.context_strategy = context_strategy
375
- self.context_max_retries = context_max_retries
376
- self.context_confirm_instructions = context_confirm_instructions
377
- self.context_selection_instructions = context_selection_instructions
378
- self.context_update_instructions = context_update_instructions
379
- self.context_format = context_format
380
-
381
- # Hook system
382
- self.hook_manager = HookManager()
383
- self.on = HookDecorator(self.hook_manager)
384
-
385
- @property
386
- def language_model(self) -> LanguageModel:
387
- """Get the underlying language model."""
388
- return self._language_model
389
-
390
- def _get_effective_context_settings(
391
- self,
392
- context_updates: Optional[
393
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
394
- ] = None,
395
- context_confirm: Optional[bool] = None,
396
- context_strategy: Optional[Literal["selective", "all"]] = None,
397
- context_max_retries: Optional[int] = None,
398
- context_confirm_instructions: Optional[str] = None,
399
- context_selection_instructions: Optional[str] = None,
400
- context_update_instructions: Optional[str] = None,
401
- context_format: Optional[Literal["json", "python", "markdown"]] = None,
402
- ) -> dict:
403
- """Get effective context settings, using provided parameters or defaults."""
404
- return {
405
- "context_updates": context_updates
406
- if context_updates is not None
407
- else self.context_updates,
408
- "context_confirm": context_confirm
409
- if context_confirm is not None
410
- else self.context_confirm,
411
- "context_strategy": context_strategy
412
- if context_strategy is not None
413
- else self.context_strategy,
414
- "context_max_retries": context_max_retries
415
- if context_max_retries is not None
416
- else self.context_max_retries,
417
- "context_confirm_instructions": context_confirm_instructions
418
- if context_confirm_instructions is not None
419
- else self.context_confirm_instructions,
420
- "context_selection_instructions": context_selection_instructions
421
- if context_selection_instructions is not None
422
- else self.context_selection_instructions,
423
- "context_update_instructions": context_update_instructions
424
- if context_update_instructions is not None
425
- else self.context_update_instructions,
426
- "context_format": context_format
427
- if context_format is not None
428
- else self.context_format,
429
- }
430
-
431
- def _should_update_context(
432
- self,
433
- context: AgentContext,
434
- timing: Literal["before", "after"],
435
- context_updates=None,
436
- ) -> bool:
437
- """Determine if context should be updated based on timing and configuration."""
438
- effective_context_updates = (
439
- context_updates if context_updates is not None else self.context_updates
440
- )
441
-
442
- if not effective_context_updates:
443
- return False
444
-
445
- if isinstance(effective_context_updates, str):
446
- return effective_context_updates == timing
447
- else:
448
- return timing in effective_context_updates
449
-
450
- def _create_context_confirm_model(self):
451
- """Create IsUpdateRequired model for context confirmation."""
452
- return create_model("IsUpdateRequired", decision=(bool, ...))
453
-
454
- def _create_context_selection_model(self, context: AgentContext):
455
- """Create FieldsToUpdate model for selective context updates."""
456
- if isinstance(context, BaseModel):
457
- field_names = list(context.model_fields.keys())
458
- elif isinstance(context, dict):
459
- field_names = list(context.keys())
460
- else:
461
- raise ValueError(
462
- f"Cannot create selection model for context type {type(context)}"
463
- )
464
-
465
- FieldEnum = Enum("FieldEnum", {name: name for name in field_names})
466
- return create_model("FieldsToUpdate", fields=(List[FieldEnum], ...))
467
-
468
- def _create_context_update_model(
469
- self, context: AgentContext, field_name: str = None
470
- ):
471
- """Create update model for context updates."""
472
- if field_name:
473
- # Single field update
474
- if isinstance(context, BaseModel):
475
- field_type = context.__class__.model_fields[field_name].annotation
476
- field_info = context.__class__.model_fields[field_name]
477
- description = getattr(
478
- field_info, "description", f"Update the {field_name} field"
479
- )
480
- elif isinstance(context, dict):
481
- field_type = type(context[field_name])
482
- description = f"Update the {field_name} field"
483
- else:
484
- field_type = Any
485
- description = f"Update the {field_name} field"
486
-
487
- return create_model(
488
- f"Update{field_name.capitalize()}",
489
- **{field_name: (field_type, Field(description=description))},
490
- )
491
- else:
492
- # All fields update - create a model with the exact same fields as the context
493
- if isinstance(context, BaseModel):
494
- # Create a model with the same fields as the context
495
- field_definitions = {}
496
- for field_name, field_info in context.model_fields.items():
497
- field_type = field_info.annotation
498
- current_value = getattr(context, field_name)
499
- description = getattr(
500
- field_info, "description", f"Current value: {current_value}"
501
- )
502
- field_definitions[field_name] = (
503
- field_type,
504
- Field(description=description),
505
- )
506
-
507
- return create_model("ContextUpdate", **field_definitions)
508
- elif isinstance(context, dict):
509
- # Create a model with the same keys as the dict
510
- field_definitions = {}
511
- for key, value in context.items():
512
- field_type = type(value)
513
- description = f"Current value: {value}"
514
- field_definitions[key] = (
515
- field_type,
516
- Field(description=description),
517
- )
518
-
519
- return create_model("ContextUpdate", **field_definitions)
520
- else:
521
- # Fallback to generic updates
522
- return create_model(
523
- "ContextUpdate",
524
- updates=(
525
- Dict[str, Any],
526
- Field(description="Dictionary of field updates"),
527
- ),
528
- )
529
-
530
- def _perform_context_update(
531
- self,
532
- context: AgentContext,
533
- model: LanguageModel,
534
- current_messages: List[Dict[str, Any]],
535
- timing: Literal["before", "after"],
536
- effective_settings: Optional[dict] = None,
537
- ) -> AgentContext:
538
- """Perform context update with retries and error handling."""
539
- updated_context = context
540
-
541
- # Use effective settings or defaults
542
- if effective_settings is None:
543
- effective_settings = {
544
- "context_confirm": self.context_confirm,
545
- "context_strategy": self.context_strategy,
546
- "context_max_retries": self.context_max_retries,
547
- "context_confirm_instructions": self.context_confirm_instructions,
548
- "context_selection_instructions": self.context_selection_instructions,
549
- "context_update_instructions": self.context_update_instructions,
550
- "context_format": self.context_format,
551
- }
552
-
553
- for attempt in range(effective_settings["context_max_retries"]):
554
- try:
555
- # Check if update is needed (if confirmation is enabled)
556
- if effective_settings["context_confirm"]:
557
- confirm_model = self._create_context_confirm_model()
558
-
559
- # Create detailed instructions with context structure
560
- context_structure = _format_context_for_instructions(
561
- updated_context, effective_settings["context_format"]
562
- )
563
- confirm_instructions = f"""Based on the conversation, determine if the context should be updated {timing} processing.
564
-
565
- Current context structure:
566
- {context_structure}
567
-
568
- Should the context be updated based on the new information provided in the conversation?"""
569
-
570
- if effective_settings["context_confirm_instructions"]:
571
- confirm_instructions += f"\n\nAdditional instructions: {effective_settings['context_confirm_instructions']}"
572
-
573
- confirm_response = model.run(
574
- messages=current_messages
575
- + [{"role": "user", "content": confirm_instructions}],
576
- type=confirm_model,
577
- instructor_mode=self.instructor_mode,
578
- )
579
-
580
- if not confirm_response.output.decision:
581
- return updated_context
582
-
583
- # Perform the update based on strategy
584
- if effective_settings["context_strategy"] == "selective":
585
- # Get fields to update
586
- selection_model = self._create_context_selection_model(
587
- updated_context
588
- )
589
-
590
- # Create detailed instructions with context structure
591
- context_structure = _format_context_for_instructions(
592
- updated_context, effective_settings["context_format"]
593
- )
594
- selection_instructions = f"""Select which fields in the context should be updated {timing} processing based on the conversation.
595
-
596
- Current context structure:
597
- {context_structure}
598
-
599
- Choose only the fields that need to be updated based on the new information provided in the conversation."""
600
-
601
- if effective_settings["context_selection_instructions"]:
602
- selection_instructions += f"\n\nAdditional instructions: {effective_settings['context_selection_instructions']}"
603
-
604
- selection_response = model.run(
605
- messages=current_messages
606
- + [{"role": "user", "content": selection_instructions}],
607
- type=selection_model,
608
- instructor_mode=self.instructor_mode,
609
- )
610
-
611
- # Update each selected field
612
- for field_enum in selection_response.output.fields:
613
- field_name = field_enum.value
614
- field_model = self._create_context_update_model(
615
- updated_context, field_name
616
- )
617
- # Get current field value for context
618
- current_value = (
619
- getattr(updated_context, field_name)
620
- if isinstance(updated_context, BaseModel)
621
- else updated_context.get(field_name)
622
- )
623
-
624
- field_instructions = f"""Update the {field_name} field in the context based on the conversation.
625
-
626
- Current value of {field_name}: {current_value}
627
-
628
- Please provide the new value for {field_name} based on the information from the conversation."""
629
-
630
- if effective_settings["context_update_instructions"]:
631
- field_instructions += f"\n\nAdditional instructions: {effective_settings['context_update_instructions']}"
632
-
633
- field_response = model.run(
634
- messages=current_messages
635
- + [{"role": "user", "content": field_instructions}],
636
- type=field_model,
637
- instructor_mode=self.instructor_mode,
638
- )
639
-
640
- # Apply the update
641
- field_updates = {
642
- field_name: getattr(field_response.output, field_name)
643
- }
644
- updated_context = _update_context_object(
645
- updated_context, field_updates
646
- )
647
-
648
- else: # strategy == "all"
649
- # Update all fields at once
650
- update_model = self._create_context_update_model(updated_context)
651
-
652
- # Create detailed instructions with context structure
653
- context_structure = _format_context_for_instructions(
654
- updated_context, effective_settings["context_format"]
655
- )
656
- update_instructions = f"""Update the context {timing} processing based on the conversation.
657
-
658
- Current context structure:
659
- {context_structure}
660
-
661
- Please update the appropriate fields based on the conversation. Only update fields that need to be changed based on the new information provided."""
662
-
663
- if effective_settings["context_update_instructions"]:
664
- update_instructions += f"\n\nAdditional instructions: {effective_settings['context_update_instructions']}"
665
-
666
- update_response = model.run(
667
- messages=current_messages
668
- + [{"role": "user", "content": update_instructions}],
669
- type=update_model,
670
- instructor_mode=self.instructor_mode,
671
- )
672
-
673
- # Apply the updates
674
- if hasattr(update_response.output, "updates"):
675
- # Legacy fallback for generic updates
676
- updated_context = _update_context_object(
677
- updated_context, update_response.output.updates
678
- )
679
- else:
680
- # New approach - extract field values directly from the response
681
- updates_dict = {}
682
- for field_name in (
683
- context.model_fields.keys()
684
- if isinstance(context, BaseModel)
685
- else context.keys()
686
- ):
687
- if hasattr(update_response.output, field_name):
688
- updates_dict[field_name] = getattr(
689
- update_response.output, field_name
690
- )
691
- updated_context = _update_context_object(
692
- updated_context, updates_dict
693
- )
694
-
695
- # Trigger context update hooks
696
- self.hook_manager.trigger_hooks("context_update", updated_context)
697
-
698
- return updated_context
699
-
700
- except Exception as e:
701
- if attempt == self.context_max_retries - 1:
702
- # Last attempt failed, return original context
703
- return updated_context
704
- # Continue to next attempt
705
- continue
706
-
707
- return updated_context
708
-
709
- def _format_messages_with_context(
710
- self, messages: List[Dict[str, Any]], context: Optional[AgentContext] = None
711
- ) -> List[Dict[str, Any]]:
712
- """Format messages with instructions and context."""
713
- formatted_messages = messages.copy()
714
-
715
- if self.instructions:
716
- system_content = self.instructions
717
-
718
- # Add context if provided
719
- if context is not None:
720
- context_str = _format_context_for_instructions(
721
- context, self.context_format
722
- )
723
- if context_str:
724
- system_content += f"\n\nContext:\n{context_str}"
725
-
726
- system_message = {"role": "system", "content": system_content}
727
- formatted_messages = [system_message] + formatted_messages
728
-
729
- return consolidate_system_messages(formatted_messages)
730
-
731
- # Overloaded run methods for streaming support
732
- @overload
733
- def run(
734
- self,
735
- messages: AgentMessages,
736
- model: Optional[Union[LanguageModel, LanguageModelName]] = None,
737
- max_steps: Optional[int] = None,
738
- context: Optional[AgentContext] = None,
739
- output_type: Optional[Type[T]] = None,
740
- end_strategy: Optional[Literal["tool"]] = None,
741
- end_tool: Optional[Callable] = None,
742
- context_updates: Optional[
743
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
744
- ] = None,
745
- context_confirm: Optional[bool] = None,
746
- context_strategy: Optional[Literal["selective", "all"]] = None,
747
- context_max_retries: Optional[int] = None,
748
- context_confirm_instructions: Optional[str] = None,
749
- context_selection_instructions: Optional[str] = None,
750
- context_update_instructions: Optional[str] = None,
751
- context_format: Optional[Literal["json", "python", "markdown"]] = None,
752
- verbose: Optional[bool] = None,
753
- debug: Optional[bool] = None,
754
- *,
755
- stream: Literal[False] = False,
756
- **kwargs: Any,
757
- ) -> AgentResponse[T, AgentContext]: ...
758
-
759
- @overload
760
- def run(
761
- self,
762
- messages: AgentMessages,
763
- model: Optional[Union[LanguageModel, LanguageModelName]] = None,
764
- max_steps: Optional[int] = None,
765
- context: Optional[AgentContext] = None,
766
- output_type: Optional[Type[T]] = None,
767
- end_strategy: Optional[Literal["tool"]] = None,
768
- end_tool: Optional[Callable] = None,
769
- context_updates: Optional[
770
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
771
- ] = None,
772
- context_confirm: Optional[bool] = None,
773
- context_strategy: Optional[Literal["selective", "all"]] = None,
774
- context_max_retries: Optional[int] = None,
775
- context_confirm_instructions: Optional[str] = None,
776
- context_selection_instructions: Optional[str] = None,
777
- context_update_instructions: Optional[str] = None,
778
- context_format: Optional[Literal["json", "python", "markdown"]] = None,
779
- verbose: Optional[bool] = None,
780
- debug: Optional[bool] = None,
781
- *,
782
- stream: Literal[True],
783
- **kwargs: Any,
784
- ) -> AgentStream[T, AgentContext]: ...
785
-
786
- def run(
787
- self,
788
- messages: AgentMessages,
789
- model: Optional[Union[LanguageModel, LanguageModelName]] = None,
790
- max_steps: Optional[int] = None,
791
- context: Optional[AgentContext] = None,
792
- output_type: Optional[Type[T]] = None,
793
- end_strategy: Optional[Literal["tool"]] = None,
794
- end_tool: Optional[Callable] = None,
795
- context_updates: Optional[
796
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
797
- ] = None,
798
- context_confirm: Optional[bool] = None,
799
- context_strategy: Optional[Literal["selective", "all"]] = None,
800
- context_max_retries: Optional[int] = None,
801
- context_confirm_instructions: Optional[str] = None,
802
- context_selection_instructions: Optional[str] = None,
803
- context_update_instructions: Optional[str] = None,
804
- context_format: Optional[Literal["json", "python", "markdown"]] = None,
805
- verbose: Optional[bool] = None,
806
- debug: Optional[bool] = None,
807
- stream: bool = False,
808
- **kwargs: Any,
809
- ) -> Union[AgentResponse[T, AgentContext], AgentStream[T, AgentContext]]:
810
- """Runs this agent and returns a final agent response or stream.
811
-
812
- You can override defaults assigned to this agent from this function directly.
813
-
814
- Args:
815
- messages: The messages to process. Can be:
816
- - A single string: "What's the weather like?"
817
- - A list of message dicts: [{"role": "user", "content": "Hello"}]
818
- - A list of strings: ["Hello", "How are you?"]
819
- model: The model to use for this run (overrides default).
820
- - Can be a LanguageModel instance or model name string like "gpt-4"
821
- max_steps: Maximum number of steps to execute (overrides default).
822
- - Useful for limiting tool usage or preventing infinite loops
823
- context: Context object for the agent (overrides default).
824
- - Any object that provides additional context for the conversation
825
- output_type: The expected output type (overrides default).
826
- - Use for structured outputs: output_type=MyPydanticModel
827
- - Defaults to str for unstructured text responses
828
- stream: Whether to return a stream instead of a final response.
829
- - If True, returns AgentStream for real-time processing
830
- - If False, returns complete AgentResponse
831
- **kwargs: Additional keyword arguments passed to the language model.
832
- - Examples: temperature=0.7, top_p=0.9, presence_penalty=0.1
833
-
834
- Returns:
835
- AgentResponse or AgentStream depending on stream parameter.
836
- - AgentResponse: Contains final output, steps taken, and metadata
837
- - AgentStream: Iterator yielding intermediate steps and final result
838
-
839
- Examples:
840
- Basic text conversation:
841
- >>> agent = Agent()
842
- >>> response = agent.run("Hello, how are you?")
843
- >>> print(response.output)
844
- "Hello! I'm doing well, thank you for asking."
845
-
846
- With custom model and parameters:
847
- >>> response = agent.run(
848
- ... messages="Explain quantum computing",
849
- ... model="gpt-4",
850
- ... max_steps=5,
851
- ... temperature=0.3
852
- ... )
853
-
854
- Structured output with Pydantic model:
855
- >>> from pydantic import BaseModel
856
- >>> class Summary(BaseModel):
857
- ... title: str
858
- ... key_points: List[str]
859
- >>> response = agent.run(
860
- ... "Summarize the benefits of renewable energy",
861
- ... output_type=Summary
862
- ... )
863
- >>> print(response.output.title)
864
- >>> print(response.output.key_points)
865
-
866
- Streaming for real-time results:
867
- >>> stream = agent.run(
868
- ... "Write a long story about space exploration",
869
- ... stream=True
870
- ... )
871
- >>> for chunk in stream:
872
- ... print(chunk.output, end="", flush=True)
873
-
874
- With context for additional information:
875
- >>> context = {"user_preferences": "technical explanations"}
876
- >>> response = agent.run(
877
- ... "How does machine learning work?",
878
- ... context=context
879
- ... )
880
- """
881
- # Handle streaming
882
- if stream:
883
- return AgentStream(
884
- agent=self,
885
- messages=messages,
886
- model=model,
887
- max_steps=max_steps,
888
- context=context,
889
- output_type=output_type,
890
- stream=stream,
891
- **kwargs,
892
- )
893
-
894
- # Set logger level for this request if specified
895
- original_level = logger.level
896
- if debug or (debug is None and self.debug):
897
- logger.level = "debug"
898
- elif verbose or (verbose is None and self.verbose):
899
- logger.level = "info"
900
-
901
- # Log agent execution start
902
- logger.info(f"Starting agent '{self.name}' execution")
903
- logger.debug(
904
- f"Agent settings: max_steps={max_steps or self.max_steps}, tools={len(self.tools)}"
905
- )
906
-
907
- try:
908
- # Use provided model or default
909
- if model is None:
910
- working_model = self.language_model
911
- elif isinstance(model, str):
912
- working_model = LanguageModel(
913
- model=model,
914
- verbose=verbose or self.verbose,
915
- debug=debug or self.debug,
916
- )
917
- else:
918
- working_model = model
919
-
920
- # Use provided max_steps or default from instance
921
- if max_steps is None:
922
- max_steps = self.max_steps
923
-
924
- # Use provided end_strategy or default from instance
925
- effective_end_strategy = (
926
- end_strategy if end_strategy is not None else self.end_strategy
927
- )
928
- effective_end_tool = end_tool if end_tool is not None else self.end_tool
929
-
930
- # Create working tools list with end_tool if needed
931
- working_tools = self.tools.copy()
932
- if effective_end_strategy == "tool" and effective_end_tool is not None:
933
- end_tool_obj = define_tool(effective_end_tool)
934
- # Only add if not already present
935
- if not any(tool.name == end_tool_obj.name for tool in working_tools):
936
- working_tools.append(end_tool_obj)
937
-
938
- # Get effective context settings
939
- effective_context_settings = self._get_effective_context_settings(
940
- context_updates=context_updates,
941
- context_confirm=context_confirm,
942
- context_strategy=context_strategy,
943
- context_max_retries=context_max_retries,
944
- context_confirm_instructions=context_confirm_instructions,
945
- context_selection_instructions=context_selection_instructions,
946
- context_update_instructions=context_update_instructions,
947
- context_format=context_format,
948
- )
949
-
950
- # Parse initial messages
951
- parsed_messages = parse_messages(messages)
952
- current_messages = parsed_messages.copy()
953
- steps: List[LanguageModelResponse[str]] = []
954
-
955
- # RUN MAIN AGENTIC LOOP
956
- logger.debug(f"Starting agentic loop with max_steps={max_steps}")
957
- for step in range(max_steps):
958
- logger.debug(f"Agent step {step + 1}/{max_steps}")
959
- # Update context before processing if configured
960
- if context and self._should_update_context(
961
- context, "before", effective_context_settings["context_updates"]
962
- ):
963
- context = self._perform_context_update(
964
- context=context,
965
- model=working_model,
966
- current_messages=current_messages,
967
- timing="before",
968
- effective_settings=effective_context_settings,
969
- )
970
-
971
- # Format messages with instructions and context for first step only
972
- if step == 0:
973
- formatted_messages = self._format_messages_with_context(
974
- messages=current_messages,
975
- context=context,
976
- )
977
- else:
978
- formatted_messages = current_messages
979
-
980
- # Prepare kwargs for language model
981
- model_kwargs = kwargs.copy()
982
- # Don't add output_type for intermediate steps - only for final response
983
- if self.instructor_mode:
984
- model_kwargs["instructor_mode"] = self.instructor_mode
985
-
986
- # Get language model response
987
- response = working_model.run(
988
- messages=formatted_messages,
989
- tools=[tool.to_dict() for tool in working_tools]
990
- if working_tools
991
- else None,
992
- **model_kwargs,
993
- )
994
-
995
- # Check if response has tool calls
996
- if response.has_tool_calls():
997
- logger.info(
998
- f"Agent '{self.name}' making tool calls: {len(response.tool_calls)} tools"
999
- )
1000
- for tool_call in response.tool_calls:
1001
- logger.debug(
1002
- f"Tool call: {tool_call.function.name}({tool_call.function.arguments})"
1003
- )
1004
-
1005
- # Add response to message history (with tool calls)
1006
- current_messages.append(response.to_message())
1007
-
1008
- # Execute tools and add their responses to messages
1009
- tool_responses = execute_tools_from_language_model_response(
1010
- tools=working_tools, response=response
1011
- )
1012
- # Add tool responses to message history
1013
- for tool_resp in tool_responses:
1014
- current_messages.append(tool_resp.to_dict())
1015
-
1016
- # This is not the final step, add to steps
1017
- steps.append(response)
1018
- else:
1019
- # No tool calls - check if this is actually the final step based on end_strategy
1020
- if effective_end_strategy == "tool":
1021
- # Check if the end_tool was called
1022
- end_tool_called = (
1023
- any(
1024
- tool_call.function.name == effective_end_tool.__name__
1025
- for tool_call in response.tool_calls
1026
- )
1027
- if response.tool_calls
1028
- else False
1029
- )
1030
-
1031
- if not end_tool_called:
1032
- # End tool not called, continue the conversation
1033
- logger.debug(
1034
- f"Agent '{self.name}' step {step + 1}: No end tool called, continuing..."
1035
- )
1036
-
1037
- # Add the response to history
1038
- current_messages.append(response.to_message())
1039
-
1040
- # Add system message instructing agent to call the end tool
1041
- current_messages.append(
1042
- {
1043
- "role": "system",
1044
- "content": f"You must call the {effective_end_tool.__name__} tool to complete your response. Do not provide a final answer until you have called this tool.",
1045
- }
1046
- )
1047
-
1048
- # Add user message to continue
1049
- current_messages.append(
1050
- {"role": "user", "content": "continue"}
1051
- )
1052
-
1053
- # Remove the continue message and append assistant content
1054
- current_messages.pop() # Remove "continue" message
1055
-
1056
- # This is not the final step, add to steps and continue
1057
- steps.append(response)
1058
- continue
1059
-
1060
- # This is the final step (either no end_strategy or end_tool was called)
1061
- logger.info(
1062
- f"Agent '{self.name}' completed execution in {step + 1} steps"
1063
- )
1064
- # Now we can make the final call with the output_type if specified
1065
- # Only make structured output call for non-str types
1066
- if output_type and output_type != str:
1067
- # Make a final call with the structured output type
1068
- final_model_kwargs = kwargs.copy()
1069
- final_model_kwargs["type"] = output_type
1070
- if self.instructor_mode:
1071
- final_model_kwargs["instructor_mode"] = self.instructor_mode
1072
-
1073
- # Create a clean conversation history for structured output
1074
- # Include the original messages and the final response content
1075
- clean_messages = []
1076
- # Add original user messages (excluding tool calls/responses)
1077
- for msg in formatted_messages:
1078
- if isinstance(msg, dict) and msg.get("role") not in [
1079
- "tool",
1080
- "assistant",
1081
- ]:
1082
- clean_messages.append(msg)
1083
- elif hasattr(msg, "role") and msg.role not in [
1084
- "tool",
1085
- "assistant",
1086
- ]:
1087
- clean_messages.append(msg.to_dict())
1088
-
1089
- # Add the final assistant response content
1090
- clean_messages.append(
1091
- {"role": "assistant", "content": response.get_content()}
1092
- )
1093
-
1094
- # Use the clean conversation history to generate structured output
1095
- final_response = working_model.run(
1096
- messages=clean_messages,
1097
- **final_model_kwargs,
1098
- )
1099
-
1100
- # Update context after processing if configured
1101
- if context and self._should_update_context(
1102
- context,
1103
- "after",
1104
- effective_context_settings["context_updates"],
1105
- ):
1106
- context = self._perform_context_update(
1107
- context=context,
1108
- model=working_model,
1109
- current_messages=current_messages,
1110
- timing="after",
1111
- effective_settings=effective_context_settings,
1112
- )
1113
- return _create_agent_response_from_language_model_response(
1114
- response=final_response, steps=steps, context=context
1115
- )
1116
- else:
1117
- # Update context after processing if configured
1118
- if context and self._should_update_context(
1119
- context,
1120
- "after",
1121
- effective_context_settings["context_updates"],
1122
- ):
1123
- context = self._perform_context_update(
1124
- context=context,
1125
- model=working_model,
1126
- current_messages=current_messages,
1127
- timing="after",
1128
- effective_settings=effective_context_settings,
1129
- )
1130
- return _create_agent_response_from_language_model_response(
1131
- response=response, steps=steps, context=context
1132
- )
1133
-
1134
- # Max steps reached - return last response
1135
- if steps:
1136
- final_response = steps[-1]
1137
- # If we have an output_type, make a final structured call (but not for str)
1138
- if output_type and output_type != str:
1139
- final_model_kwargs = kwargs.copy()
1140
- final_model_kwargs["type"] = output_type
1141
- if self.instructor_mode:
1142
- final_model_kwargs["instructor_mode"] = self.instructor_mode
1143
-
1144
- # Create clean messages for structured output
1145
- clean_messages = []
1146
- formatted_messages = self._format_messages_with_context(
1147
- messages=current_messages,
1148
- context=context,
1149
- )
1150
-
1151
- # Add original user messages (excluding tool calls/responses)
1152
- for msg in formatted_messages:
1153
- if isinstance(msg, dict) and msg.get("role") not in [
1154
- "tool",
1155
- "assistant",
1156
- ]:
1157
- clean_messages.append(msg)
1158
- elif hasattr(msg, "role") and msg.role not in [
1159
- "tool",
1160
- "assistant",
1161
- ]:
1162
- clean_messages.append(msg.to_dict())
1163
-
1164
- # Add final response content
1165
- clean_messages.append(
1166
- {"role": "assistant", "content": final_response.get_content()}
1167
- )
1168
-
1169
- final_response = working_model.run(
1170
- messages=clean_messages,
1171
- **final_model_kwargs,
1172
- )
1173
- else:
1174
- # No steps taken, make a final call
1175
- final_model_kwargs = kwargs.copy()
1176
- if output_type and output_type != str:
1177
- final_model_kwargs["type"] = output_type
1178
- if self.instructor_mode:
1179
- final_model_kwargs["instructor_mode"] = self.instructor_mode
1180
-
1181
- final_response = working_model.run(
1182
- messages=self._format_messages_with_context(
1183
- messages=current_messages,
1184
- context=context,
1185
- ),
1186
- **final_model_kwargs,
1187
- )
1188
-
1189
- # Update context after processing if configured
1190
- if context and self._should_update_context(
1191
- context, "after", effective_context_settings["context_updates"]
1192
- ):
1193
- context = self._perform_context_update(
1194
- context=context,
1195
- model=working_model,
1196
- current_messages=current_messages,
1197
- timing="after",
1198
- effective_settings=effective_context_settings,
1199
- )
1200
-
1201
- return _create_agent_response_from_language_model_response(
1202
- response=final_response, steps=steps, context=context
1203
- )
1204
-
1205
- finally:
1206
- # Restore original logger level
1207
- if debug is not None or verbose is not None:
1208
- logger.level = original_level
1209
-
1210
- async def async_run(
1211
- self,
1212
- messages: AgentMessages,
1213
- model: Optional[Union[LanguageModel, LanguageModelName]] = None,
1214
- max_steps: Optional[int] = None,
1215
- context: Optional[AgentContext] = None,
1216
- output_type: Optional[Type[T]] = None,
1217
- context_updates: Optional[
1218
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
1219
- ] = None,
1220
- context_confirm: Optional[bool] = None,
1221
- context_strategy: Optional[Literal["selective", "all"]] = None,
1222
- context_max_retries: Optional[int] = None,
1223
- context_confirm_instructions: Optional[str] = None,
1224
- context_selection_instructions: Optional[str] = None,
1225
- context_update_instructions: Optional[str] = None,
1226
- context_format: Optional[Literal["json", "python", "markdown"]] = None,
1227
- verbose: Optional[bool] = None,
1228
- debug: Optional[bool] = None,
1229
- end_strategy: Optional[Literal["tool"]] = None,
1230
- end_tool: Optional[Callable] = None,
1231
- **kwargs: Any,
1232
- ) -> AgentResponse[T, AgentContext]:
1233
- """Runs this agent asynchronously and returns a final agent response.
1234
-
1235
- You can override defaults assigned to this agent from this function directly.
1236
- This is the async version of run() for non-blocking execution.
1237
-
1238
- Args:
1239
- messages: The messages to process. Can be:
1240
- - A single string: "What's the weather like?"
1241
- - A list of message dicts: [{"role": "user", "content": "Hello"}]
1242
- - A list of strings: ["Hello", "How are you?"]
1243
- model: The model to use for this run (overrides default).
1244
- - Can be a LanguageModel instance or model name string like "gpt-4"
1245
- max_steps: Maximum number of steps to execute (overrides default).
1246
- - Useful for limiting tool usage or preventing infinite loops
1247
- context: Context object for the agent (overrides default).
1248
- - Any object that provides additional context for the conversation
1249
- output_type: The expected output type (overrides default).
1250
- - Use for structured outputs: output_type=MyPydanticModel
1251
- - Defaults to str for unstructured text responses
1252
- **kwargs: Additional keyword arguments passed to the language model.
1253
- - Examples: temperature=0.7, top_p=0.9, presence_penalty=0.1
1254
-
1255
- Returns:
1256
- AgentResponse containing the final output, steps taken, and metadata.
1257
-
1258
- Examples:
1259
- Basic async usage:
1260
- >>> import asyncio
1261
- >>> agent = Agent()
1262
- >>> async def main():
1263
- ... response = await agent.async_run("Hello, how are you?")
1264
- ... print(response.output)
1265
- >>> asyncio.run(main())
1266
-
1267
- Multiple concurrent requests:
1268
- >>> async def process_multiple():
1269
- ... tasks = [
1270
- ... agent.async_run("What's 2+2?"),
1271
- ... agent.async_run("What's the capital of France?"),
1272
- ... agent.async_run("Explain photosynthesis")
1273
- ... ]
1274
- ... responses = await asyncio.gather(*tasks)
1275
- ... return responses
1276
-
1277
- With structured output:
1278
- >>> from pydantic import BaseModel
1279
- >>> class Analysis(BaseModel):
1280
- ... sentiment: str
1281
- ... confidence: float
1282
- >>> async def analyze_text():
1283
- ... response = await agent.async_run(
1284
- ... "Analyze the sentiment of: 'I love this product!'",
1285
- ... output_type=Analysis
1286
- ... )
1287
- ... return response.output
1288
-
1289
- With custom model and context:
1290
- >>> async def custom_run():
1291
- ... context = {"domain": "medical", "expertise_level": "expert"}
1292
- ... response = await agent.async_run(
1293
- ... "Explain diabetes",
1294
- ... model="gpt-4",
1295
- ... context=context,
1296
- ... temperature=0.2
1297
- ... )
1298
- ... return response.output
1299
- """
1300
- # Set logger level for this request if specified
1301
- original_level = logger.level
1302
- if debug or (debug is None and self.debug):
1303
- logger.level = "debug"
1304
- elif verbose or (verbose is None and self.verbose):
1305
- logger.level = "info"
1306
-
1307
- try:
1308
- # Use provided model or default
1309
- if model is None:
1310
- working_model = self.language_model
1311
- elif isinstance(model, str):
1312
- working_model = LanguageModel(
1313
- model=model,
1314
- verbose=verbose or self.verbose,
1315
- debug=debug or self.debug,
1316
- )
1317
- else:
1318
- working_model = model
1319
-
1320
- # Use provided max_steps or default from instance
1321
- if max_steps is None:
1322
- max_steps = self.max_steps
1323
-
1324
- # Use provided end_strategy or default from instance
1325
- effective_end_strategy = (
1326
- end_strategy if end_strategy is not None else self.end_strategy
1327
- )
1328
- effective_end_tool = end_tool if end_tool is not None else self.end_tool
1329
-
1330
- # Create working tools list with end_tool if needed
1331
- working_tools = self.tools.copy()
1332
- if effective_end_strategy == "tool" and effective_end_tool is not None:
1333
- end_tool_obj = define_tool(effective_end_tool)
1334
- # Only add if not already present
1335
- if not any(tool.name == end_tool_obj.name for tool in working_tools):
1336
- working_tools.append(end_tool_obj)
1337
-
1338
- # Get effective context settings
1339
- effective_context_settings = self._get_effective_context_settings(
1340
- context_updates=context_updates,
1341
- context_confirm=context_confirm,
1342
- context_strategy=context_strategy,
1343
- context_max_retries=context_max_retries,
1344
- context_confirm_instructions=context_confirm_instructions,
1345
- context_selection_instructions=context_selection_instructions,
1346
- context_update_instructions=context_update_instructions,
1347
- context_format=context_format,
1348
- )
1349
-
1350
- # Parse initial messages
1351
- parsed_messages = parse_messages(messages)
1352
- current_messages = parsed_messages.copy()
1353
- steps: List[LanguageModelResponse[str]] = []
1354
-
1355
- # RUN MAIN AGENTIC LOOP
1356
- for step in range(max_steps):
1357
- # Update context before processing if configured
1358
- if context and self._should_update_context(
1359
- context, "before", effective_context_settings["context_updates"]
1360
- ):
1361
- context = self._perform_context_update(
1362
- context=context,
1363
- model=working_model,
1364
- current_messages=current_messages,
1365
- timing="before",
1366
- effective_settings=effective_context_settings,
1367
- )
1368
-
1369
- # Format messages with instructions and context for first step only
1370
- if step == 0:
1371
- formatted_messages = self._format_messages_with_context(
1372
- messages=current_messages,
1373
- context=context,
1374
- )
1375
- else:
1376
- formatted_messages = current_messages
1377
-
1378
- # Prepare kwargs for language model
1379
- model_kwargs = kwargs.copy()
1380
- # Don't add output_type for intermediate steps - only for final response
1381
- if self.instructor_mode:
1382
- model_kwargs["instructor_mode"] = self.instructor_mode
1383
-
1384
- # Get language model response
1385
- response = await working_model.async_run(
1386
- messages=formatted_messages,
1387
- tools=[tool.to_dict() for tool in working_tools]
1388
- if working_tools
1389
- else None,
1390
- **model_kwargs,
1391
- )
1392
-
1393
- # Check if response has tool calls
1394
- if response.has_tool_calls():
1395
- # Add response to message history (with tool calls)
1396
- current_messages.append(response.to_message())
1397
-
1398
- # Execute tools and add their responses to messages
1399
- tool_responses = execute_tools_from_language_model_response(
1400
- tools=working_tools, response=response
1401
- )
1402
- # Add tool responses to message history
1403
- for tool_resp in tool_responses:
1404
- current_messages.append(tool_resp.to_dict())
1405
-
1406
- # This is not the final step, add to steps
1407
- steps.append(response)
1408
- else:
1409
- # No tool calls - check if this is actually the final step based on end_strategy
1410
- if effective_end_strategy == "tool":
1411
- # Check if the end_tool was called
1412
- end_tool_called = (
1413
- any(
1414
- tool_call.function.name == effective_end_tool.__name__
1415
- for tool_call in response.tool_calls
1416
- )
1417
- if response.tool_calls
1418
- else False
1419
- )
1420
-
1421
- if not end_tool_called:
1422
- # End tool not called, continue the conversation
1423
- logger.debug(
1424
- f"Agent '{self.name}' step {step + 1}: No end tool called, continuing..."
1425
- )
1426
-
1427
- # Add the response to history
1428
- current_messages.append(response.to_message())
1429
-
1430
- # Add system message instructing agent to call the end tool
1431
- current_messages.append(
1432
- {
1433
- "role": "system",
1434
- "content": f"You must call the {effective_end_tool.__name__} tool to complete your response. Do not provide a final answer until you have called this tool.",
1435
- }
1436
- )
1437
-
1438
- # Add user message to continue
1439
- current_messages.append(
1440
- {"role": "user", "content": "continue"}
1441
- )
1442
-
1443
- # Remove the continue message and append assistant content
1444
- current_messages.pop() # Remove "continue" message
1445
-
1446
- # This is not the final step, add to steps and continue
1447
- steps.append(response)
1448
- continue
1449
-
1450
- # This is the final step (either no end_strategy or end_tool was called)
1451
- # Now we can make the final call with the output_type if specified
1452
- # Only make structured output call for non-str types
1453
- if output_type and output_type != str:
1454
- # Make a final call with the structured output type
1455
- final_model_kwargs = kwargs.copy()
1456
- final_model_kwargs["type"] = output_type
1457
- if self.instructor_mode:
1458
- final_model_kwargs["instructor_mode"] = self.instructor_mode
1459
-
1460
- # Create a clean conversation history for structured output
1461
- # Include the original messages and the final response content
1462
- clean_messages = []
1463
- # Add original user messages (excluding tool calls/responses)
1464
- for msg in formatted_messages:
1465
- if isinstance(msg, dict) and msg.get("role") not in [
1466
- "tool",
1467
- "assistant",
1468
- ]:
1469
- clean_messages.append(msg)
1470
- elif hasattr(msg, "role") and msg.role not in [
1471
- "tool",
1472
- "assistant",
1473
- ]:
1474
- clean_messages.append(msg.to_dict())
1475
-
1476
- # Add the final assistant response content
1477
- clean_messages.append(
1478
- {"role": "assistant", "content": response.get_content()}
1479
- )
1480
-
1481
- # Use the clean conversation history to generate structured output
1482
- final_response = await working_model.async_run(
1483
- messages=clean_messages,
1484
- **final_model_kwargs,
1485
- )
1486
-
1487
- # Update context after processing if configured
1488
- if context and self._should_update_context(
1489
- context,
1490
- "after",
1491
- effective_context_settings["context_updates"],
1492
- ):
1493
- context = self._perform_context_update(
1494
- context=context,
1495
- model=working_model,
1496
- current_messages=current_messages,
1497
- timing="after",
1498
- effective_settings=effective_context_settings,
1499
- )
1500
- return _create_agent_response_from_language_model_response(
1501
- response=final_response, steps=steps, context=context
1502
- )
1503
- else:
1504
- # Update context after processing if configured
1505
- if context and self._should_update_context(
1506
- context,
1507
- "after",
1508
- effective_context_settings["context_updates"],
1509
- ):
1510
- context = self._perform_context_update(
1511
- context=context,
1512
- model=working_model,
1513
- current_messages=current_messages,
1514
- timing="after",
1515
- effective_settings=effective_context_settings,
1516
- )
1517
- return _create_agent_response_from_language_model_response(
1518
- response=response, steps=steps, context=context
1519
- )
1520
-
1521
- # Max steps reached - return last response
1522
- if steps:
1523
- final_response = steps[-1]
1524
- # If we have an output_type, make a final structured call (but not for str)
1525
- if output_type and output_type != str:
1526
- final_model_kwargs = kwargs.copy()
1527
- final_model_kwargs["type"] = output_type
1528
- if self.instructor_mode:
1529
- final_model_kwargs["instructor_mode"] = self.instructor_mode
1530
-
1531
- # Create clean messages for structured output
1532
- clean_messages = []
1533
- formatted_messages = self._format_messages_with_context(
1534
- messages=current_messages,
1535
- context=context,
1536
- )
1537
-
1538
- # Add original user messages (excluding tool calls/responses)
1539
- for msg in formatted_messages:
1540
- if isinstance(msg, dict) and msg.get("role") not in [
1541
- "tool",
1542
- "assistant",
1543
- ]:
1544
- clean_messages.append(msg)
1545
- elif hasattr(msg, "role") and msg.role not in [
1546
- "tool",
1547
- "assistant",
1548
- ]:
1549
- clean_messages.append(msg.to_dict())
1550
-
1551
- # Add final response content
1552
- clean_messages.append(
1553
- {"role": "assistant", "content": final_response.get_content()}
1554
- )
1555
-
1556
- final_response = await working_model.async_run(
1557
- messages=clean_messages,
1558
- **final_model_kwargs,
1559
- )
1560
- else:
1561
- # No steps taken, make a final call
1562
- final_model_kwargs = kwargs.copy()
1563
- if output_type and output_type != str:
1564
- final_model_kwargs["type"] = output_type
1565
- if self.instructor_mode:
1566
- final_model_kwargs["instructor_mode"] = self.instructor_mode
1567
-
1568
- final_response = await working_model.async_run(
1569
- messages=self._format_messages_with_context(
1570
- messages=current_messages,
1571
- context=context,
1572
- ),
1573
- **final_model_kwargs,
1574
- )
1575
-
1576
- # Update context after processing if configured
1577
- if context and self._should_update_context(
1578
- context, "after", effective_context_settings["context_updates"]
1579
- ):
1580
- context = self._perform_context_update(
1581
- context=context,
1582
- model=working_model,
1583
- current_messages=current_messages,
1584
- timing="after",
1585
- effective_settings=effective_context_settings,
1586
- )
1587
-
1588
- return _create_agent_response_from_language_model_response(
1589
- response=final_response, steps=steps, context=context
1590
- )
1591
-
1592
- finally:
1593
- # Restore original logger level
1594
- if debug is not None or verbose is not None:
1595
- logger.level = original_level
1596
-
1597
- def stream(
1598
- self,
1599
- messages: AgentMessages,
1600
- model: Optional[Union[LanguageModel, LanguageModelName]] = None,
1601
- max_steps: Optional[int] = None,
1602
- context: Optional[AgentContext] = None,
1603
- output_type: Optional[Type[T]] = None,
1604
- **kwargs: Any,
1605
- ) -> AgentStream[T, AgentContext]:
1606
- """Create a stream that yields agent steps.
1607
-
1608
- Args:
1609
- messages: The input messages to process
1610
- model: Language model to use (overrides agent's default)
1611
- max_steps: Maximum number of steps to take
1612
- context: Context object to maintain state
1613
- output_type: Type for structured output
1614
- **kwargs: Additional parameters for the language model
1615
-
1616
- Returns:
1617
- An AgentStream that can be iterated over
1618
- """
1619
- return AgentStream(
1620
- agent=self,
1621
- messages=messages,
1622
- model=model,
1623
- max_steps=max_steps,
1624
- context=context,
1625
- output_type=output_type,
1626
- stream=True,
1627
- **kwargs,
1628
- )
1629
-
1630
- def as_a2a(
1631
- self,
1632
- *,
1633
- # Worker configuration
1634
- context: Optional[AgentContext] = None,
1635
- # Storage and broker configuration
1636
- storage: Optional[Any] = None,
1637
- broker: Optional[Any] = None,
1638
- # Server configuration
1639
- host: str = "0.0.0.0",
1640
- port: int = 8000,
1641
- reload: bool = False,
1642
- workers: int = 1,
1643
- log_level: str = "info",
1644
- # A2A configuration
1645
- name: Optional[str] = None,
1646
- url: Optional[str] = None,
1647
- version: str = "1.0.0",
1648
- description: Optional[str] = None,
1649
- # Advanced configuration
1650
- lifespan_timeout: int = 30,
1651
- **uvicorn_kwargs: Any,
1652
- ) -> "FastA2A": # type: ignore
1653
- """
1654
- Convert this agent to an A2A server application.
1655
-
1656
- This method creates a FastA2A server that can handle A2A requests
1657
- for this agent instance. It sets up the necessary Worker, Storage,
1658
- and Broker components automatically.
1659
-
1660
- Args:
1661
- context: Initial context for the agent
1662
- storage: Custom storage backend (defaults to InMemoryStorage)
1663
- broker: Custom broker backend (defaults to InMemoryBroker)
1664
- host: Host to bind the server to
1665
- port: Port to bind the server to
1666
- reload: Enable auto-reload for development
1667
- workers: Number of worker processes
1668
- log_level: Logging level
1669
- name: Agent name for the A2A server (defaults to agent's name)
1670
- url: URL where the agent is hosted
1671
- version: API version
1672
- description: API description for the A2A server (defaults to agent's description)
1673
- lifespan_timeout: Timeout for lifespan events
1674
- **uvicorn_kwargs: Additional arguments passed to uvicorn
1675
-
1676
- Returns:
1677
- FastA2A application instance that can be run with uvicorn
1678
-
1679
- Examples:
1680
- Convert agent to A2A server:
1681
- ```python
1682
- agent = Agent(
1683
- name="assistant",
1684
- instructions="You are a helpful assistant",
1685
- model="openai/gpt-4o-mini"
1686
- )
1687
-
1688
- app = agent.as_a2a(port=8080)
1689
-
1690
- # Run with uvicorn
1691
- import uvicorn
1692
- uvicorn.run(app, host="0.0.0.0", port=8080)
1693
- ```
1694
-
1695
- Or use the CLI:
1696
- ```bash
1697
- uvicorn mymodule:agent.as_a2a() --reload
1698
- ```
1699
-
1700
- With custom configuration:
1701
- ```python
1702
- app = agent.as_a2a(
1703
- name="My Assistant API",
1704
- description="A helpful AI assistant",
1705
- host="localhost",
1706
- port=3000
1707
- )
1708
- ```
1709
- """
1710
- from ..a2a import as_a2a_app
1711
-
1712
- return as_a2a_app(
1713
- self,
1714
- context=context,
1715
- storage=storage,
1716
- broker=broker,
1717
- host=host,
1718
- port=port,
1719
- reload=reload,
1720
- workers=workers,
1721
- log_level=log_level,
1722
- name=name or self.name,
1723
- url=url,
1724
- version=version,
1725
- description=description or self.description,
1726
- lifespan_timeout=lifespan_timeout,
1727
- **uvicorn_kwargs,
1728
- )
1729
-
1730
- def iter(
1731
- self,
1732
- messages: AgentMessages,
1733
- model: Optional[Union[LanguageModel, LanguageModelName]] = None,
1734
- max_steps: Optional[int] = None,
1735
- context: Optional[AgentContext] = None,
1736
- output_type: Optional[Type[T]] = None,
1737
- context_updates: Optional[
1738
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
1739
- ] = None,
1740
- context_confirm: Optional[bool] = None,
1741
- context_strategy: Optional[Literal["selective", "all"]] = None,
1742
- context_max_retries: Optional[int] = None,
1743
- context_confirm_instructions: Optional[str] = None,
1744
- context_selection_instructions: Optional[str] = None,
1745
- context_update_instructions: Optional[str] = None,
1746
- context_format: Optional[Literal["json", "python", "markdown"]] = None,
1747
- end_strategy: Optional[Literal["tool"]] = None,
1748
- end_tool: Optional[Callable] = None,
1749
- **kwargs: Any,
1750
- ) -> AgentStream[T, AgentContext]:
1751
- """Iterate over agent steps, yielding each step response.
1752
-
1753
- You can override defaults assigned to this agent from this function directly.
1754
- Returns an AgentStream that yields intermediate steps and the final result.
1755
-
1756
- Args:
1757
- messages: The messages to process. Can be:
1758
- - A single string: "What's the weather like?"
1759
- - A list of message dicts: [{"role": "user", "content": "Hello"}]
1760
- - A list of strings: ["Hello", "How are you?"]
1761
- model: The model to use for this run (overrides default).
1762
- - Can be a LanguageModel instance or model name string like "gpt-4"
1763
- max_steps: Maximum number of steps to execute (overrides default).
1764
- - Useful for limiting tool usage or preventing infinite loops
1765
- context: Context object for the agent (overrides default).
1766
- - Any object that provides additional context for the conversation
1767
- output_type: The expected output type (overrides default).
1768
- - Use for structured outputs: output_type=MyPydanticModel
1769
- - Defaults to str for unstructured text responses
1770
- **kwargs: Additional keyword arguments passed to the language model.
1771
- - Examples: temperature=0.7, top_p=0.9, presence_penalty=0.1
1772
-
1773
- Returns:
1774
- AgentStream that can be iterated over to get each step response,
1775
- including tool calls and intermediate reasoning steps.
1776
-
1777
- Examples:
1778
- Basic iteration over steps:
1779
- >>> agent = Agent(tools=[calculator_tool])
1780
- >>> stream = agent.iter("What's 25 * 47?")
1781
- >>> for step in stream:
1782
- ... print(f"Step {step.step_number}: {step.output}")
1783
- ... if step.tool_calls:
1784
- ... print(f"Tool calls: {len(step.tool_calls)}")
1785
-
1786
- Real-time processing with streaming:
1787
- >>> stream = agent.iter("Write a poem about nature")
1788
- >>> for chunk in stream:
1789
- ... if chunk.output:
1790
- ... print(chunk.output, end="", flush=True)
1791
- ... if chunk.is_final:
1792
- ... print("\n--- Final response ---")
1793
-
1794
- With structured output iteration:
1795
- >>> from pydantic import BaseModel
1796
- >>> class StepAnalysis(BaseModel):
1797
- ... reasoning: str
1798
- ... confidence: float
1799
- >>> stream = agent.iter(
1800
- ... "Analyze this step by step: Why is the sky blue?",
1801
- ... output_type=StepAnalysis
1802
- ... )
1803
- >>> for step in stream:
1804
- ... if step.output:
1805
- ... print(f"Reasoning: {step.output.reasoning}")
1806
- ... print(f"Confidence: {step.output.confidence}")
1807
-
1808
- Processing with custom model and context:
1809
- >>> context = {"domain": "science", "depth": "detailed"}
1810
- >>> stream = agent.iter(
1811
- ... "Explain quantum entanglement",
1812
- ... model="gpt-4",
1813
- ... context=context,
1814
- ... max_steps=3,
1815
- ... temperature=0.1
1816
- ... )
1817
- >>> results = []
1818
- >>> for step in stream:
1819
- ... results.append(step.output)
1820
- ... if step.is_final:
1821
- ... break
1822
-
1823
- Error handling during iteration:
1824
- >>> try:
1825
- ... stream = agent.iter("Complex calculation task")
1826
- ... for step in stream:
1827
- ... if step.error:
1828
- ... print(f"Error in step: {step.error}")
1829
- ... else:
1830
- ... print(f"Step result: {step.output}")
1831
- ... except Exception as e:
1832
- ... print(f"Stream error: {e}")
1833
- """
1834
- return AgentStream(
1835
- agent=self,
1836
- messages=messages,
1837
- model=model,
1838
- max_steps=max_steps,
1839
- context=context,
1840
- output_type=output_type,
1841
- stream=True,
1842
- end_strategy=end_strategy,
1843
- end_tool=end_tool,
1844
- **kwargs,
1845
- )
1846
-
1847
- def async_iter(
1848
- self,
1849
- messages: AgentMessages,
1850
- model: Optional[Union[LanguageModel, LanguageModelName]] = None,
1851
- max_steps: Optional[int] = None,
1852
- context: Optional[AgentContext] = None,
1853
- output_type: Optional[Type[T]] = None,
1854
- **kwargs: Any,
1855
- ) -> AgentStream[T, AgentContext]:
1856
- """Async iterate over agent steps, yielding each step response.
1857
-
1858
- Args:
1859
- messages: The input messages to process
1860
- model: Language model to use (overrides agent's default)
1861
- max_steps: Maximum number of steps to take
1862
- context: Context object to maintain state
1863
- output_type: Type for structured output
1864
- **kwargs: Additional parameters for the language model
1865
-
1866
- Returns:
1867
- An AgentStream that can be iterated over asynchronously
1868
- """
1869
- return AgentStream(
1870
- agent=self,
1871
- messages=messages,
1872
- model=model,
1873
- max_steps=max_steps,
1874
- context=context,
1875
- output_type=output_type,
1876
- stream=True,
1877
- **kwargs,
1878
- )
1879
-
1880
-
1881
- def create_agent(
1882
- name: str = "agent",
1883
- instructions: Optional[str] = None,
1884
- model: Union[LanguageModel, LanguageModelName] = "openai/gpt-4o-mini",
1885
- description: Optional[str] = None,
1886
- tools: Union[List[Tool], Callable, None] = None,
1887
- settings: Optional[AgentSettings] = None,
1888
- instructor_mode: Optional[LanguageModelInstructorMode] = None,
1889
- # Context management parameters
1890
- context_updates: Optional[
1891
- Union[List[Literal["before", "after"]], Literal["before", "after"]]
1892
- ] = None,
1893
- context_confirm: bool = False,
1894
- context_strategy: Literal["selective", "all"] = "all",
1895
- context_max_retries: int = 3,
1896
- context_confirm_instructions: Optional[str] = None,
1897
- context_selection_instructions: Optional[str] = None,
1898
- context_update_instructions: Optional[str] = None,
1899
- context_format: Literal["json", "python", "markdown"] = "json",
1900
- verbose: bool = False,
1901
- debug: bool = False,
1902
- **kwargs: Any,
1903
- ) -> Agent[T]:
1904
- """Create a new AI agent with specified capabilities and behavior.
1905
-
1906
- An agent is an intelligent assistant that can use tools, follow instructions,
1907
- and maintain context across conversations. It combines a language model with
1908
- additional capabilities like tool execution and structured output generation.
1909
-
1910
- Args:
1911
- name: A human-readable name for the agent (default: "agent")
1912
- instructions: System instructions that define the agent's behavior and personality
1913
- model: The language model to use - either a LanguageModel instance or model name string
1914
- description: Optional description of what the agent does
1915
- tools: List of tools/functions the agent can call, or a single callable
1916
- settings: AgentSettings object to customize default behavior
1917
- instructor_mode: Mode for structured output generation
1918
- context_updates: When to update context - "before", "after", or both
1919
- context_confirm: Whether to confirm context updates with the user
1920
- context_strategy: How to select context updates - "selective" or "all"
1921
- context_max_retries: Maximum attempts for context update operations
1922
- context_confirm_instructions: Custom instructions for context confirmation
1923
- context_selection_instructions: Custom instructions for context selection
1924
- context_update_instructions: Custom instructions for context updates
1925
- context_format: Format for context display - "json", "python", or "markdown"
1926
- verbose: If True, set logger to INFO level for detailed output
1927
- debug: If True, set logger to DEBUG level for maximum verbosity
1928
- **kwargs: Additional parameters passed to the underlying language model
1929
-
1930
- Example:
1931
- Basic agent:
1932
- >>> agent = create_agent(name="assistant", instructions="You are helpful")
1933
-
1934
- Agent with tools:
1935
- >>> def calculator(x: int, y: int) -> int:
1936
- ... return x + y
1937
- >>> agent = create_agent(tools=[calculator])
1938
-
1939
- Agent with custom settings:
1940
- >>> settings = AgentSettings(max_steps=5)
1941
- >>> agent = create_agent(settings=settings, model="gpt-4")
1942
- """
1943
- return Agent(
1944
- name=name,
1945
- instructions=instructions,
1946
- model=model,
1947
- description=description,
1948
- tools=tools,
1949
- settings=settings,
1950
- instructor_mode=instructor_mode,
1951
- context_updates=context_updates,
1952
- context_confirm=context_confirm,
1953
- context_strategy=context_strategy,
1954
- context_max_retries=context_max_retries,
1955
- context_confirm_instructions=context_confirm_instructions,
1956
- context_selection_instructions=context_selection_instructions,
1957
- context_update_instructions=context_update_instructions,
1958
- context_format=context_format,
1959
- verbose=verbose,
1960
- debug=debug,
1961
- **kwargs,
1962
- )
1963
-
1964
-
1965
- __all__ = [
1966
- "Agent",
1967
- "AgentSettings",
1968
- "AgentModelSettings",
1969
- "AgentEvent",
1970
- "HookManager",
1971
- "HookDecorator",
1972
- "create_agent",
1973
- ]