hammad-python 0.0.19__py3-none-any.whl → 0.0.21__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 (83) hide show
  1. hammad/__init__.py +7 -137
  2. hammad/_internal.py +1 -0
  3. hammad/cli/_runner.py +8 -8
  4. hammad/cli/plugins.py +55 -26
  5. hammad/cli/styles/utils.py +16 -8
  6. hammad/data/__init__.py +1 -5
  7. hammad/data/collections/__init__.py +2 -3
  8. hammad/data/collections/collection.py +41 -22
  9. hammad/data/collections/indexes/__init__.py +1 -1
  10. hammad/data/collections/indexes/qdrant/__init__.py +1 -1
  11. hammad/data/collections/indexes/qdrant/index.py +106 -118
  12. hammad/data/collections/indexes/qdrant/settings.py +14 -14
  13. hammad/data/collections/indexes/qdrant/utils.py +28 -38
  14. hammad/data/collections/indexes/tantivy/__init__.py +1 -1
  15. hammad/data/collections/indexes/tantivy/index.py +57 -59
  16. hammad/data/collections/indexes/tantivy/settings.py +8 -19
  17. hammad/data/collections/indexes/tantivy/utils.py +28 -52
  18. hammad/data/models/__init__.py +2 -7
  19. hammad/data/sql/__init__.py +1 -1
  20. hammad/data/sql/database.py +71 -73
  21. hammad/data/sql/types.py +37 -51
  22. hammad/formatting/__init__.py +2 -1
  23. hammad/formatting/json/converters.py +2 -2
  24. hammad/genai/__init__.py +96 -36
  25. hammad/genai/agents/__init__.py +47 -1
  26. hammad/genai/agents/agent.py +1298 -0
  27. hammad/genai/agents/run.py +615 -0
  28. hammad/genai/agents/types/__init__.py +29 -22
  29. hammad/genai/agents/types/agent_context.py +13 -0
  30. hammad/genai/agents/types/agent_event.py +128 -0
  31. hammad/genai/agents/types/agent_hooks.py +220 -0
  32. hammad/genai/agents/types/agent_messages.py +31 -0
  33. hammad/genai/agents/types/agent_response.py +122 -0
  34. hammad/genai/agents/types/agent_stream.py +318 -0
  35. hammad/genai/models/__init__.py +1 -0
  36. hammad/genai/models/embeddings/__init__.py +39 -0
  37. hammad/genai/{embedding_models/embedding_model.py → models/embeddings/model.py} +45 -41
  38. hammad/genai/{embedding_models → models/embeddings}/run.py +10 -8
  39. hammad/genai/models/embeddings/types/__init__.py +37 -0
  40. hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_name.py +2 -4
  41. hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_response.py +11 -4
  42. hammad/genai/{embedding_models/embedding_model_request.py → models/embeddings/types/embedding_model_run_params.py} +4 -3
  43. hammad/genai/models/embeddings/types/embedding_model_settings.py +47 -0
  44. hammad/genai/models/language/__init__.py +48 -0
  45. hammad/genai/{language_models/language_model.py → models/language/model.py} +496 -204
  46. hammad/genai/{language_models → models/language}/run.py +80 -57
  47. hammad/genai/models/language/types/__init__.py +40 -0
  48. hammad/genai/models/language/types/language_model_instructor_mode.py +47 -0
  49. hammad/genai/models/language/types/language_model_messages.py +28 -0
  50. hammad/genai/{language_models/_types.py → models/language/types/language_model_name.py} +3 -40
  51. hammad/genai/{language_models → models/language/types}/language_model_request.py +17 -25
  52. hammad/genai/{language_models → models/language/types}/language_model_response.py +60 -67
  53. hammad/genai/{language_models → models/language/types}/language_model_response_chunk.py +8 -5
  54. hammad/genai/models/language/types/language_model_settings.py +89 -0
  55. hammad/genai/{language_models/_streaming.py → models/language/types/language_model_stream.py} +221 -243
  56. hammad/genai/{language_models/_utils → models/language/utils}/__init__.py +8 -11
  57. hammad/genai/models/language/utils/requests.py +421 -0
  58. hammad/genai/{language_models/_utils/_structured_outputs.py → models/language/utils/structured_outputs.py} +31 -20
  59. hammad/genai/models/model_provider.py +4 -0
  60. hammad/genai/{multimodal_models.py → models/multimodal.py} +4 -5
  61. hammad/genai/models/reranking.py +26 -0
  62. hammad/genai/types/__init__.py +1 -0
  63. hammad/genai/types/base.py +215 -0
  64. hammad/genai/{agents/types → types}/history.py +101 -88
  65. hammad/genai/{agents/types/tool.py → types/tools.py} +157 -140
  66. hammad/logging/logger.py +9 -1
  67. hammad/mcp/client/__init__.py +2 -3
  68. hammad/mcp/client/client.py +10 -10
  69. hammad/mcp/servers/__init__.py +2 -1
  70. hammad/service/decorators.py +1 -3
  71. hammad/web/models.py +1 -3
  72. hammad/web/search/client.py +10 -22
  73. {hammad_python-0.0.19.dist-info → hammad_python-0.0.21.dist-info}/METADATA +10 -2
  74. hammad_python-0.0.21.dist-info/RECORD +127 -0
  75. hammad/genai/embedding_models/__init__.py +0 -41
  76. hammad/genai/language_models/__init__.py +0 -35
  77. hammad/genai/language_models/_utils/_completions.py +0 -131
  78. hammad/genai/language_models/_utils/_messages.py +0 -89
  79. hammad/genai/language_models/_utils/_requests.py +0 -202
  80. hammad/genai/rerank_models.py +0 -26
  81. hammad_python-0.0.19.dist-info/RECORD +0 -111
  82. {hammad_python-0.0.19.dist-info → hammad_python-0.0.21.dist-info}/WHEEL +0 -0
  83. {hammad_python-0.0.19.dist-info → hammad_python-0.0.21.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1298 @@
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
+ overload,
15
+ TYPE_CHECKING,
16
+ )
17
+ from pydantic import BaseModel, Field, create_model
18
+ from dataclasses import dataclass, field
19
+ from enum import Enum
20
+ import json
21
+
22
+ from ...logging.logger import _get_internal_logger
23
+
24
+ from ..types.base import BaseGenAIModel, BaseGenAIModelSettings
25
+ from ..models.language.model import LanguageModel
26
+ from ..models.language.types import (
27
+ LanguageModelResponse,
28
+ LanguageModelName,
29
+ LanguageModelInstructorMode,
30
+ )
31
+ from ..types.tools import (
32
+ Tool,
33
+ define_tool,
34
+ execute_tools_from_language_model_response,
35
+ )
36
+ from ..models.language.utils.requests import (
37
+ parse_messages_input as parse_messages,
38
+ consolidate_system_messages,
39
+ )
40
+ from ...formatting.text.converters import convert_to_text
41
+ from .types.agent_response import (
42
+ AgentResponse,
43
+ _create_agent_response_from_language_model_response,
44
+ )
45
+ from .types.agent_stream import AgentStream
46
+ from .types.agent_context import AgentContext
47
+ from .types.agent_event import AgentEvent
48
+ from .types.agent_hooks import HookManager, HookDecorator
49
+ from .types.agent_messages import AgentMessages
50
+
51
+ if TYPE_CHECKING:
52
+ pass
53
+
54
+
55
+ T = TypeVar("T")
56
+
57
+
58
+ logger = _get_internal_logger(__name__)
59
+
60
+
61
+ @dataclass
62
+ class AgentSettings:
63
+ """Settings object that controls the default behavior of an agent's run."""
64
+
65
+ max_steps: int = field(default=10)
66
+ """The maximum amount of steps the agent can take before stopping."""
67
+
68
+ add_name_to_instructions: bool = field(default=True)
69
+ """Whether to add the agent name to the instructions."""
70
+
71
+ context_format: Literal["json", "python", "markdown"] = field(default="json")
72
+ """Format for context in instructions."""
73
+
74
+ # Context management settings
75
+ context_updates: Optional[
76
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
77
+ ] = field(default=None)
78
+ """When to update context ('before', 'after', or both)."""
79
+
80
+ context_confirm: bool = field(default=False)
81
+ """Whether to confirm context updates."""
82
+
83
+ context_strategy: Literal["selective", "all"] = field(default="all")
84
+ """Strategy for context updates."""
85
+
86
+ context_max_retries: int = field(default=3)
87
+ """Maximum retries for context updates."""
88
+
89
+ context_confirm_instructions: Optional[str] = field(default=None)
90
+ """Custom instructions for context confirmation."""
91
+
92
+ context_selection_instructions: Optional[str] = field(default=None)
93
+ """Custom instructions for context selection."""
94
+
95
+ context_update_instructions: Optional[str] = field(default=None)
96
+ """Custom instructions for context updates."""
97
+
98
+
99
+ class AgentModelSettings(BaseGenAIModelSettings):
100
+ """Agent-specific model settings that extend the base model settings."""
101
+
102
+ instructor_mode: Optional[LanguageModelInstructorMode] = None
103
+ """Instructor mode for structured outputs."""
104
+
105
+ max_steps: int = 10
106
+ """Maximum number of steps the agent can take."""
107
+
108
+ add_name_to_instructions: bool = True
109
+ """Whether to add the agent name to the instructions."""
110
+
111
+ context_format: Literal["json", "python", "markdown"] = "json"
112
+ """Format for context in instructions."""
113
+
114
+ # Context management settings
115
+ context_updates: Optional[
116
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
117
+ ] = None
118
+ """When to update context ('before', 'after', or both)."""
119
+
120
+ context_confirm: bool = False
121
+ """Whether to confirm context updates."""
122
+
123
+ context_strategy: Literal["selective", "all"] = "all"
124
+ """Strategy for context updates."""
125
+
126
+ context_max_retries: int = 3
127
+ """Maximum retries for context updates."""
128
+
129
+ context_confirm_instructions: Optional[str] = None
130
+ """Custom instructions for context confirmation."""
131
+
132
+ context_selection_instructions: Optional[str] = None
133
+ """Custom instructions for context selection."""
134
+
135
+ context_update_instructions: Optional[str] = None
136
+ """Custom instructions for context updates."""
137
+
138
+
139
+ def _build_tools(tools: List[Tool] | Callable | None) -> List[Tool]:
140
+ """Builds a list of tools from a list of tools or a callable that returns a list of tools."""
141
+ if tools is None:
142
+ return []
143
+ if callable(tools):
144
+ return [define_tool(tools)]
145
+
146
+ processed_tools = []
147
+ for tool in tools:
148
+ if not isinstance(tool, Tool):
149
+ tool = define_tool(tool)
150
+ processed_tools.append(tool)
151
+
152
+ return processed_tools
153
+
154
+
155
+ def _get_instructions(
156
+ name: str,
157
+ instructions: Optional[str],
158
+ add_name_to_instructions: bool,
159
+ ) -> Optional[str]:
160
+ """Gets the instructions for an agent."""
161
+ if add_name_to_instructions and name:
162
+ base_instructions = instructions or ""
163
+ return f"You are {name}.\n\n{base_instructions}".strip()
164
+ return instructions
165
+
166
+
167
+ def _format_context_for_instructions(
168
+ context: AgentContext | None,
169
+ context_format: Literal["json", "python", "markdown"] = "json",
170
+ ) -> str:
171
+ """Format context object for inclusion in instructions."""
172
+ if context is None:
173
+ return ""
174
+
175
+ if context_format == "json":
176
+ if isinstance(context, BaseModel):
177
+ return context.model_dump_json(indent=2)
178
+ elif isinstance(context, dict):
179
+ return json.dumps(context, indent=2)
180
+ else:
181
+ return json.dumps(str(context), indent=2)
182
+
183
+ elif context_format == "python":
184
+ if hasattr(context, "__repr__"):
185
+ return repr(context)
186
+ elif hasattr(context, "__str__"):
187
+ return str(context)
188
+ else:
189
+ return str(context)
190
+
191
+ elif context_format == "markdown":
192
+ return convert_to_text(context)
193
+
194
+ return str(context)
195
+
196
+
197
+ def _update_context_object(
198
+ context: AgentContext, updates: Dict[str, Any]
199
+ ) -> AgentContext:
200
+ """Update a context object with new values."""
201
+ if isinstance(context, BaseModel):
202
+ # For Pydantic models, create a copy with updated values
203
+ return context.model_copy(update=updates)
204
+ elif isinstance(context, dict):
205
+ # For dictionaries, update in place
206
+ updated_context = context.copy()
207
+ updated_context.update(updates)
208
+ return updated_context
209
+ else:
210
+ raise ValueError(f"Cannot update context of type {type(context)}")
211
+
212
+
213
+ class Agent(BaseGenAIModel, Generic[T]):
214
+ """A generative AI agent that can execute tools, generate structured outputs,
215
+ and maintain context across multiple conversation steps.
216
+ """
217
+
218
+ model: LanguageModelName = "openai/gpt-4o-mini"
219
+ """The language model to use for the agent."""
220
+
221
+ name: str = "agent"
222
+ """The name of the agent."""
223
+
224
+ description: Optional[str] = None
225
+ """A description of the agent."""
226
+
227
+ instructions: Optional[str] = None
228
+ """System instructions for the agent."""
229
+
230
+ tools: List[Tool] = Field(default_factory=list)
231
+ """List of tools available to the agent."""
232
+
233
+ settings: AgentSettings = Field(default_factory=AgentSettings)
234
+ """Agent-specific settings."""
235
+
236
+ instructor_mode: Optional[LanguageModelInstructorMode] = None
237
+ """Instructor mode for structured outputs."""
238
+
239
+ def __init__(
240
+ self,
241
+ name: str = "agent",
242
+ instructions: Optional[str] = None,
243
+ model: Union[LanguageModel, LanguageModelName] = "openai/gpt-4o-mini",
244
+ description: Optional[str] = None,
245
+ tools: Union[List[Tool], Callable, None] = None,
246
+ settings: Optional[AgentSettings] = None,
247
+ instructor_mode: Optional[LanguageModelInstructorMode] = None,
248
+ # Context management parameters
249
+ context_updates: Optional[
250
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
251
+ ] = None,
252
+ context_confirm: bool = False,
253
+ context_strategy: Literal["selective", "all"] = "all",
254
+ context_max_retries: int = 3,
255
+ context_confirm_instructions: Optional[str] = None,
256
+ context_selection_instructions: Optional[str] = None,
257
+ context_update_instructions: Optional[str] = None,
258
+ context_format: Literal["json", "python", "markdown"] = "json",
259
+ **kwargs: Any,
260
+ ):
261
+ """Create a new AI agent with specified capabilities and behavior.
262
+
263
+ An agent is an intelligent assistant that can use tools, follow instructions,
264
+ and maintain context across conversations. It combines a language model with
265
+ additional capabilities like tool execution and structured output generation.
266
+
267
+ Args:
268
+ name: A human-readable name for the agent (default: "agent")
269
+ instructions: System instructions that define the agent's behavior and personality
270
+ model: The language model to use - either a LanguageModel instance or model name string
271
+ description: Optional description of what the agent does
272
+ tools: List of tools/functions the agent can call, or a single callable
273
+ settings: AgentSettings object to customize default behavior
274
+ instructor_mode: Mode for structured output generation
275
+ context_updates: When to update context - "before", "after", or both
276
+ context_confirm: Whether to confirm context updates with the user
277
+ context_strategy: How to select context updates - "selective" or "all"
278
+ context_max_retries: Maximum attempts for context update operations
279
+ context_confirm_instructions: Custom instructions for context confirmation
280
+ context_selection_instructions: Custom instructions for context selection
281
+ context_update_instructions: Custom instructions for context updates
282
+ context_format: Format for context display - "json", "python", or "markdown"
283
+ **kwargs: Additional parameters passed to the underlying language model
284
+
285
+ Example:
286
+ Basic agent:
287
+ >>> agent = Agent(name="assistant", instructions="You are helpful")
288
+
289
+ Agent with tools:
290
+ >>> def calculator(x: int, y: int) -> int:
291
+ ... return x + y
292
+ >>> agent = Agent(tools=[calculator])
293
+
294
+ Agent with custom settings:
295
+ >>> settings = AgentSettings(max_steps=5)
296
+ >>> agent = Agent(settings=settings, model="gpt-4")
297
+ """
298
+ # Initialize BaseGenAIModel with basic parameters
299
+ super().__init__(
300
+ model=model if isinstance(model, str) else model.model, **kwargs
301
+ )
302
+
303
+ # Agent-specific initialization
304
+ self.name = name
305
+ self.description = description
306
+ self.tools = _build_tools(tools)
307
+ self.settings = settings or AgentSettings()
308
+ self.instructor_mode = instructor_mode
309
+
310
+ # Process instructions
311
+ self.instructions = _get_instructions(
312
+ name=name,
313
+ instructions=instructions,
314
+ add_name_to_instructions=self.settings.add_name_to_instructions,
315
+ )
316
+
317
+ # Initialize the language model
318
+ if isinstance(model, LanguageModel):
319
+ self._language_model = model
320
+ else:
321
+ self._language_model = LanguageModel(model=model, **kwargs)
322
+
323
+ # Context management settings
324
+ self.context_updates = context_updates
325
+ self.context_confirm = context_confirm
326
+ self.context_strategy = context_strategy
327
+ self.context_max_retries = context_max_retries
328
+ self.context_confirm_instructions = context_confirm_instructions
329
+ self.context_selection_instructions = context_selection_instructions
330
+ self.context_update_instructions = context_update_instructions
331
+ self.context_format = context_format
332
+
333
+ # Hook system
334
+ self.hook_manager = HookManager()
335
+ self.on = HookDecorator(self.hook_manager)
336
+
337
+ @property
338
+ def language_model(self) -> LanguageModel:
339
+ """Get the underlying language model."""
340
+ return self._language_model
341
+
342
+ def _get_effective_context_settings(
343
+ self,
344
+ context_updates: Optional[
345
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
346
+ ] = None,
347
+ context_confirm: Optional[bool] = None,
348
+ context_strategy: Optional[Literal["selective", "all"]] = None,
349
+ context_max_retries: Optional[int] = None,
350
+ context_confirm_instructions: Optional[str] = None,
351
+ context_selection_instructions: Optional[str] = None,
352
+ context_update_instructions: Optional[str] = None,
353
+ context_format: Optional[Literal["json", "python", "markdown"]] = None,
354
+ ) -> dict:
355
+ """Get effective context settings, using provided parameters or defaults."""
356
+ return {
357
+ "context_updates": context_updates if context_updates is not None else self.context_updates,
358
+ "context_confirm": context_confirm if context_confirm is not None else self.context_confirm,
359
+ "context_strategy": context_strategy if context_strategy is not None else self.context_strategy,
360
+ "context_max_retries": context_max_retries if context_max_retries is not None else self.context_max_retries,
361
+ "context_confirm_instructions": context_confirm_instructions if context_confirm_instructions is not None else self.context_confirm_instructions,
362
+ "context_selection_instructions": context_selection_instructions if context_selection_instructions is not None else self.context_selection_instructions,
363
+ "context_update_instructions": context_update_instructions if context_update_instructions is not None else self.context_update_instructions,
364
+ "context_format": context_format if context_format is not None else self.context_format,
365
+ }
366
+
367
+ def _should_update_context(
368
+ self, context: AgentContext, timing: Literal["before", "after"], context_updates=None
369
+ ) -> bool:
370
+ """Determine if context should be updated based on timing and configuration."""
371
+ effective_context_updates = context_updates if context_updates is not None else self.context_updates
372
+
373
+ if not effective_context_updates:
374
+ return False
375
+
376
+ if isinstance(effective_context_updates, str):
377
+ return effective_context_updates == timing
378
+ else:
379
+ return timing in effective_context_updates
380
+
381
+ def _create_context_confirm_model(self):
382
+ """Create IsUpdateRequired model for context confirmation."""
383
+ return create_model("IsUpdateRequired", decision=(bool, ...))
384
+
385
+ def _create_context_selection_model(self, context: AgentContext):
386
+ """Create FieldsToUpdate model for selective context updates."""
387
+ if isinstance(context, BaseModel):
388
+ field_names = list(context.model_fields.keys())
389
+ elif isinstance(context, dict):
390
+ field_names = list(context.keys())
391
+ else:
392
+ raise ValueError(
393
+ f"Cannot create selection model for context type {type(context)}"
394
+ )
395
+
396
+ FieldEnum = Enum("FieldEnum", {name: name for name in field_names})
397
+ return create_model("FieldsToUpdate", fields=(List[FieldEnum], ...))
398
+
399
+ def _create_context_update_model(
400
+ self, context: AgentContext, field_name: str = None
401
+ ):
402
+ """Create update model for context updates."""
403
+ if field_name:
404
+ # Single field update
405
+ if isinstance(context, BaseModel):
406
+ field_type = context.__class__.model_fields[field_name].annotation
407
+ field_info = context.__class__.model_fields[field_name]
408
+ description = getattr(field_info, 'description', f"Update the {field_name} field")
409
+ elif isinstance(context, dict):
410
+ field_type = type(context[field_name])
411
+ description = f"Update the {field_name} field"
412
+ else:
413
+ field_type = Any
414
+ description = f"Update the {field_name} field"
415
+
416
+ return create_model(
417
+ f"Update{field_name.capitalize()}",
418
+ **{field_name: (field_type, Field(description=description))}
419
+ )
420
+ else:
421
+ # All fields update - create a model with the exact same fields as the context
422
+ if isinstance(context, BaseModel):
423
+ # Create a model with the same fields as the context
424
+ field_definitions = {}
425
+ for field_name, field_info in context.model_fields.items():
426
+ field_type = field_info.annotation
427
+ current_value = getattr(context, field_name)
428
+ description = getattr(field_info, 'description', f"Current value: {current_value}")
429
+ field_definitions[field_name] = (field_type, Field(description=description))
430
+
431
+ return create_model("ContextUpdate", **field_definitions)
432
+ elif isinstance(context, dict):
433
+ # Create a model with the same keys as the dict
434
+ field_definitions = {}
435
+ for key, value in context.items():
436
+ field_type = type(value)
437
+ description = f"Current value: {value}"
438
+ field_definitions[key] = (field_type, Field(description=description))
439
+
440
+ return create_model("ContextUpdate", **field_definitions)
441
+ else:
442
+ # Fallback to generic updates
443
+ return create_model("ContextUpdate", updates=(Dict[str, Any], Field(description="Dictionary of field updates")))
444
+
445
+ def _perform_context_update(
446
+ self,
447
+ context: AgentContext,
448
+ model: LanguageModel,
449
+ current_messages: List[Dict[str, Any]],
450
+ timing: Literal["before", "after"],
451
+ effective_settings: Optional[dict] = None,
452
+ ) -> AgentContext:
453
+ """Perform context update with retries and error handling."""
454
+ updated_context = context
455
+
456
+ # Use effective settings or defaults
457
+ if effective_settings is None:
458
+ effective_settings = {
459
+ "context_confirm": self.context_confirm,
460
+ "context_strategy": self.context_strategy,
461
+ "context_max_retries": self.context_max_retries,
462
+ "context_confirm_instructions": self.context_confirm_instructions,
463
+ "context_selection_instructions": self.context_selection_instructions,
464
+ "context_update_instructions": self.context_update_instructions,
465
+ "context_format": self.context_format,
466
+ }
467
+
468
+ for attempt in range(effective_settings["context_max_retries"]):
469
+ try:
470
+ # Check if update is needed (if confirmation is enabled)
471
+ if effective_settings["context_confirm"]:
472
+ confirm_model = self._create_context_confirm_model()
473
+
474
+ # Create detailed instructions with context structure
475
+ context_structure = _format_context_for_instructions(updated_context, effective_settings["context_format"])
476
+ confirm_instructions = f"""Based on the conversation, determine if the context should be updated {timing} processing.
477
+
478
+ Current context structure:
479
+ {context_structure}
480
+
481
+ Should the context be updated based on the new information provided in the conversation?"""
482
+
483
+ if effective_settings["context_confirm_instructions"]:
484
+ confirm_instructions += f"\n\nAdditional instructions: {effective_settings['context_confirm_instructions']}"
485
+
486
+ confirm_response = model.run(
487
+ messages=current_messages
488
+ + [{"role": "user", "content": confirm_instructions}],
489
+ type=confirm_model,
490
+ instructor_mode=self.instructor_mode,
491
+ )
492
+
493
+ if not confirm_response.output.decision:
494
+ return updated_context
495
+
496
+ # Perform the update based on strategy
497
+ if effective_settings["context_strategy"] == "selective":
498
+ # Get fields to update
499
+ selection_model = self._create_context_selection_model(
500
+ updated_context
501
+ )
502
+
503
+ # Create detailed instructions with context structure
504
+ context_structure = _format_context_for_instructions(updated_context, effective_settings["context_format"])
505
+ selection_instructions = f"""Select which fields in the context should be updated {timing} processing based on the conversation.
506
+
507
+ Current context structure:
508
+ {context_structure}
509
+
510
+ Choose only the fields that need to be updated based on the new information provided in the conversation."""
511
+
512
+ if effective_settings["context_selection_instructions"]:
513
+ selection_instructions += f"\n\nAdditional instructions: {effective_settings['context_selection_instructions']}"
514
+
515
+ selection_response = model.run(
516
+ messages=current_messages
517
+ + [{"role": "user", "content": selection_instructions}],
518
+ type=selection_model,
519
+ instructor_mode=self.instructor_mode,
520
+ )
521
+
522
+ # Update each selected field
523
+ for field_enum in selection_response.output.fields:
524
+ field_name = field_enum.value
525
+ field_model = self._create_context_update_model(
526
+ updated_context, field_name
527
+ )
528
+ # Get current field value for context
529
+ current_value = getattr(updated_context, field_name) if isinstance(updated_context, BaseModel) else updated_context.get(field_name)
530
+
531
+ field_instructions = f"""Update the {field_name} field in the context based on the conversation.
532
+
533
+ Current value of {field_name}: {current_value}
534
+
535
+ Please provide the new value for {field_name} based on the information from the conversation."""
536
+
537
+ if effective_settings["context_update_instructions"]:
538
+ field_instructions += f"\n\nAdditional instructions: {effective_settings['context_update_instructions']}"
539
+
540
+ field_response = model.run(
541
+ messages=current_messages
542
+ + [{"role": "user", "content": field_instructions}],
543
+ type=field_model,
544
+ instructor_mode=self.instructor_mode,
545
+ )
546
+
547
+ # Apply the update
548
+ field_updates = {
549
+ field_name: getattr(field_response.output, field_name)
550
+ }
551
+ updated_context = _update_context_object(
552
+ updated_context, field_updates
553
+ )
554
+
555
+ else: # strategy == "all"
556
+ # Update all fields at once
557
+ update_model = self._create_context_update_model(updated_context)
558
+
559
+ # Create detailed instructions with context structure
560
+ context_structure = _format_context_for_instructions(updated_context, effective_settings["context_format"])
561
+ update_instructions = f"""Update the context {timing} processing based on the conversation.
562
+
563
+ Current context structure:
564
+ {context_structure}
565
+
566
+ Please update the appropriate fields based on the conversation. Only update fields that need to be changed based on the new information provided."""
567
+
568
+ if effective_settings["context_update_instructions"]:
569
+ update_instructions += f"\n\nAdditional instructions: {effective_settings['context_update_instructions']}"
570
+
571
+ update_response = model.run(
572
+ messages=current_messages
573
+ + [{"role": "user", "content": update_instructions}],
574
+ type=update_model,
575
+ instructor_mode=self.instructor_mode,
576
+ )
577
+
578
+ # Apply the updates
579
+ if hasattr(update_response.output, 'updates'):
580
+ # Legacy fallback for generic updates
581
+ updated_context = _update_context_object(
582
+ updated_context, update_response.output.updates
583
+ )
584
+ else:
585
+ # New approach - extract field values directly from the response
586
+ updates_dict = {}
587
+ for field_name in (context.model_fields.keys() if isinstance(context, BaseModel) else context.keys()):
588
+ if hasattr(update_response.output, field_name):
589
+ updates_dict[field_name] = getattr(update_response.output, field_name)
590
+ updated_context = _update_context_object(updated_context, updates_dict)
591
+
592
+ # Trigger context update hooks
593
+ self.hook_manager.trigger_hooks("context_update", updated_context)
594
+
595
+ return updated_context
596
+
597
+ except Exception as e:
598
+ if attempt == self.context_max_retries - 1:
599
+ # Last attempt failed, return original context
600
+ return updated_context
601
+ # Continue to next attempt
602
+ continue
603
+
604
+ return updated_context
605
+
606
+ def _format_messages_with_context(
607
+ self, messages: List[Dict[str, Any]], context: Optional[AgentContext] = None
608
+ ) -> List[Dict[str, Any]]:
609
+ """Format messages with instructions and context."""
610
+ formatted_messages = messages.copy()
611
+
612
+ if self.instructions:
613
+ system_content = self.instructions
614
+
615
+ # Add context if provided
616
+ if context is not None:
617
+ context_str = _format_context_for_instructions(
618
+ context, self.context_format
619
+ )
620
+ if context_str:
621
+ system_content += f"\n\nContext:\n{context_str}"
622
+
623
+ system_message = {"role": "system", "content": system_content}
624
+ formatted_messages = [system_message] + formatted_messages
625
+
626
+ return consolidate_system_messages(formatted_messages)
627
+
628
+ # Overloaded run methods for streaming support
629
+ @overload
630
+ def run(
631
+ self,
632
+ messages: AgentMessages,
633
+ model: Optional[Union[LanguageModel, LanguageModelName]] = None,
634
+ max_steps: Optional[int] = None,
635
+ context: Optional[AgentContext] = None,
636
+ output_type: Optional[Type[T]] = None,
637
+ context_updates: Optional[
638
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
639
+ ] = None,
640
+ context_confirm: Optional[bool] = None,
641
+ context_strategy: Optional[Literal["selective", "all"]] = None,
642
+ context_max_retries: Optional[int] = None,
643
+ context_confirm_instructions: Optional[str] = None,
644
+ context_selection_instructions: Optional[str] = None,
645
+ context_update_instructions: Optional[str] = None,
646
+ context_format: Optional[Literal["json", "python", "markdown"]] = None,
647
+ *,
648
+ stream: Literal[False] = False,
649
+ **kwargs: Any,
650
+ ) -> AgentResponse[T, AgentContext]: ...
651
+
652
+ @overload
653
+ def run(
654
+ self,
655
+ messages: AgentMessages,
656
+ model: Optional[Union[LanguageModel, LanguageModelName]] = None,
657
+ max_steps: Optional[int] = None,
658
+ context: Optional[AgentContext] = None,
659
+ output_type: Optional[Type[T]] = None,
660
+ context_updates: Optional[
661
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
662
+ ] = None,
663
+ context_confirm: Optional[bool] = None,
664
+ context_strategy: Optional[Literal["selective", "all"]] = None,
665
+ context_max_retries: Optional[int] = None,
666
+ context_confirm_instructions: Optional[str] = None,
667
+ context_selection_instructions: Optional[str] = None,
668
+ context_update_instructions: Optional[str] = None,
669
+ context_format: Optional[Literal["json", "python", "markdown"]] = None,
670
+ *,
671
+ stream: Literal[True],
672
+ **kwargs: Any,
673
+ ) -> AgentStream[T, AgentContext]: ...
674
+
675
+ def run(
676
+ self,
677
+ messages: AgentMessages,
678
+ model: Optional[Union[LanguageModel, LanguageModelName]] = None,
679
+ max_steps: Optional[int] = None,
680
+ context: Optional[AgentContext] = None,
681
+ output_type: Optional[Type[T]] = None,
682
+ context_updates: Optional[
683
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
684
+ ] = None,
685
+ context_confirm: Optional[bool] = None,
686
+ context_strategy: Optional[Literal["selective", "all"]] = None,
687
+ context_max_retries: Optional[int] = None,
688
+ context_confirm_instructions: Optional[str] = None,
689
+ context_selection_instructions: Optional[str] = None,
690
+ context_update_instructions: Optional[str] = None,
691
+ context_format: Optional[Literal["json", "python", "markdown"]] = None,
692
+ stream: bool = False,
693
+ **kwargs: Any,
694
+ ) -> Union[AgentResponse[T, AgentContext], AgentStream[T, AgentContext]]:
695
+ """Runs this agent and returns a final agent response or stream.
696
+
697
+ You can override defaults assigned to this agent from this function directly.
698
+
699
+ Args:
700
+ messages: The messages to process. Can be:
701
+ - A single string: "What's the weather like?"
702
+ - A list of message dicts: [{"role": "user", "content": "Hello"}]
703
+ - A list of strings: ["Hello", "How are you?"]
704
+ model: The model to use for this run (overrides default).
705
+ - Can be a LanguageModel instance or model name string like "gpt-4"
706
+ max_steps: Maximum number of steps to execute (overrides default).
707
+ - Useful for limiting tool usage or preventing infinite loops
708
+ context: Context object for the agent (overrides default).
709
+ - Any object that provides additional context for the conversation
710
+ output_type: The expected output type (overrides default).
711
+ - Use for structured outputs: output_type=MyPydanticModel
712
+ - Defaults to str for unstructured text responses
713
+ stream: Whether to return a stream instead of a final response.
714
+ - If True, returns AgentStream for real-time processing
715
+ - If False, returns complete AgentResponse
716
+ **kwargs: Additional keyword arguments passed to the language model.
717
+ - Examples: temperature=0.7, top_p=0.9, presence_penalty=0.1
718
+
719
+ Returns:
720
+ AgentResponse or AgentStream depending on stream parameter.
721
+ - AgentResponse: Contains final output, steps taken, and metadata
722
+ - AgentStream: Iterator yielding intermediate steps and final result
723
+
724
+ Examples:
725
+ Basic text conversation:
726
+ >>> agent = Agent()
727
+ >>> response = agent.run("Hello, how are you?")
728
+ >>> print(response.output)
729
+ "Hello! I'm doing well, thank you for asking."
730
+
731
+ With custom model and parameters:
732
+ >>> response = agent.run(
733
+ ... messages="Explain quantum computing",
734
+ ... model="gpt-4",
735
+ ... max_steps=5,
736
+ ... temperature=0.3
737
+ ... )
738
+
739
+ Structured output with Pydantic model:
740
+ >>> from pydantic import BaseModel
741
+ >>> class Summary(BaseModel):
742
+ ... title: str
743
+ ... key_points: List[str]
744
+ >>> response = agent.run(
745
+ ... "Summarize the benefits of renewable energy",
746
+ ... output_type=Summary
747
+ ... )
748
+ >>> print(response.output.title)
749
+ >>> print(response.output.key_points)
750
+
751
+ Streaming for real-time results:
752
+ >>> stream = agent.run(
753
+ ... "Write a long story about space exploration",
754
+ ... stream=True
755
+ ... )
756
+ >>> for chunk in stream:
757
+ ... print(chunk.output, end="", flush=True)
758
+
759
+ With context for additional information:
760
+ >>> context = {"user_preferences": "technical explanations"}
761
+ >>> response = agent.run(
762
+ ... "How does machine learning work?",
763
+ ... context=context
764
+ ... )
765
+ """
766
+ # Handle streaming
767
+ if stream:
768
+ return AgentStream(
769
+ agent=self,
770
+ messages=messages,
771
+ model=model,
772
+ max_steps=max_steps,
773
+ context=context,
774
+ output_type=output_type,
775
+ stream=stream,
776
+ **kwargs,
777
+ )
778
+
779
+ # Use provided model or default
780
+ if model is None:
781
+ working_model = self.language_model
782
+ elif isinstance(model, str):
783
+ working_model = LanguageModel(model=model)
784
+ else:
785
+ working_model = model
786
+
787
+ # Use provided max_steps or default
788
+ if max_steps is None:
789
+ max_steps = self.settings.max_steps
790
+
791
+ # Get effective context settings
792
+ effective_context_settings = self._get_effective_context_settings(
793
+ context_updates=context_updates,
794
+ context_confirm=context_confirm,
795
+ context_strategy=context_strategy,
796
+ context_max_retries=context_max_retries,
797
+ context_confirm_instructions=context_confirm_instructions,
798
+ context_selection_instructions=context_selection_instructions,
799
+ context_update_instructions=context_update_instructions,
800
+ context_format=context_format,
801
+ )
802
+
803
+ # Parse initial messages
804
+ parsed_messages = parse_messages(messages)
805
+ current_messages = parsed_messages.copy()
806
+ steps: List[LanguageModelResponse[str]] = []
807
+
808
+ # RUN MAIN AGENTIC LOOP
809
+ for step in range(max_steps):
810
+ # Update context before processing if configured
811
+ if context and self._should_update_context(context, "before", effective_context_settings["context_updates"]):
812
+ context = self._perform_context_update(
813
+ context=context,
814
+ model=working_model,
815
+ current_messages=current_messages,
816
+ timing="before",
817
+ effective_settings=effective_context_settings,
818
+ )
819
+
820
+ # Format messages with instructions and context for first step only
821
+ if step == 0:
822
+ formatted_messages = self._format_messages_with_context(
823
+ messages=current_messages,
824
+ context=context,
825
+ )
826
+ else:
827
+ formatted_messages = current_messages
828
+
829
+ # Prepare kwargs for language model
830
+ model_kwargs = kwargs.copy()
831
+ if output_type:
832
+ model_kwargs["type"] = output_type
833
+ if self.instructor_mode:
834
+ model_kwargs["instructor_mode"] = self.instructor_mode
835
+
836
+ # Get language model response
837
+ response = working_model.run(
838
+ messages=formatted_messages,
839
+ tools=[tool.to_dict() for tool in self.tools]
840
+ if self.tools
841
+ else None,
842
+ **model_kwargs,
843
+ )
844
+
845
+ # Check if response has tool calls
846
+ if response.has_tool_calls():
847
+ # Add response to message history (with tool calls)
848
+ current_messages.append(response.to_message())
849
+
850
+ # Execute tools and add their responses to messages
851
+ tool_responses = execute_tools_from_language_model_response(
852
+ tools=self.tools, response=response
853
+ )
854
+ # Add tool responses to message history
855
+ for tool_resp in tool_responses:
856
+ current_messages.append(tool_resp.to_dict())
857
+
858
+ # This is not the final step, add to steps
859
+ steps.append(response)
860
+ else:
861
+ # No tool calls - this is the final step
862
+ # Update context after processing if configured
863
+ if context and self._should_update_context(context, "after", effective_context_settings["context_updates"]):
864
+ context = self._perform_context_update(
865
+ context=context,
866
+ model=working_model,
867
+ current_messages=current_messages,
868
+ timing="after",
869
+ effective_settings=effective_context_settings,
870
+ )
871
+ return _create_agent_response_from_language_model_response(
872
+ response=response, steps=steps, context=context
873
+ )
874
+
875
+ # Max steps reached - return last response
876
+ if steps:
877
+ final_response = steps[-1]
878
+ else:
879
+ # No steps taken, make a final call
880
+ final_response = working_model.run(
881
+ messages=self._format_messages_with_context(
882
+ messages=current_messages,
883
+ context=context,
884
+ ),
885
+ **model_kwargs,
886
+ )
887
+
888
+ # Update context after processing if configured
889
+ if context and self._should_update_context(context, "after", effective_context_settings["context_updates"]):
890
+ context = self._perform_context_update(
891
+ context=context,
892
+ model=working_model,
893
+ current_messages=current_messages,
894
+ timing="after",
895
+ effective_settings=effective_context_settings,
896
+ )
897
+
898
+ return _create_agent_response_from_language_model_response(
899
+ response=final_response, steps=steps, context=context
900
+ )
901
+
902
+ async def async_run(
903
+ self,
904
+ messages: AgentMessages,
905
+ model: Optional[Union[LanguageModel, LanguageModelName]] = None,
906
+ max_steps: Optional[int] = None,
907
+ context: Optional[AgentContext] = None,
908
+ output_type: Optional[Type[T]] = None,
909
+ context_updates: Optional[
910
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
911
+ ] = None,
912
+ context_confirm: Optional[bool] = None,
913
+ context_strategy: Optional[Literal["selective", "all"]] = None,
914
+ context_max_retries: Optional[int] = None,
915
+ context_confirm_instructions: Optional[str] = None,
916
+ context_selection_instructions: Optional[str] = None,
917
+ context_update_instructions: Optional[str] = None,
918
+ context_format: Optional[Literal["json", "python", "markdown"]] = None,
919
+ **kwargs: Any,
920
+ ) -> AgentResponse[T, AgentContext]:
921
+ """Runs this agent asynchronously and returns a final agent response.
922
+
923
+ You can override defaults assigned to this agent from this function directly.
924
+ This is the async version of run() for non-blocking execution.
925
+
926
+ Args:
927
+ messages: The messages to process. Can be:
928
+ - A single string: "What's the weather like?"
929
+ - A list of message dicts: [{"role": "user", "content": "Hello"}]
930
+ - A list of strings: ["Hello", "How are you?"]
931
+ model: The model to use for this run (overrides default).
932
+ - Can be a LanguageModel instance or model name string like "gpt-4"
933
+ max_steps: Maximum number of steps to execute (overrides default).
934
+ - Useful for limiting tool usage or preventing infinite loops
935
+ context: Context object for the agent (overrides default).
936
+ - Any object that provides additional context for the conversation
937
+ output_type: The expected output type (overrides default).
938
+ - Use for structured outputs: output_type=MyPydanticModel
939
+ - Defaults to str for unstructured text responses
940
+ **kwargs: Additional keyword arguments passed to the language model.
941
+ - Examples: temperature=0.7, top_p=0.9, presence_penalty=0.1
942
+
943
+ Returns:
944
+ AgentResponse containing the final output, steps taken, and metadata.
945
+
946
+ Examples:
947
+ Basic async usage:
948
+ >>> import asyncio
949
+ >>> agent = Agent()
950
+ >>> async def main():
951
+ ... response = await agent.async_run("Hello, how are you?")
952
+ ... print(response.output)
953
+ >>> asyncio.run(main())
954
+
955
+ Multiple concurrent requests:
956
+ >>> async def process_multiple():
957
+ ... tasks = [
958
+ ... agent.async_run("What's 2+2?"),
959
+ ... agent.async_run("What's the capital of France?"),
960
+ ... agent.async_run("Explain photosynthesis")
961
+ ... ]
962
+ ... responses = await asyncio.gather(*tasks)
963
+ ... return responses
964
+
965
+ With structured output:
966
+ >>> from pydantic import BaseModel
967
+ >>> class Analysis(BaseModel):
968
+ ... sentiment: str
969
+ ... confidence: float
970
+ >>> async def analyze_text():
971
+ ... response = await agent.async_run(
972
+ ... "Analyze the sentiment of: 'I love this product!'",
973
+ ... output_type=Analysis
974
+ ... )
975
+ ... return response.output
976
+
977
+ With custom model and context:
978
+ >>> async def custom_run():
979
+ ... context = {"domain": "medical", "expertise_level": "expert"}
980
+ ... response = await agent.async_run(
981
+ ... "Explain diabetes",
982
+ ... model="gpt-4",
983
+ ... context=context,
984
+ ... temperature=0.2
985
+ ... )
986
+ ... return response.output
987
+ """
988
+ # Use provided model or default
989
+ if model is None:
990
+ working_model = self.language_model
991
+ elif isinstance(model, str):
992
+ working_model = LanguageModel(model=model)
993
+ else:
994
+ working_model = model
995
+
996
+ # Use provided max_steps or default
997
+ if max_steps is None:
998
+ max_steps = self.settings.max_steps
999
+
1000
+ # Get effective context settings
1001
+ effective_context_settings = self._get_effective_context_settings(
1002
+ context_updates=context_updates,
1003
+ context_confirm=context_confirm,
1004
+ context_strategy=context_strategy,
1005
+ context_max_retries=context_max_retries,
1006
+ context_confirm_instructions=context_confirm_instructions,
1007
+ context_selection_instructions=context_selection_instructions,
1008
+ context_update_instructions=context_update_instructions,
1009
+ context_format=context_format,
1010
+ )
1011
+
1012
+ # Parse initial messages
1013
+ parsed_messages = parse_messages(messages)
1014
+ current_messages = parsed_messages.copy()
1015
+ steps: List[LanguageModelResponse[str]] = []
1016
+
1017
+ # RUN MAIN AGENTIC LOOP
1018
+ for step in range(max_steps):
1019
+ # Update context before processing if configured
1020
+ if context and self._should_update_context(context, "before", effective_context_settings["context_updates"]):
1021
+ context = self._perform_context_update(
1022
+ context=context,
1023
+ model=working_model,
1024
+ current_messages=current_messages,
1025
+ timing="before",
1026
+ effective_settings=effective_context_settings,
1027
+ )
1028
+
1029
+ # Format messages with instructions and context for first step only
1030
+ if step == 0:
1031
+ formatted_messages = self._format_messages_with_context(
1032
+ messages=current_messages,
1033
+ context=context,
1034
+ )
1035
+ else:
1036
+ formatted_messages = current_messages
1037
+
1038
+ # Prepare kwargs for language model
1039
+ model_kwargs = kwargs.copy()
1040
+ if output_type:
1041
+ model_kwargs["type"] = output_type
1042
+ if self.instructor_mode:
1043
+ model_kwargs["instructor_mode"] = self.instructor_mode
1044
+
1045
+ # Get language model response
1046
+ response = await working_model.async_run(
1047
+ messages=formatted_messages,
1048
+ tools=[tool.to_dict() for tool in self.tools]
1049
+ if self.tools
1050
+ else None,
1051
+ **model_kwargs,
1052
+ )
1053
+
1054
+ # Check if response has tool calls
1055
+ if response.has_tool_calls():
1056
+ # Add response to message history (with tool calls)
1057
+ current_messages.append(response.to_message())
1058
+
1059
+ # Execute tools and add their responses to messages
1060
+ tool_responses = execute_tools_from_language_model_response(
1061
+ tools=self.tools, response=response
1062
+ )
1063
+ # Add tool responses to message history
1064
+ for tool_resp in tool_responses:
1065
+ current_messages.append(tool_resp.to_dict())
1066
+
1067
+ # This is not the final step, add to steps
1068
+ steps.append(response)
1069
+ else:
1070
+ # No tool calls - this is the final step
1071
+ # Update context after processing if configured
1072
+ if context and self._should_update_context(context, "after", effective_context_settings["context_updates"]):
1073
+ context = self._perform_context_update(
1074
+ context=context,
1075
+ model=working_model,
1076
+ current_messages=current_messages,
1077
+ timing="after",
1078
+ effective_settings=effective_context_settings,
1079
+ )
1080
+ return _create_agent_response_from_language_model_response(
1081
+ response=response, steps=steps, context=context
1082
+ )
1083
+
1084
+ # Max steps reached - return last response
1085
+ if steps:
1086
+ final_response = steps[-1]
1087
+ else:
1088
+ # No steps taken, make a final call
1089
+ final_response = await working_model.async_run(
1090
+ messages=self._format_messages_with_context(
1091
+ messages=current_messages,
1092
+ context=context,
1093
+ ),
1094
+ **model_kwargs,
1095
+ )
1096
+
1097
+ # Update context after processing if configured
1098
+ if context and self._should_update_context(context, "after", effective_context_settings["context_updates"]):
1099
+ context = self._perform_context_update(
1100
+ context=context,
1101
+ model=working_model,
1102
+ current_messages=current_messages,
1103
+ timing="after",
1104
+ effective_settings=effective_context_settings,
1105
+ )
1106
+
1107
+ return _create_agent_response_from_language_model_response(
1108
+ response=final_response, steps=steps, context=context
1109
+ )
1110
+
1111
+ def stream(
1112
+ self,
1113
+ messages: AgentMessages,
1114
+ model: Optional[Union[LanguageModel, LanguageModelName]] = None,
1115
+ max_steps: Optional[int] = None,
1116
+ context: Optional[AgentContext] = None,
1117
+ output_type: Optional[Type[T]] = None,
1118
+ **kwargs: Any,
1119
+ ) -> AgentStream[T, AgentContext]:
1120
+ """Create a stream that yields agent steps.
1121
+
1122
+ Args:
1123
+ messages: The input messages to process
1124
+ model: Language model to use (overrides agent's default)
1125
+ max_steps: Maximum number of steps to take
1126
+ context: Context object to maintain state
1127
+ output_type: Type for structured output
1128
+ **kwargs: Additional parameters for the language model
1129
+
1130
+ Returns:
1131
+ An AgentStream that can be iterated over
1132
+ """
1133
+ return AgentStream(
1134
+ agent=self,
1135
+ messages=messages,
1136
+ model=model,
1137
+ max_steps=max_steps,
1138
+ context=context,
1139
+ output_type=output_type,
1140
+ stream=True,
1141
+ **kwargs,
1142
+ )
1143
+
1144
+ def iter(
1145
+ self,
1146
+ messages: AgentMessages,
1147
+ model: Optional[Union[LanguageModel, LanguageModelName]] = None,
1148
+ max_steps: Optional[int] = None,
1149
+ context: Optional[AgentContext] = None,
1150
+ output_type: Optional[Type[T]] = None,
1151
+ context_updates: Optional[
1152
+ Union[List[Literal["before", "after"]], Literal["before", "after"]]
1153
+ ] = None,
1154
+ context_confirm: Optional[bool] = None,
1155
+ context_strategy: Optional[Literal["selective", "all"]] = None,
1156
+ context_max_retries: Optional[int] = None,
1157
+ context_confirm_instructions: Optional[str] = None,
1158
+ context_selection_instructions: Optional[str] = None,
1159
+ context_update_instructions: Optional[str] = None,
1160
+ context_format: Optional[Literal["json", "python", "markdown"]] = None,
1161
+ **kwargs: Any,
1162
+ ) -> AgentStream[T, AgentContext]:
1163
+ """Iterate over agent steps, yielding each step response.
1164
+
1165
+ You can override defaults assigned to this agent from this function directly.
1166
+ Returns an AgentStream that yields intermediate steps and the final result.
1167
+
1168
+ Args:
1169
+ messages: The messages to process. Can be:
1170
+ - A single string: "What's the weather like?"
1171
+ - A list of message dicts: [{"role": "user", "content": "Hello"}]
1172
+ - A list of strings: ["Hello", "How are you?"]
1173
+ model: The model to use for this run (overrides default).
1174
+ - Can be a LanguageModel instance or model name string like "gpt-4"
1175
+ max_steps: Maximum number of steps to execute (overrides default).
1176
+ - Useful for limiting tool usage or preventing infinite loops
1177
+ context: Context object for the agent (overrides default).
1178
+ - Any object that provides additional context for the conversation
1179
+ output_type: The expected output type (overrides default).
1180
+ - Use for structured outputs: output_type=MyPydanticModel
1181
+ - Defaults to str for unstructured text responses
1182
+ **kwargs: Additional keyword arguments passed to the language model.
1183
+ - Examples: temperature=0.7, top_p=0.9, presence_penalty=0.1
1184
+
1185
+ Returns:
1186
+ AgentStream that can be iterated over to get each step response,
1187
+ including tool calls and intermediate reasoning steps.
1188
+
1189
+ Examples:
1190
+ Basic iteration over steps:
1191
+ >>> agent = Agent(tools=[calculator_tool])
1192
+ >>> stream = agent.iter("What's 25 * 47?")
1193
+ >>> for step in stream:
1194
+ ... print(f"Step {step.step_number}: {step.output}")
1195
+ ... if step.tool_calls:
1196
+ ... print(f"Tool calls: {len(step.tool_calls)}")
1197
+
1198
+ Real-time processing with streaming:
1199
+ >>> stream = agent.iter("Write a poem about nature")
1200
+ >>> for chunk in stream:
1201
+ ... if chunk.output:
1202
+ ... print(chunk.output, end="", flush=True)
1203
+ ... if chunk.is_final:
1204
+ ... print("\n--- Final response ---")
1205
+
1206
+ With structured output iteration:
1207
+ >>> from pydantic import BaseModel
1208
+ >>> class StepAnalysis(BaseModel):
1209
+ ... reasoning: str
1210
+ ... confidence: float
1211
+ >>> stream = agent.iter(
1212
+ ... "Analyze this step by step: Why is the sky blue?",
1213
+ ... output_type=StepAnalysis
1214
+ ... )
1215
+ >>> for step in stream:
1216
+ ... if step.output:
1217
+ ... print(f"Reasoning: {step.output.reasoning}")
1218
+ ... print(f"Confidence: {step.output.confidence}")
1219
+
1220
+ Processing with custom model and context:
1221
+ >>> context = {"domain": "science", "depth": "detailed"}
1222
+ >>> stream = agent.iter(
1223
+ ... "Explain quantum entanglement",
1224
+ ... model="gpt-4",
1225
+ ... context=context,
1226
+ ... max_steps=3,
1227
+ ... temperature=0.1
1228
+ ... )
1229
+ >>> results = []
1230
+ >>> for step in stream:
1231
+ ... results.append(step.output)
1232
+ ... if step.is_final:
1233
+ ... break
1234
+
1235
+ Error handling during iteration:
1236
+ >>> try:
1237
+ ... stream = agent.iter("Complex calculation task")
1238
+ ... for step in stream:
1239
+ ... if step.error:
1240
+ ... print(f"Error in step: {step.error}")
1241
+ ... else:
1242
+ ... print(f"Step result: {step.output}")
1243
+ ... except Exception as e:
1244
+ ... print(f"Stream error: {e}")
1245
+ """
1246
+ return AgentStream(
1247
+ agent=self,
1248
+ messages=messages,
1249
+ model=model,
1250
+ max_steps=max_steps,
1251
+ context=context,
1252
+ output_type=output_type,
1253
+ stream=True,
1254
+ **kwargs,
1255
+ )
1256
+
1257
+ def async_iter(
1258
+ self,
1259
+ messages: AgentMessages,
1260
+ model: Optional[Union[LanguageModel, LanguageModelName]] = None,
1261
+ max_steps: Optional[int] = None,
1262
+ context: Optional[AgentContext] = None,
1263
+ output_type: Optional[Type[T]] = None,
1264
+ **kwargs: Any,
1265
+ ) -> AgentStream[T, AgentContext]:
1266
+ """Async iterate over agent steps, yielding each step response.
1267
+
1268
+ Args:
1269
+ messages: The input messages to process
1270
+ model: Language model to use (overrides agent's default)
1271
+ max_steps: Maximum number of steps to take
1272
+ context: Context object to maintain state
1273
+ output_type: Type for structured output
1274
+ **kwargs: Additional parameters for the language model
1275
+
1276
+ Returns:
1277
+ An AgentStream that can be iterated over asynchronously
1278
+ """
1279
+ return AgentStream(
1280
+ agent=self,
1281
+ messages=messages,
1282
+ model=model,
1283
+ max_steps=max_steps,
1284
+ context=context,
1285
+ output_type=output_type,
1286
+ stream=True,
1287
+ **kwargs,
1288
+ )
1289
+
1290
+
1291
+ __all__ = [
1292
+ "Agent",
1293
+ "AgentSettings",
1294
+ "AgentModelSettings",
1295
+ "AgentEvent",
1296
+ "HookManager",
1297
+ "HookDecorator",
1298
+ ]