Skip to content

Commit 45d0c99

Browse files
chore: add type annotations and docstrings to openai agent adapters (#3505)
1 parent 1f1ab14 commit 45d0c99

File tree

4 files changed

+352
-131
lines changed

4 files changed

+352
-131
lines changed
Lines changed: 134 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,99 @@
1-
from typing import Any, List, Optional
1+
"""OpenAI agents adapter for CrewAI integration.
22
3-
from pydantic import Field, PrivateAttr
3+
This module contains the OpenAIAgentAdapter class that integrates OpenAI Assistants
4+
with CrewAI's agent system, providing tool integration and structured output support.
5+
"""
6+
7+
from typing import Any, cast
8+
9+
from pydantic import ConfigDict, Field, PrivateAttr
10+
from typing_extensions import Unpack
411

512
from crewai.agents.agent_adapters.base_agent_adapter import BaseAgentAdapter
13+
from crewai.agents.agent_adapters.openai_agents.openai_agent_tool_adapter import (
14+
OpenAIAgentToolAdapter,
15+
)
16+
from crewai.agents.agent_adapters.openai_agents.protocols import (
17+
AgentKwargs,
18+
OpenAIAgentsModule,
19+
)
20+
from crewai.agents.agent_adapters.openai_agents.protocols import (
21+
OpenAIAgent as OpenAIAgentProtocol,
22+
)
623
from crewai.agents.agent_adapters.openai_agents.structured_output_converter import (
724
OpenAIConverterAdapter,
825
)
926
from crewai.agents.agent_builder.base_agent import BaseAgent
10-
from crewai.tools import BaseTool
11-
from crewai.tools.agent_tools.agent_tools import AgentTools
12-
from crewai.utilities import Logger
1327
from crewai.events.event_bus import crewai_event_bus
1428
from crewai.events.types.agent_events import (
1529
AgentExecutionCompletedEvent,
1630
AgentExecutionErrorEvent,
1731
AgentExecutionStartedEvent,
1832
)
19-
20-
try:
21-
from agents import Agent as OpenAIAgent # type: ignore
22-
from agents import Runner, enable_verbose_stdout_logging # type: ignore
23-
24-
from .openai_agent_tool_adapter import OpenAIAgentToolAdapter
25-
26-
OPENAI_AVAILABLE = True
27-
except ImportError:
28-
OPENAI_AVAILABLE = False
33+
from crewai.tools import BaseTool
34+
from crewai.tools.agent_tools.agent_tools import AgentTools
35+
from crewai.utilities import Logger
36+
from crewai.utilities.import_utils import require
37+
38+
openai_agents_module = cast(
39+
OpenAIAgentsModule,
40+
require(
41+
"agents",
42+
purpose="OpenAI agents functionality",
43+
),
44+
)
45+
OpenAIAgent = openai_agents_module.Agent
46+
Runner = openai_agents_module.Runner
47+
enable_verbose_stdout_logging = openai_agents_module.enable_verbose_stdout_logging
2948

3049

3150
class OpenAIAgentAdapter(BaseAgentAdapter):
32-
"""Adapter for OpenAI Assistants"""
51+
"""Adapter for OpenAI Assistants.
52+
53+
Integrates OpenAI Assistants API with CrewAI's agent system, providing
54+
tool configuration, structured output handling, and task execution.
55+
"""
3356

34-
model_config = {"arbitrary_types_allowed": True}
57+
model_config = ConfigDict(arbitrary_types_allowed=True)
3558

36-
_openai_agent: "OpenAIAgent" = PrivateAttr()
37-
_logger: Logger = PrivateAttr(default_factory=lambda: Logger())
38-
_active_thread: Optional[str] = PrivateAttr(default=None)
59+
_openai_agent: OpenAIAgentProtocol = PrivateAttr()
60+
_logger: Logger = PrivateAttr(default_factory=Logger)
61+
_active_thread: str | None = PrivateAttr(default=None)
3962
function_calling_llm: Any = Field(default=None)
4063
step_callback: Any = Field(default=None)
41-
_tool_adapter: "OpenAIAgentToolAdapter" = PrivateAttr()
64+
_tool_adapter: OpenAIAgentToolAdapter = PrivateAttr()
4265
_converter_adapter: OpenAIConverterAdapter = PrivateAttr()
4366

4467
def __init__(
4568
self,
46-
model: str = "gpt-4o-mini",
47-
tools: Optional[List[BaseTool]] = None,
48-
agent_config: Optional[dict] = None,
49-
**kwargs,
50-
):
51-
if not OPENAI_AVAILABLE:
52-
raise ImportError(
53-
"OpenAI Agent Dependencies are not installed. Please install it using `uv add openai-agents`"
54-
)
55-
else:
56-
role = kwargs.pop("role", None)
57-
goal = kwargs.pop("goal", None)
58-
backstory = kwargs.pop("backstory", None)
59-
super().__init__(
60-
role=role,
61-
goal=goal,
62-
backstory=backstory,
63-
tools=tools,
64-
agent_config=agent_config,
65-
**kwargs,
66-
)
67-
self._tool_adapter = OpenAIAgentToolAdapter(tools=tools)
68-
self.llm = model
69-
self._converter_adapter = OpenAIConverterAdapter(self)
69+
**kwargs: Unpack[AgentKwargs],
70+
) -> None:
71+
"""Initialize the OpenAI agent adapter.
72+
73+
Args:
74+
**kwargs: All initialization arguments including role, goal, backstory,
75+
model, tools, and agent_config.
76+
77+
Raises:
78+
ImportError: If OpenAI agent dependencies are not installed.
79+
"""
80+
super().__init__(**kwargs)
81+
self._tool_adapter = OpenAIAgentToolAdapter(tools=kwargs.get("tools"))
82+
self.llm = kwargs.get("model", "gpt-4o-mini")
83+
self._converter_adapter = OpenAIConverterAdapter(agent_adapter=self)
7084

