77import logging
88import json
99import asyncio
10- from enum import Enum
10+ import time
11+ import re
12+ import numpy as np
1113
1214from pydantic import BaseModel , Field , PrivateAttr , ConfigDict , ValidationError
1315
2123from ..core .isa .isa_manager import Instruction , InstructionType , InstructionStatus
2224from ..core .isa .selector import InstructionSelector
2325from ..ell2a .integration import ELL2AIntegration
24- from ..ell2a .types .message import Message , MessageRole , MessageType
25- from ..core .types import AgentType , AgentMode , AgentStatus , AgentConfig , ModelConfig
26+ from ..ell2a .types .message import Message , ContentBlock , MessageRole , MessageType
27+ from ..core .base_types import AgentType , AgentMode , AgentStatus
2628from ..transformations .advanced_strategies import AdvancedTransformationStrategy
2729from ..core .exceptions import WorkflowExecutionError , ConfigurationError
28- from unittest .mock import AsyncMock
2930from ..core .workflow_types import WorkflowConfig
31+ from ..core .agent_config import AgentConfig
3032
3133logger = logging .getLogger (__name__ )
3234
@@ -39,6 +41,104 @@ class AgentState(BaseModel):
3941 iteration : int = Field (default = 0 )
4042 last_error : Optional [str ] = None
4143 messages_processed : int = Field (default = 0 )
44+ messages : List [Dict [str , Any ]] = Field (default_factory = list )
45+ metrics : Dict [str , Any ] = Field (default_factory = dict )
46+ errors : List [Dict [str , Any ]] = Field (default_factory = list )
47+ result : Optional [Dict [str , Any ]] = None
48+ error : Optional [Dict [str , Any ]] = None
49+ start_time : Optional [float ] = None
50+ end_time : Optional [float ] = None
51+
52+ def __str__ (self ) -> str :
53+ """String representation."""
54+ return f"AgentState(status={ self .status } , iteration={ self .iteration } )"
55+
56+ def __repr__ (self ) -> str :
57+ """Detailed string representation."""
58+ return f"AgentState(status={ self .status } , iteration={ self .iteration } , errors={ len (self .errors )} )"
59+
60+ def to_dict (self ) -> Dict [str , Any ]:
61+ """Convert to dictionary."""
62+ return {
63+ "status" : self .status ,
64+ "iteration" : self .iteration ,
65+ "last_error" : self .last_error ,
66+ "messages_processed" : self .messages_processed ,
67+ "metrics" : self .metrics ,
68+ "errors" : self .errors ,
69+ "result" : self .result ,
70+ "error" : self .error ,
71+ "start_time" : self .start_time ,
72+ "end_time" : self .end_time
73+ }
74+
75+ async def process_message (self , message : Union [str , Dict [str , Any ], Message , ContentBlock ]) -> str :
76+ """Process a message and update agent state.
77+
78+ Args:
79+ message: Message to process
80+
81+ Returns:
82+ str: Processed message content
83+
84+ Raises:
85+ ValueError: If message processing fails or message string conversion fails
86+ """
87+ # Record start time
88+ self .start_time = time .time ()
89+ self .status = AgentStatus .PROCESSING
90+
91+ try :
92+ # Specifically handle ErrorMessage and ErrorContentBlock
93+ if hasattr (message , '__str__' ) and message .__class__ .__name__ in ['ErrorMessage' , 'ErrorContentBlock' ]:
94+ raise ValueError ("Error during string conversion" )
95+
96+ # Convert message to string representation
97+ message_str = str (message )
98+
99+ # Process the message
100+ if isinstance (message , str ):
101+ processed_message = {"content" : message , "role" : "user" }
102+ elif isinstance (message , dict ):
103+ processed_message = message
104+ elif isinstance (message , Message ) or isinstance (message , ContentBlock ):
105+ processed_message = message .model_dump ()
106+ else :
107+ raise ValueError (f"Unsupported message type: { type (message )} " )
108+
109+ # Update messages processed
110+ self .messages_processed += 1
111+ self .messages .append (processed_message )
112+
113+ # Update status
114+ self .status = AgentStatus .SUCCESS
115+
116+ # Return the string representation
117+ return message_str
118+
119+ except ValueError as e :
120+ # If ValueError is raised during string conversion or processing
121+ self .status = AgentStatus .FAILED
122+ self .last_error = str (e )
123+ self .errors .append ({
124+ "error" : str (e ),
125+ "timestamp" : str (datetime .now ())
126+ })
127+ raise
128+
129+ except Exception as e :
130+ # Handle other unexpected errors
131+ self .status = AgentStatus .FAILED
132+ self .last_error = str (e )
133+ self .errors .append ({
134+ "error" : str (e ),
135+ "timestamp" : str (datetime .now ())
136+ })
137+ raise
138+
139+ finally :
140+ # Always record end time
141+ self .end_time = time .time ()
42142
43143class AgentBase (BaseModel ):
44144 """Base agent class."""
@@ -173,33 +273,45 @@ def __init__(self, config: Optional[Union['AgentConfig', Dict[str, Any]]] = None
173273 ValueError: If the configuration is invalid
174274 """
175275 # Import at runtime to avoid circular import
176- from ..core .config import AgentConfig , ModelConfig , WorkflowConfig
276+ from ..core .agent_config import AgentConfig
277+ from ..core .model_config import ModelConfig
278+ from ..core .workflow_types import WorkflowConfig
177279
178280 if config is None :
179- config = AgentConfig (name = name or str (uuid .uuid4 ()))
281+ config = AgentConfig (name = name or str (uuid .uuid4 ()), type = kwargs . get ( 'type' , 'generic' ) )
180282 elif isinstance (config , dict ):
181283 if not config : # Empty dictionary
182284 raise ValueError ("Agent must have a configuration" )
183285 try :
184286 # Extract domain config and name from config
185287 domain_config = config .get ("DOMAIN_CONFIG" , {})
186288 agent_name = config .get ("AGENT" , {}).get ("name" )
187- config = AgentConfig (** config )
289+ agent_type = config .get ("AGENT" , {}).get ("type" , kwargs .get ('type' , 'generic' ))
290+
291+ # Remove type from config if present to avoid multiple values
292+ config_copy = config .copy ()
293+ config_copy .pop ('type' , None )
294+ config_copy .pop ('AGENT' , None )
295+ config_copy .pop ('name' , None )
296+
297+ config = AgentConfig (
298+ name = kwargs .get ('name' , agent_name or name or str (uuid .uuid4 ())),
299+ type = agent_type ,
300+ ** config_copy
301+ )
188302 config .domain_config = domain_config
189- if agent_name :
190- config .name = agent_name
191303 except Exception as e :
192304 raise ValueError (f"Invalid agent configuration: { str (e )} " )
193305
194306 self .id = kwargs .get ('id' , str (uuid .uuid4 ()))
195307 self .name = name or config .name
196- self .type = kwargs .get ('type' , config .type )
308+ self .type = kwargs .get ('type' , config .type or 'generic' )
197309 self .mode = kwargs .get ('mode' , getattr (config , 'mode' , 'sequential' ))
198310 self .config = config
199311 self .domain_config = getattr (config , 'domain_config' , {}) # Extract domain_config
200312 self .metadata : Dict [str , Any ] = {}
201313 self ._initialized = False
202- self ._status : AgentStatus = AgentStatus .IDLE
314+ self ._status : AgentStatus = AgentStatus .INITIALIZED
203315
204316 # Ensure max_errors is always an int
205317 config_max_errors = getattr (config , 'max_errors' , None )
@@ -209,7 +321,7 @@ def __init__(self, config: Optional[Union['AgentConfig', Dict[str, Any]]] = None
209321 self .history : List [Dict [str , Any ]] = [] # Add history list
210322
211323 # Initialize state
212- self .state = AgentState (status = AgentStatus .IDLE )
324+ self .state = AgentState (status = AgentStatus .INITIALIZED )
213325
214326 # Initialize components
215327 self ._ell2a = kwargs .get ('ell2a' , None )
@@ -294,7 +406,7 @@ async def initialize(self) -> None:
294406 await self ._instruction_selector .initialize ()
295407
296408 self ._initialized = True
297- self ._status = AgentStatus .IDLE
409+ self ._status = AgentStatus .INITIALIZED
298410 except Exception as e :
299411 self ._status = AgentStatus .FAILED
300412 raise
@@ -319,12 +431,14 @@ async def process_message(self, message: Union[str, Dict[str, Any], Message]) ->
319431 elif isinstance (message , dict ):
320432 message = Message (** message )
321433
322- # Add message to history
323- self .history .append ({
324- "role" : message .role ,
325- "content" : message .content ,
326- "timestamp" : str (datetime .now ())
327- })
434+ # Optionally add message to history based on a configuration flag
435+ disable_history = getattr (self , '_disable_history' , False )
436+ if not disable_history :
437+ self .history .append ({
438+ "role" : message .role ,
439+ "content" : str (message .content ),
440+ "timestamp" : str (datetime .now ())
441+ })
328442
329443 # Update state
330444 self .state .messages_processed += 1
@@ -333,26 +447,33 @@ async def process_message(self, message: Union[str, Dict[str, Any], Message]) ->
333447 try :
334448 # Process message using ELL2A
335449 result = await self ._ell2a .process_message (message )
336- self .state .status = AgentStatus .IDLE
450+ self .state .status = AgentStatus .SUCCESS
337451
338- # Add response to history
339- response_content = result .content if isinstance (result , Message ) else str (result )
340- if isinstance (response_content , list ):
341- response_content = " " .join (str (block .content ) for block in response_content )
342- self .history .append ({
343- "role" : MessageRole .ASSISTANT ,
344- "content" : response_content ,
345- "timestamp" : str (datetime .now ())
346- })
452+ # Optionally add response to history
453+ if not disable_history :
454+ response_content = result .content if isinstance (result , Message ) else str (result )
455+ if isinstance (response_content , list ):
456+ response_content = " " .join (str (block .text ) if isinstance (block , ContentBlock ) else str (block ) for block in response_content )
457+ elif isinstance (response_content , ContentBlock ):
458+ response_content = response_content .text or ""
459+ self .history .append ({
460+ "role" : MessageRole .ASSISTANT ,
461+ "content" : response_content ,
462+ "timestamp" : str (datetime .now ())
463+ })
347464
348465 # Return the response content
466+ if isinstance (result , str ):
467+ if result .startswith ("[ContentBlock(" ):
468+ # Extract the text field from the ContentBlock string representation
469+ match = re .search (r"text='([^']*)'" , result )
470+ if match :
471+ return match .group (1 ) or ""
472+ return result
349473 if isinstance (result , Message ):
350- content = result .content
351- if isinstance (content , list ):
352- return " " .join (str (block .content ) for block in content )
353- return str (content )
354- if isinstance (result , (list , tuple )):
355- return str (result [0 ] if result else "" )
474+ # Extract text from the content block
475+ if isinstance (result .content , ContentBlock ):
476+ return result .content .text or ""
356477 return str (result )
357478 except Exception as e :
358479 self .state .status = AgentStatus .FAILED
@@ -384,13 +505,41 @@ async def execute(self, input_data: Dict[str, Any]) -> str:
384505 raise WorkflowExecutionError (error_msg )
385506
386507 # Create message from input data
508+ data = input_data .get ("data" , "" )
509+ if isinstance (data , np .ndarray ):
510+ # Convert numpy array to list representation with each row on a new line
511+ data_rows = []
512+ for row in data :
513+ data_rows .append (str (row .tolist ()))
514+ data_str = "\n " .join (data_rows )
515+ else :
516+ data_str = str (data )
517+
518+ content_block = ContentBlock (
519+ type = MessageType .RESULT ,
520+ text = data_str
521+ )
387522 message = Message (
388- content = str ( input_data . get ( "data" , "" )) ,
523+ content = content_block ,
389524 role = MessageRole .USER ,
525+ type = MessageType .RESULT ,
390526 metadata = input_data .get ("metadata" , {})
391527 )
392528
393- return await self .process_message (message )
529+ result = await self .process_message (message )
530+ if isinstance (result , str ):
531+ # Extract text from ContentBlock string representation
532+ match = re .search (r"text='([^']*)'" , result )
533+ if match :
534+ # Unescape the string to handle newlines correctly
535+ return match .group (1 ).encode ('utf-8' ).decode ('unicode_escape' )
536+ return result
537+ if isinstance (result , Message ):
538+ # Extract text from the content block
539+ if isinstance (result .content , ContentBlock ):
540+ return result .content .text .encode ('utf-8' ).decode ('unicode_escape' )
541+ return str (result .content )
542+ return str (result )
394543
395544 async def cleanup (self ) -> None :
396545 """Clean up agent resources."""
@@ -421,7 +570,7 @@ async def cleanup(self) -> None:
421570 cleanup_method ()
422571
423572 self ._initialized = False
424- self ._status = AgentStatus .INITIALIZED
573+ self ._status = AgentStatus .STOPPED
425574 except Exception as e :
426575 logger .error (f"Error during cleanup: { e } " )
427576 raise
@@ -436,11 +585,29 @@ def __init__(self, config=None):
436585 self .name = 'remote_agent'
437586 self .type = AgentType .GENERIC
438587 self .version = '1.0.0'
588+ self ._status = AgentStatus .INITIALIZED
589+ self ._initialized = False
439590
440- async def initialize (self ):
591+ @ray .method (num_returns = 1 )
592+ def get_status_remote (self ):
593+ """Remote method to get agent status."""
594+ return str (self ._status )
595+
596+ @ray .method (num_returns = 1 )
597+ def initialize (self ):
441598 """Initialize remote agent."""
442- pass
599+ self ._initialized = True
600+ return True
443601
444- async def cleanup (self ):
602+ @ray .method (num_returns = 1 )
603+ def cleanup (self ):
445604 """Clean up remote agent resources."""
446- pass
605+ self ._status = AgentStatus .STOPPED
606+ return True
607+
608+ def set_status (self , value : AgentStatus ) -> None :
609+ """Set agent status."""
610+ self ._status = value
611+
612+ self ._status = value
613+
0 commit comments