22
33import asyncio
44from collections .abc import Callable
5- import time
65from functools import wraps
76import inspect
87import json
8+ import time
99from types import MethodType
1010from typing import (
1111 TYPE_CHECKING ,
4949 LiteAgentExecutionErrorEvent ,
5050 LiteAgentExecutionStartedEvent ,
5151)
52+ from crewai .events .types .logging_events import AgentLogsExecutionEvent
5253from crewai .events .types .memory_events import (
5354 MemoryRetrievalCompletedEvent ,
5455 MemoryRetrievalFailedEvent ,
5556 MemoryRetrievalStartedEvent ,
5657)
57- from crewai .events .types .logging_events import AgentLogsExecutionEvent
5858from crewai .flow .flow_trackable import FlowTrackable
5959from crewai .hooks .llm_hooks import get_after_llm_call_hooks , get_before_llm_call_hooks
60- from crewai .hooks .types import AfterLLMCallHookType , BeforeLLMCallHookType
60+ from crewai .hooks .types import (
61+ AfterLLMCallHookCallable ,
62+ AfterLLMCallHookType ,
63+ BeforeLLMCallHookCallable ,
64+ BeforeLLMCallHookType ,
65+ )
6166from crewai .lite_agent_output import LiteAgentOutput
6267from crewai .llm import LLM
6368from crewai .llms .base_llm import BaseLLM
@@ -270,11 +275,11 @@ class LiteAgent(FlowTrackable, BaseModel):
270275 _guardrail : GuardrailCallable | None = PrivateAttr (default = None )
271276 _guardrail_retry_count : int = PrivateAttr (default = 0 )
272277 _callbacks : list [TokenCalcHandler ] = PrivateAttr (default_factory = list )
273- _before_llm_call_hooks : list [BeforeLLMCallHookType ] = PrivateAttr (
274- default_factory = get_before_llm_call_hooks
278+ _before_llm_call_hooks : list [BeforeLLMCallHookType | BeforeLLMCallHookCallable ] = (
279+ PrivateAttr ( default_factory = get_before_llm_call_hooks )
275280 )
276- _after_llm_call_hooks : list [AfterLLMCallHookType ] = PrivateAttr (
277- default_factory = get_after_llm_call_hooks
281+ _after_llm_call_hooks : list [AfterLLMCallHookType | AfterLLMCallHookCallable ] = (
282+ PrivateAttr ( default_factory = get_after_llm_call_hooks )
278283 )
279284 _memory : Any = PrivateAttr (default = None )
280285
@@ -440,12 +445,16 @@ def _original_role(self) -> str:
440445 return self .role
441446
442447 @property
443- def before_llm_call_hooks (self ) -> list [BeforeLLMCallHookType ]:
448+ def before_llm_call_hooks (
449+ self ,
450+ ) -> list [BeforeLLMCallHookType | BeforeLLMCallHookCallable ]:
444451 """Get the before_llm_call hooks for this agent."""
445452 return self ._before_llm_call_hooks
446453
447454 @property
448- def after_llm_call_hooks (self ) -> list [AfterLLMCallHookType ]:
455+ def after_llm_call_hooks (
456+ self ,
457+ ) -> list [AfterLLMCallHookType | AfterLLMCallHookCallable ]:
449458 """Get the after_llm_call hooks for this agent."""
450459 return self ._after_llm_call_hooks
451460
@@ -482,11 +491,12 @@ def kickoff(
482491 # Inject memory tools once if memory is configured (mirrors Agent._prepare_kickoff)
483492 if self ._memory is not None :
484493 from crewai .tools .memory_tools import create_memory_tools
485- from crewai .utilities .agent_utils import sanitize_tool_name
494+ from crewai .utilities .string_utils import sanitize_tool_name
486495
487496 existing_names = {sanitize_tool_name (t .name ) for t in self ._parsed_tools }
488497 memory_tools = [
489- mt for mt in create_memory_tools (self ._memory )
498+ mt
499+ for mt in create_memory_tools (self ._memory )
490500 if sanitize_tool_name (mt .name ) not in existing_names
491501 ]
492502 if memory_tools :
@@ -565,9 +575,10 @@ def _inject_memory_context(self) -> None:
565575 if memory_block :
566576 formatted = self .i18n .slice ("memory" ).format (memory = memory_block )
567577 if self ._messages and self ._messages [0 ].get ("role" ) == "system" :
568- self ._messages [0 ]["content" ] = (
569- self ._messages [0 ].get ("content" , "" ) + "\n \n " + formatted
570- )
578+ existing_content = self ._messages [0 ].get ("content" , "" )
579+ if not isinstance (existing_content , str ):
580+ existing_content = ""
581+ self ._messages [0 ]["content" ] = existing_content + "\n \n " + formatted
571582 crewai_event_bus .emit (
572583 self ,
573584 event = MemoryRetrievalCompletedEvent (
@@ -593,11 +604,7 @@ def _save_to_memory(self, output_text: str) -> None:
593604 return
594605 input_str = self ._get_last_user_content () or "User request"
595606 try :
596- raw = (
597- f"Input: { input_str } \n "
598- f"Agent: { self .role } \n "
599- f"Result: { output_text } "
600- )
607+ raw = f"Input: { input_str } \n Agent: { self .role } \n Result: { output_text } "
601608 extracted = self ._memory .extract_memories (raw )
602609 if extracted :
603610 self ._memory .remember_many (extracted , agent_role = self .role )
@@ -622,13 +629,20 @@ def _execute_core(
622629 )
623630
624631 # Execute the agent using invoke loop
625- agent_finish = self ._invoke_loop ()
632+ active_response_format = response_format or self .response_format
633+ agent_finish = self ._invoke_loop (response_model = active_response_format )
626634 if self ._memory is not None :
627- self ._save_to_memory (agent_finish .output )
635+ output_text = (
636+ agent_finish .output .model_dump_json ()
637+ if isinstance (agent_finish .output , BaseModel )
638+ else agent_finish .output
639+ )
640+ self ._save_to_memory (output_text )
628641 formatted_result : BaseModel | None = None
629642
630- active_response_format = response_format or self .response_format
631- if active_response_format :
643+ if isinstance (agent_finish .output , BaseModel ):
644+ formatted_result = agent_finish .output
645+ elif active_response_format :
632646 try :
633647 model_schema = generate_model_description (active_response_format )
634648 schema = json .dumps (model_schema , indent = 2 )
@@ -660,8 +674,13 @@ def _execute_core(
660674 usage_metrics = self ._token_process .get_summary ()
661675
662676 # Create output
677+ raw_output = (
678+ agent_finish .output .model_dump_json ()
679+ if isinstance (agent_finish .output , BaseModel )
680+ else agent_finish .output
681+ )
663682 output = LiteAgentOutput (
664- raw = agent_finish . output ,
683+ raw = raw_output ,
665684 pydantic = formatted_result ,
666685 agent_role = self .role ,
667686 usage_metrics = usage_metrics .model_dump () if usage_metrics else None ,
@@ -838,10 +857,15 @@ def _format_messages(
838857
839858 return formatted_messages
840859
841- def _invoke_loop (self ) -> AgentFinish :
860+ def _invoke_loop (
861+ self , response_model : type [BaseModel ] | None = None
862+ ) -> AgentFinish :
842863 """
843864 Run the agent's thought process until it reaches a conclusion or max iterations.
844865
866+ Args:
867+ response_model: Optional Pydantic model for native structured output.
868+
845869 Returns:
846870 AgentFinish: The final result of the agent execution.
847871 """
@@ -870,12 +894,19 @@ def _invoke_loop(self) -> AgentFinish:
870894 printer = self ._printer ,
871895 from_agent = self ,
872896 executor_context = self ,
897+ response_model = response_model ,
873898 verbose = self .verbose ,
874899 )
875900
876901 except Exception as e :
877902 raise e
878903
904+ if isinstance (answer , BaseModel ):
905+ formatted_answer = AgentFinish (
906+ thought = "" , output = answer , text = answer .model_dump_json ()
907+ )
908+ break
909+
879910 formatted_answer = process_llm_response (
880911 cast (str , answer ), self .use_stop_words
881912 )
@@ -901,7 +932,7 @@ def _invoke_loop(self) -> AgentFinish:
901932 )
902933
903934 self ._append_message (formatted_answer .text , role = "assistant" )
904- except OutputParserError as e : # noqa: PERF203
935+ except OutputParserError as e :
905936 if self .verbose :
906937 self ._printer .print (
907938 content = "Failed to parse LLM output. Retrying..." ,
0 commit comments