7185
def _build_system_prompt(self) -> str:
72-
"""Build a system prompt for the OpenAI agent."""
86+
"""Build a system prompt for the OpenAI agent.
87+
88+
Creates a prompt containing the agent's role, goal, and backstory,
89+
then enhances it with structured output instructions if needed.
90+
91+
Returns:
92+
The complete system prompt string.
93+
"""
7394
base_prompt = f"""
7495
You are {self.role}.
75-
96+
7697
Your goal is: {self.goal}
7798
7899
Your backstory: {self.backstory}
@@ -84,18 +105,33 @@ def _build_system_prompt(self) -> str:
84105
def execute_task(
85106
self,
86107
task: Any,
87-
context: Optional[str] = None,
88-
tools: Optional[List[BaseTool]] = None,
108+
context: str | None = None,
109+
tools: list[BaseTool] | None = None,
89110
) -> str:
90-
"""Execute a task using the OpenAI Assistant"""
111+
"""Execute a task using the OpenAI Assistant.
112+
113+
Configures the assistant, processes the task, and handles event emission
114+
for execution tracking.
115+
116+
Args:
117+
task: The task object to execute.
118+
context: Optional context information for the task.
119+
tools: Optional additional tools for this execution.
120+
121+
Returns:
122+
The final answer from the task execution.
123+
124+
Raises:
125+
Exception: If task execution fails.
126+
"""
91127
self._converter_adapter.configure_structured_output(task)
92128
self.create_agent_executor(tools)
93129

94130
if self.verbose:
95131
enable_verbose_stdout_logging()
96132

97133
try:
98-
task_prompt = task.prompt()
134+
task_prompt: str = task.prompt()
99135
if context:
100136
task_prompt = self.i18n.slice("task_with_context").format(
101137
task=task_prompt, context=context
@@ -109,8 +145,8 @@ def execute_task(
109145
task=task,
110146
),
111147
)
112-
result = self.agent_executor.run_sync(self._openai_agent, task_prompt)
113-
final_answer = self.handle_execution_result(result)
148+
result: Any = self.agent_executor.run_sync(self._openai_agent, task_prompt)
149+
final_answer: str = self.handle_execution_result(result)
114150
crewai_event_bus.emit(
115151
self,
116152
event=AgentExecutionCompletedEvent(
@@ -120,7 +156,7 @@ def execute_task(
120156
return final_answer
121157

122158
except Exception as e:
123-
self._logger.log("error", f"Error executing OpenAI task: {str(e)}")
159+
self._logger.log("error", f"Error executing OpenAI task: {e!s}")
124160
crewai_event_bus.emit(
125161
self,
126162
event=AgentExecutionErrorEvent(
@@ -131,15 +167,22 @@ def execute_task(
131167
)
132168
raise
133169

134-
def create_agent_executor(self, tools: Optional[List[BaseTool]] = None) -> None:
135-
"""
136-
Configure the OpenAI agent for execution.
170+
def create_agent_executor(self, tools: list[BaseTool] | None = None) -> None:
171+
"""Configure the OpenAI agent for execution.
172+
137173
While OpenAI handles execution differently through Runner,
138-
we can use this method to set up tools and configurations.
174+
this method sets up tools and agent configuration.
175+
176+
Args:
177+
tools: Optional tools to configure for the agent.
178+
179+
Notes:
180+
TODO: Properly type agent_executor in BaseAgent to avoid type issues
181+
when assigning Runner class to this attribute.
139182
"""
140-
all_tools = list(self.tools or []) + list(tools or [])
183+
all_tools: list[BaseTool] = list(self.tools or []) + list(tools or [])
141184

