diff --git a/src/crewai/agents/agent_adapters/base_agent_adapter.py b/src/crewai/agents/agent_adapters/base_agent_adapter.py index 7bbb83442..f4a7ffd58 100644 --- a/src/crewai/agents/agent_adapters/base_agent_adapter.py +++ b/src/crewai/agents/agent_adapters/base_agent_adapter.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod -from typing import Any, List, Optional +from typing import Any, Dict, List, Optional -from pydantic import BaseModel +from pydantic import PrivateAttr from crewai.agent import BaseAgent from crewai.tools import BaseTool @@ -16,11 +16,13 @@ class BaseAgentAdapter(BaseAgent, ABC): """ adapted_structured_output: bool = False + _agent_config: Optional[Dict[str, Any]] = PrivateAttr(default=None) model_config = {"arbitrary_types_allowed": True} - def __init__(self, **kwargs: Any): + def __init__(self, agent_config: Optional[Dict[str, Any]] = None, **kwargs: Any): super().__init__(**kwargs) + self._agent_config = agent_config @abstractmethod def configure_tools(self, tools: Optional[List[BaseTool]] = None) -> None: diff --git a/src/crewai/agents/agent_adapters/base_converter_adapter.py b/src/crewai/agents/agent_adapters/base_converter_adapter.py new file mode 100644 index 000000000..0bb9128d5 --- /dev/null +++ b/src/crewai/agents/agent_adapters/base_converter_adapter.py @@ -0,0 +1,24 @@ +from abc import ABC, abstractmethod + + +class BaseConverterAdapter(ABC): + def __init__(self, agent_adapter): + self.agent_adapter = agent_adapter + + @abstractmethod + def configure_structured_output(self, task) -> None: + """Configure agents to return structured output. + Must support json and pydantic output. + """ + pass + + @abstractmethod + def enhance_system_prompt(self, base_prompt: str) -> str: + """Enhance the system prompt with structured output instructions.""" + pass + + @abstractmethod + def post_process_result(self, result: str) -> str: + """Post-process the result to ensure it matches the expected format.""" + # Transform the string result to the expected format. + pass diff --git a/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py b/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py index 1ea2b457d..06c275f7f 100644 --- a/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py +++ b/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py @@ -45,9 +45,10 @@ class LangGraphAgentAdapter(BaseAgentAdapter): role: str, goal: str, backstory: str, - tools: Optional[List[BaseTool]] = None, + tools: Optional[List[BaseTool]] = [], llm: Any = None, max_iterations: int = 10, + agent_config: Optional[Dict[str, Any]] = None, **kwargs, ): """Initialize the LangGraph agent adapter.""" @@ -57,9 +58,10 @@ class LangGraphAgentAdapter(BaseAgentAdapter): backstory=backstory, tools=tools, llm=llm or self.model, + agent_config=agent_config, **kwargs, ) - self._tool_adapter = LangGraphToolAdapter(tools=tools) + self._tool_adapter = LangGraphToolAdapter(tools=tools or []) self._converter_adapter = LangGraphConverterAdapter(self) self._max_iterations = max_iterations self._setup_graph() @@ -70,15 +72,13 @@ class LangGraphAgentAdapter(BaseAgentAdapter): # Initialize memory for the agent self._memory = MemorySaver() - # Convert CrewAI tools to LangGraph/LangChain compatible tools converted_tools = self._tool_adapter.converted_tools - print("langgraph converted_tools", converted_tools) - # Create the agent graph with ReAct pattern self._graph = create_react_agent( model=self.llm, tools=converted_tools, checkpointer=self._memory, + debug=self.verbose, ) except ImportError as e: diff --git a/src/crewai/agents/agent_adapters/langgraph/structured_output_converter.py b/src/crewai/agents/agent_adapters/langgraph/structured_output_converter.py index 9beeae74a..79f6dcb15 100644 --- a/src/crewai/agents/agent_adapters/langgraph/structured_output_converter.py +++ b/src/crewai/agents/agent_adapters/langgraph/structured_output_converter.py @@ -1,9 +1,10 @@ import json +from crewai.agents.agent_adapters.base_converter_adapter import BaseConverterAdapter from crewai.utilities.converter import generate_model_description -class LangGraphConverterAdapter: +class LangGraphConverterAdapter(BaseConverterAdapter): """Adapter for handling structured output conversion in LangGraph agents""" def __init__(self, agent_adapter): diff --git a/src/crewai/agents/agent_adapters/openai_agents/structured_output_adapter.py b/src/crewai/agents/agent_adapters/openai_agents/structured_output_adapter.py index 8aba1cca9..8c0524c01 100644 --- a/src/crewai/agents/agent_adapters/openai_agents/structured_output_adapter.py +++ b/src/crewai/agents/agent_adapters/openai_agents/structured_output_adapter.py @@ -1,10 +1,11 @@ import json import re +from crewai.agents.agent_adapters.base_converter_adapter import BaseConverterAdapter from crewai.utilities.converter import generate_model_description -class OpenAIConverterAdapter: +class OpenAIConverterAdapter(BaseConverterAdapter): """ Adapter for handling structured output conversion in OpenAI agents. @@ -53,6 +54,28 @@ class OpenAIConverterAdapter: self._output_model = task.output_pydantic self.agent_adapter._openai_agent.output_type = task.output_pydantic + def enhance_system_prompt(self, base_prompt: str) -> str: + """ + Enhance the base system prompt with structured output requirements if needed. + + Args: + base_prompt: The original system prompt + + Returns: + Enhanced system prompt with output format instructions if needed + """ + if not self._output_format: + return base_prompt + + output_instructions = f""" + Your response MUST conform to the following {self._output_format.upper()} schema: + {self._schema} + + Ensure your final response is properly formatted according to this schema. + """ + + return f"{base_prompt}\n\n{output_instructions}" + def post_process_result(self, result: str) -> str: """ Post-process the result to ensure it matches the expected format. @@ -65,10 +88,8 @@ class OpenAIConverterAdapter: Returns: Processed result conforming to the expected output format """ - print("result", result) if not self._output_format: return result - print("self._output_format", self._output_format) # Try to extract valid JSON if it's wrapped in code blocks or other text if isinstance(result, str) and self._output_format in ["json", "pydantic"]: # First, try to parse as is