Files
crewAI/src/crewai/agents/agent_adapters/langgraph/langgraph_adapter.py
Lorenze Jay 870dffbb89
Some checks are pending
Notify Downstream / notify-downstream (push) Waiting to run
Feat/byoa (#2523)
* feat: add OpenAI agent adapter implementation

- Introduced OpenAIAgentAdapter class to facilitate interaction with OpenAI Assistants.
- Implemented methods for task execution, tool configuration, and response processing.
- Added support for converting CrewAI tools to OpenAI format and handling delegation tools.

* created an adapter for the delegate and ask_question tools

* delegate and ask_questions work and it delegates to crewai agents*

* refactor: introduce OpenAIAgentToolAdapter for tool management

- Created OpenAIAgentToolAdapter class to encapsulate tool configuration and conversion for OpenAI Assistant.
- Removed tool configuration logic from OpenAIAgentAdapter and integrated it into the new adapter.
- Enhanced the tool conversion process to ensure compatibility with OpenAI's requirements.

* feat: implement BaseAgentAdapter for agent integration

- Introduced BaseAgentAdapter as an abstract base class for agent adapters in CrewAI.
- Defined common interface and methods for configuring tools and structured output.
- Updated OpenAIAgentAdapter to inherit from BaseAgentAdapter, enhancing its structure and functionality.

* feat: add LangGraph agent and tool adapter for CrewAI integration

- Introduced LangGraphAgentAdapter to facilitate interaction with LangGraph agents.
- Implemented methods for task execution, context handling, and tool configuration.
- Created LangGraphToolAdapter to convert CrewAI tools into LangGraph-compatible format.
- Enhanced error handling and logging for task execution and streaming processes.

* feat: enhance LangGraphToolAdapter and improve conversion instructions

- Added type hints for better clarity and type checking in LangGraphToolAdapter.
- Updated conversion instructions to ensure compatibility with optional LLM checks.

* feat: integrate structured output handling in LangGraph and OpenAI agents

- Added LangGraphConverterAdapter for managing structured output in LangGraph agents.
- Enhanced LangGraphAgentAdapter to utilize the new converter for system prompt and task execution.
- Updated LangGraphToolAdapter to use StructuredTool for better compatibility.
- Introduced OpenAIConverterAdapter for structured output management in OpenAI agents.
- Improved task execution flow in OpenAIAgentAdapter to incorporate structured output configuration and post-processing.

* feat: implement BaseToolAdapter for tool integration

- Introduced BaseToolAdapter as an abstract base class for tool adapters in CrewAI.
- Updated LangGraphToolAdapter and OpenAIAgentToolAdapter to inherit from BaseToolAdapter, enhancing their structure and functionality.
- Improved tool configuration methods to support better integration with various frameworks.
- Added type hints and documentation for clarity and maintainability.

* feat: enhance OpenAIAgentAdapter with configurable agent properties

- Refactored OpenAIAgentAdapter to accept agent configuration as an argument.
- Introduced a method to build a system prompt for the OpenAI agent, improving task execution context.
- Updated initialization to utilize role, goal, and backstory from kwargs, enhancing flexibility in agent setup.
- Improved tool handling and integration within the adapter.

* feat: enhance agent adapters with structured output support

- Introduced BaseConverterAdapter as an abstract class for structured output handling.
- Implemented LangGraphConverterAdapter and OpenAIConverterAdapter to manage structured output in their respective agents.
- Updated BaseAgentAdapter to accept an agent configuration dictionary during initialization.
- Enhanced LangGraphAgentAdapter to utilize the new converter and improved tool handling.
- Added methods for configuring structured output and enhancing system prompts in converter adapters.

* refactor: remove _parse_tools method from OpenAIAgentAdapter and BaseAgent

- Eliminated the _parse_tools method from OpenAIAgentAdapter and its abstract declaration in BaseAgent.
- Cleaned up related test code in MockAgent to reflect the removal of the method.

* also removed _parse_tools here as not used

* feat: add dynamic import handling for LangGraph dependencies

- Implemented conditional imports for LangGraph components to handle ImportError gracefully.
- Updated LangGraphAgentAdapter initialization to check for LangGraph availability and raise an informative error if dependencies are missing.
- Enhanced the agent adapter's robustness by ensuring it only initializes components when the required libraries are present.

* fix: improve error handling for agent adapters

- Updated LangGraphAgentAdapter to raise an ImportError with a clear message if LangGraph dependencies are not installed.
- Refactored OpenAIAgentAdapter to include a similar check for OpenAI dependencies, ensuring robust initialization and user guidance for missing libraries.
- Enhanced overall error handling in agent adapters to prevent runtime issues when dependencies are unavailable.

* refactor: enhance tool handling in agent adapters

- Updated BaseToolAdapter to initialize original and converted tools in the constructor.
- Renamed method `all_tools` to `tools` for clarity in BaseToolAdapter.
- Added `sanitize_tool_name` method to ensure tool names are API compatible.
- Modified LangGraphAgentAdapter to utilize the updated tool handling and ensure proper tool configuration.
- Refactored LangGraphToolAdapter to streamline tool conversion and ensure consistent naming conventions.

* feat: emit AgentExecutionCompletedEvent in agent adapters

- Added emission of AgentExecutionCompletedEvent in both LangGraphAgentAdapter and OpenAIAgentAdapter to signal task completion.
- Enhanced event handling to include agent, task, and output details for better tracking of execution results.

* docs: Enhance BaseConverterAdapter documentation

- Added a detailed docstring to the BaseConverterAdapter class, outlining its purpose and the expected functionality for all converter adapters.
- Updated the post_process_result method's docstring to specify the expected format of the result as a string.

* docs: Add comprehensive guide for bringing custom agents into CrewAI

- Introduced a new documentation file detailing the process of integrating custom agents using the BaseAgentAdapter, BaseToolAdapter, and BaseConverter.
- Included step-by-step instructions for creating custom adapters, configuring tools, and handling structured output.
- Provided examples for implementing adapters for various frameworks, enhancing the usability of CrewAI for developers.

* feat: Introduce adapted_agent flag in BaseAgent and update BaseAgentAdapter initialization

- Added an `adapted_agent` boolean field to the BaseAgent class to indicate if the agent is adapted.
- Updated the BaseAgentAdapter's constructor to pass `adapted_agent=True` to the superclass, ensuring proper initialization of the new field.

* feat: Enhance LangGraphAgentAdapter to support optional agent configuration

- Updated LangGraphAgentAdapter to conditionally apply agent configuration when creating the agent graph, allowing for more flexible initialization.
- Modified LangGraphToolAdapter to ensure only instances of BaseTool are converted, improving tool compatibility and handling.

* feat: Introduce OpenAIConverterAdapter for structured output handling

- Added OpenAIConverterAdapter to manage structured output conversion for OpenAI agents, enhancing their ability to process and format results.
- Updated OpenAIAgentAdapter to utilize the new converter for configuring structured output and post-processing results.
- Removed the deprecated get_output_converter method from OpenAIAgentAdapter.
- Added unit tests for BaseAgentAdapter and BaseToolAdapter to ensure proper functionality and integration of new features.

* feat: Enhance tool adapters to support asynchronous execution

- Updated LangGraphToolAdapter and OpenAIAgentToolAdapter to handle asynchronous tool execution by checking if the output is awaitable.
- Introduced `inspect` import to facilitate the awaitability check.
- Refactored tool wrapper functions to ensure proper handling of both synchronous and asynchronous tool results.

* fix: Correct method definition syntax and enhance tool adapter implementation

- Updated the method definition for `configure_structured_output` to include the `def` keyword for clarity.
- Added an asynchronous tool wrapper to ensure tools can operate in both synchronous and asynchronous contexts.
- Modified the constructor of the custom converter adapter to directly assign the agent adapter, improving clarity and functionality.

* linted

* refactor: Improve tool processing logic in BaseAgent

- Added a check to return an empty list if no tools are provided.
- Simplified the tool attribute validation by using a list of required attributes.
- Removed commented-out abstract method definition for clarity.

* refactor: Simplify tool handling in agent adapters

- Changed default value of `tools` parameter in LangGraphAgentAdapter to None for better handling of empty tool lists.
- Updated tool initialization in both LangGraphAgentAdapter and OpenAIAgentAdapter to directly pass the `tools` parameter, removing unnecessary list handling.
- Cleaned up commented-out code in OpenAIConverterAdapter to improve readability.

* refactor: Remove unused stream_task method from LangGraphAgentAdapter

- Deleted the `stream_task` method from LangGraphAgentAdapter to streamline the code and eliminate unnecessary complexity.
- This change enhances maintainability by focusing on essential functionalities within the agent adapter.
2025-04-17 09:22:48 -07:00

227 lines
7.7 KiB
Python

from typing import Any, AsyncIterable, Dict, List, Optional
from pydantic import Field, PrivateAttr
from crewai.agents.agent_adapters.base_agent_adapter import BaseAgentAdapter
from crewai.agents.agent_adapters.langgraph.langgraph_tool_adapter import (
LangGraphToolAdapter,
)
from crewai.agents.agent_adapters.langgraph.structured_output_converter import (
LangGraphConverterAdapter,
)
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.tools.agent_tools.agent_tools import AgentTools
from crewai.tools.base_tool import BaseTool
from crewai.utilities import Logger
from crewai.utilities.converter import Converter
from crewai.utilities.events import crewai_event_bus
from crewai.utilities.events.agent_events import (
AgentExecutionCompletedEvent,
AgentExecutionErrorEvent,
AgentExecutionStartedEvent,
)
try:
from langchain_core.messages import ToolMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
LANGGRAPH_AVAILABLE = True
except ImportError:
LANGGRAPH_AVAILABLE = False
class LangGraphAgentAdapter(BaseAgentAdapter):
"""Adapter for LangGraph agents to work with CrewAI."""
model_config = {"arbitrary_types_allowed": True}
_logger: Logger = PrivateAttr(default_factory=lambda: Logger())
_tool_adapter: LangGraphToolAdapter = PrivateAttr()
_graph: Any = PrivateAttr(default=None)
_memory: Any = PrivateAttr(default=None)
_max_iterations: int = PrivateAttr(default=10)
function_calling_llm: Any = Field(default=None)
step_callback: Any = Field(default=None)
model: str = Field(default="gpt-4o")
verbose: bool = Field(default=False)
def __init__(
self,
role: str,
goal: str,
backstory: str,
tools: Optional[List[BaseTool]] = None,
llm: Any = None,
max_iterations: int = 10,
agent_config: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize the LangGraph agent adapter."""
if not LANGGRAPH_AVAILABLE:
raise ImportError(
"LangGraph Agent Dependencies are not installed. Please install it using `uv add langchain-core langgraph`"
)
super().__init__(
role=role,
goal=goal,
backstory=backstory,
tools=tools,
llm=llm or self.model,
agent_config=agent_config,
**kwargs,
)
self._tool_adapter = LangGraphToolAdapter(tools=tools)
self._converter_adapter = LangGraphConverterAdapter(self)
self._max_iterations = max_iterations
self._setup_graph()
def _setup_graph(self) -> None:
"""Set up the LangGraph workflow graph."""
try:
self._memory = MemorySaver()
converted_tools: List[Any] = self._tool_adapter.tools()
if self._agent_config:
self._graph = create_react_agent(
model=self.llm,
tools=converted_tools,
checkpointer=self._memory,
debug=self.verbose,
**self._agent_config,
)
else:
self._graph = create_react_agent(
model=self.llm,
tools=converted_tools or [],
checkpointer=self._memory,
debug=self.verbose,
)
except ImportError as e:
self._logger.log(
"error", f"Failed to import LangGraph dependencies: {str(e)}"
)
raise
except Exception as e:
self._logger.log("error", f"Error setting up LangGraph agent: {str(e)}")
raise
def _build_system_prompt(self) -> str:
"""Build a system prompt for the LangGraph agent."""
base_prompt = f"""
You are {self.role}.
Your goal is: {self.goal}
Your backstory: {self.backstory}
When working on tasks, think step-by-step and use the available tools when necessary.
"""
return self._converter_adapter.enhance_system_prompt(base_prompt)
def execute_task(
self,
task: Any,
context: Optional[str] = None,
tools: Optional[List[BaseTool]] = None,
) -> str:
"""Execute a task using the LangGraph workflow."""
self.create_agent_executor(tools)
self.configure_structured_output(task)
try:
task_prompt = task.prompt() if hasattr(task, "prompt") else str(task)
if context:
task_prompt = self.i18n.slice("task_with_context").format(
task=task_prompt, context=context
)
crewai_event_bus.emit(
self,
event=AgentExecutionStartedEvent(
agent=self,
tools=self.tools,
task_prompt=task_prompt,
task=task,
),
)
session_id = f"task_{id(task)}"
config = {"configurable": {"thread_id": session_id}}
result = self._graph.invoke(
{
"messages": [
("system", self._build_system_prompt()),
("user", task_prompt),
]
},
config,
)
messages = result.get("messages", [])
last_message = messages[-1] if messages else None
final_answer = ""
if isinstance(last_message, dict):
final_answer = last_message.get("content", "")
elif hasattr(last_message, "content"):
final_answer = getattr(last_message, "content", "")
final_answer = (
self._converter_adapter.post_process_result(final_answer)
or "Task execution completed but no clear answer was provided."
)
crewai_event_bus.emit(
self,
event=AgentExecutionCompletedEvent(
agent=self, task=task, output=final_answer
),
)
return final_answer
except Exception as e:
self._logger.log("error", f"Error executing LangGraph task: {str(e)}")
crewai_event_bus.emit(
self,
event=AgentExecutionErrorEvent(
agent=self,
task=task,
error=str(e),
),
)
raise
def create_agent_executor(self, tools: Optional[List[BaseTool]] = None) -> None:
"""Configure the LangGraph agent for execution."""
self.configure_tools(tools)
def configure_tools(self, tools: Optional[List[BaseTool]] = None) -> None:
"""Configure tools for the LangGraph agent."""
if tools:
all_tools = list(self.tools or []) + list(tools or [])
self._tool_adapter.configure_tools(all_tools)
available_tools = self._tool_adapter.tools()
self._graph.tools = available_tools
def get_delegation_tools(self, agents: List[BaseAgent]) -> List[BaseTool]:
"""Implement delegation tools support for LangGraph."""
agent_tools = AgentTools(agents=agents)
return agent_tools.tools()
def get_output_converter(
self, llm: Any, text: str, model: Any, instructions: str
) -> Any:
"""Convert output format if needed."""
return Converter(llm=llm, text=text, model=model, instructions=instructions)
def configure_structured_output(self, task) -> None:
"""Configure the structured output for LangGraph."""
self._converter_adapter.configure_structured_output(task)