1
1
import instructor
2
2
from pydantic import BaseModel , Field
3
- from typing import Optional , Type , Generator , AsyncGenerator , get_args
3
+ from typing import Optional , Type , Generator , AsyncGenerator , get_args , Dict , List , Callable
4
+ import logging
4
5
from atomic_agents .context .chat_history import ChatHistory
5
6
from atomic_agents .context .system_prompt_generator import (
6
7
BaseDynamicContextProvider ,
@@ -73,10 +74,11 @@ class AgentConfig(BaseModel):
73
74
74
75
class AtomicAgent [InputSchema : BaseIOSchema , OutputSchema : BaseIOSchema ]:
75
76
"""
76
- Base class for chat agents.
77
+ Base class for chat agents with full Instructor hook system integration .
77
78
78
79
This class provides the core functionality for handling chat interactions, including managing history,
79
- generating system prompts, and obtaining responses from a language model.
80
+ generating system prompts, and obtaining responses from a language model. It includes comprehensive
81
+ hook system support for monitoring and error handling.
80
82
81
83
Type Parameters:
82
84
InputSchema: Schema for the user input, must be a subclass of BaseIOSchema.
@@ -92,6 +94,39 @@ class AtomicAgent[InputSchema: BaseIOSchema, OutputSchema: BaseIOSchema]:
92
94
current_user_input (Optional[InputSchema]): The current user input being processed.
93
95
model_api_parameters (dict): Additional parameters passed to the API provider.
94
96
- Use this for parameters like 'temperature', 'max_tokens', etc.
97
+
98
+ Hook System:
99
+ The AtomicAgent integrates with Instructor's hook system to provide comprehensive monitoring
100
+ and error handling capabilities. Supported events include:
101
+
102
+ - 'parse:error': Triggered when Pydantic validation fails
103
+ - 'completion:kwargs': Triggered before completion request
104
+ - 'completion:response': Triggered after completion response
105
+ - 'completion:error': Triggered on completion errors
106
+ - 'completion:last_attempt': Triggered on final retry attempt
107
+
108
+ Hook Methods:
109
+ - register_hook(event, handler): Register a hook handler for an event
110
+ - unregister_hook(event, handler): Remove a hook handler
111
+ - clear_hooks(event=None): Clear hooks for specific event or all events
112
+ - enable_hooks()/disable_hooks(): Control hook processing
113
+ - hooks_enabled: Property to check if hooks are enabled
114
+
115
+ Example:
116
+ ```python
117
+ # Basic usage
118
+ agent = AtomicAgent[InputSchema, OutputSchema](config)
119
+
120
+ # Register parse error hook for intelligent retry handling
121
+ def handle_parse_error(error):
122
+ print(f"Validation failed: {error}")
123
+ # Implement custom retry logic, logging, etc.
124
+
125
+ agent.register_hook("parse:error", handle_parse_error)
126
+
127
+ # Now parse:error hooks will fire on validation failures
128
+ response = agent.run(user_input)
129
+ ```
95
130
"""
96
131
97
132
def __init__ (self , config : AgentConfig ):
@@ -110,6 +145,10 @@ def __init__(self, config: AgentConfig):
110
145
self .current_user_input = None
111
146
self .model_api_parameters = config .model_api_parameters or {}
112
147
148
+ # Hook management attributes
149
+ self ._hook_handlers : Dict [str , List [Callable ]] = {}
150
+ self ._hooks_enabled : bool = True
151
+
113
152
def reset_history (self ):
114
153
"""
115
154
Resets the history to its initial state.
@@ -318,6 +357,90 @@ def unregister_context_provider(self, provider_name: str):
318
357
else :
319
358
raise KeyError (f"Context provider '{ provider_name } ' not found." )
320
359
360
+ # Hook Management Methods
361
+ def register_hook (self , event : str , handler : Callable ) -> None :
362
+ """
363
+ Registers a hook handler for a specific event.
364
+
365
+ Args:
366
+ event (str): The event name (e.g., 'parse:error', 'completion:kwargs', etc.)
367
+ handler (Callable): The callback function to handle the event
368
+ """
369
+ if event not in self ._hook_handlers :
370
+ self ._hook_handlers [event ] = []
371
+ self ._hook_handlers [event ].append (handler )
372
+
373
+ # Register with instructor client if it supports hooks
374
+ if hasattr (self .client , "on" ):
375
+ self .client .on (event , handler )
376
+
377
+ def unregister_hook (self , event : str , handler : Callable ) -> None :
378
+ """
379
+ Unregisters a hook handler for a specific event.
380
+
381
+ Args:
382
+ event (str): The event name
383
+ handler (Callable): The callback function to remove
384
+ """
385
+ if event in self ._hook_handlers and handler in self ._hook_handlers [event ]:
386
+ self ._hook_handlers [event ].remove (handler )
387
+
388
+ # Remove from instructor client if it supports hooks
389
+ if hasattr (self .client , "off" ):
390
+ self .client .off (event , handler )
391
+
392
+ def clear_hooks (self , event : Optional [str ] = None ) -> None :
393
+ """
394
+ Clears hook handlers for a specific event or all events.
395
+
396
+ Args:
397
+ event (Optional[str]): The event name to clear, or None to clear all
398
+ """
399
+ if event :
400
+ if event in self ._hook_handlers :
401
+ # Clear from instructor client first
402
+ if hasattr (self .client , "clear" ):
403
+ self .client .clear (event )
404
+ self ._hook_handlers [event ].clear ()
405
+ else :
406
+ # Clear all hooks
407
+ if hasattr (self .client , "clear" ):
408
+ self .client .clear ()
409
+ self ._hook_handlers .clear ()
410
+
411
+ def _dispatch_hook (self , event : str , * args , ** kwargs ) -> None :
412
+ """
413
+ Internal method to dispatch hook events with error isolation.
414
+
415
+ Args:
416
+ event (str): The event name
417
+ *args: Arguments to pass to handlers
418
+ **kwargs: Keyword arguments to pass to handlers
419
+ """
420
+ if not self ._hooks_enabled or event not in self ._hook_handlers :
421
+ return
422
+
423
+ for handler in self ._hook_handlers [event ]:
424
+ try :
425
+ handler (* args , ** kwargs )
426
+ except Exception as e :
427
+ # Log error but don't interrupt main flow
428
+ logger = logging .getLogger (__name__ )
429
+ logger .warning (f"Hook handler for '{ event } ' raised exception: { e } " )
430
+
431
+ def enable_hooks (self ) -> None :
432
+ """Enable hook processing."""
433
+ self ._hooks_enabled = True
434
+
435
+ def disable_hooks (self ) -> None :
436
+ """Disable hook processing."""
437
+ self ._hooks_enabled = False
438
+
439
+ @property
440
+ def hooks_enabled (self ) -> bool :
441
+ """Check if hooks are enabled."""
442
+ return self ._hooks_enabled
443
+
321
444
322
445
if __name__ == "__main__" :
323
446
from rich .console import Console
0 commit comments