142-
instructions = self._build_system_prompt()
185+
instructions: str = self._build_system_prompt()
143186
self._openai_agent = OpenAIAgent(
144187
name=self.role,
145188
instructions=instructions,
@@ -152,27 +195,48 @@ def create_agent_executor(self, tools: Optional[List[BaseTool]] = None) -> None:
152195

153196
self.agent_executor = Runner
154197

155-
def configure_tools(self, tools: Optional[List[BaseTool]] = None) -> None:
156-
"""Configure tools for the OpenAI Assistant"""
198+
def configure_tools(self, tools: list[BaseTool] | None = None) -> None:
199+
"""Configure tools for the OpenAI Assistant.
200+
201+
Args:
202+
tools: Optional tools to configure for the assistant.
203+
"""
157204
if tools:
158205
self._tool_adapter.configure_tools(tools)
159206
if self._tool_adapter.converted_tools:
160207
self._openai_agent.tools = self._tool_adapter.converted_tools
161208

162209
def handle_execution_result(self, result: Any) -> str:
163-
"""Process OpenAI Assistant execution result converting any structured output to a string"""
210+
"""Process OpenAI Assistant execution result.
211+
212+
Converts any structured output to a string through the converter adapter.
213+
214+
Args:
215+
result: The execution result from the OpenAI assistant.
216+
217+
Returns:
218+
Processed result as a string.
219+
"""
164220
return self._converter_adapter.post_process_result(result.final_output)
165221

166-
def get_delegation_tools(self, agents: List[BaseAgent]) -> List[BaseTool]:
167-
"""Implement delegation tools support"""
168-
agent_tools = AgentTools(agents=agents)
169-
tools = agent_tools.tools()
170-
return tools
222+
def get_delegation_tools(self, agents: list[BaseAgent]) -> list[BaseTool]:
223+
"""Implement delegation tools support.
224+
225+
Creates delegation tools that allow this agent to delegate tasks to other agents.
226+
227+
Args:
228+
agents: List of agents available for delegation.
229+
230+
Returns:
231+
List of delegation tools.
232+
"""
233+
agent_tools: AgentTools = AgentTools(agents=agents)
234+
return agent_tools.tools()
171235

172-
def configure_structured_output(self, task) -> None:
236+
def configure_structured_output(self, task: Any) -> None:
173237
"""Configure the structured output for the specific agent implementation.
174238
175239
Args:
176-
structured_output: The structured output to be configured
240+
task: The task object containing output format specifications.
177241
"""
178242
self._converter_adapter.configure_structured_output(task)

0 commit comments

Comments
 (0)