Save changes

This commit is contained in:
James Hush
2025-09-17 11:09:03 +08:00
parent 54c8f336c3
commit 0421d97954
2 changed files with 89 additions and 27 deletions

View File

@@ -17,6 +17,7 @@ Requirements:
import os import os
import random import random
from typing import Any
# Import agents SDK for tools and agent creation # Import agents SDK for tools and agent creation
from agents import Agent, function_tool from agents import Agent, function_tool
@@ -119,7 +120,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
) )
# Create tools for the agent # Create tools for the agent
tools = [ tools: list[Any] = [
get_weather, get_weather,
get_random_fact, get_random_fact,
] ]

View File

@@ -6,19 +6,31 @@
"""OpenAI Agents SDK integration service. """OpenAI Agents SDK integration service.
Provides integration with the OpenAI Agents SDK for building agentic AI applications Provides integration with the OpenAI Agents SDK for building AI applications
within Pipecat pipelines. This service allows leveraging agent loops, handoffs, within Pipecat pipelines. This service allows leveraging agent loops, handoffs,
guardrails, sessions, and tools from the OpenAI Agents SDK. guardrails, sessions, and tools from the OpenAI Agents SDK.
""" """
import asyncio import asyncio
import os import os
from typing import Any, Awaitable, Callable, Dict, List, Optional, Union, override from typing import (
Any,
Awaitable,
Callable,
Dict,
List,
Optional,
Protocol,
Sequence,
Union,
override,
runtime_checkable,
)
from loguru import logger from loguru import logger
try: try:
from agents import Agent, InputGuardrail, OutputGuardrail, Runner from agents import Agent, InputGuardrail, OutputGuardrail, Runner, Tool
from agents.result import RunResult, RunResultStreaming from agents.result import RunResult, RunResultStreaming
from agents.stream_events import StreamEvent from agents.stream_events import StreamEvent
except ImportError as e: except ImportError as e:
@@ -45,6 +57,26 @@ from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.ai_service import AIService from pipecat.services.ai_service import AIService
@runtime_checkable
class ToolLike(Protocol):
"""Protocol for tool-like objects."""
def __call__(self, *args: Any, **kwargs: Any) -> Any:
"""Tool call interface."""
...
@runtime_checkable
class AgentLike(Protocol):
"""Protocol for agent-like objects."""
name: str
def __call__(self, *args: Any, **kwargs: Any) -> Any:
"""Agent call interface."""
...
class OpenAIAgentService(AIService): class OpenAIAgentService(AIService):
"""OpenAI Agents SDK service for Pipecat. """OpenAI Agents SDK service for Pipecat.
@@ -61,11 +93,11 @@ class OpenAIAgentService(AIService):
*, *,
agent: Optional[Agent] = None, agent: Optional[Agent] = None,
name: str = "Assistant", name: str = "Assistant",
instructions: str = "You are a helpful assistant.", instructions: Union[str, Sequence[str]] = "You are a helpful assistant.",
handoffs: Optional[List[Agent]] = None, handoffs: Optional[Sequence[AgentLike]] = None,
tools: Optional[List[Callable]] = None, tools: Optional[Sequence[ToolLike]] = None,
input_guardrails: Optional[List[InputGuardrail]] = None, input_guardrails: Optional[Sequence[InputGuardrail]] = None,
output_guardrails: Optional[List[OutputGuardrail]] = None, output_guardrails: Optional[Sequence[OutputGuardrail]] = None,
model_config: Optional[Dict[str, Any]] = None, model_config: Optional[Dict[str, Any]] = None,
session_config: Optional[Dict[str, Any]] = None, session_config: Optional[Dict[str, Any]] = None,
api_key: Optional[str] = None, api_key: Optional[str] = None,
@@ -101,13 +133,27 @@ class OpenAIAgentService(AIService):
if agent: if agent:
self._agent = agent self._agent = agent
else: else:
# Convert sequences to lists and handle string instructions
agent_handoffs: List[Any] = list(handoffs) if handoffs else []
agent_tools: List[Any] = list(tools) if tools else []
agent_input_guardrails: List[Any] = list(input_guardrails) if input_guardrails else []
agent_output_guardrails: List[Any] = (
list(output_guardrails) if output_guardrails else []
)
# Handle instructions - convert sequence to string if needed
if isinstance(instructions, str):
agent_instructions = instructions
else:
agent_instructions = " ".join(str(instr) for instr in instructions)
self._agent = Agent( self._agent = Agent(
name=name, name=name,
instructions=instructions, instructions=agent_instructions,
handoffs=handoffs or [], handoffs=agent_handoffs,
tools=tools or [], tools=agent_tools,
input_guardrails=input_guardrails or [], input_guardrails=agent_input_guardrails,
output_guardrails=output_guardrails or [], output_guardrails=agent_output_guardrails,
model=model_config.get("model", "gpt-4o") if model_config else "gpt-4o", model=model_config.get("model", "gpt-4o") if model_config else "gpt-4o",
) )
@@ -153,7 +199,8 @@ class OpenAIAgentService(AIService):
logger.info(f"Updated agent instructions for {self._agent.name}") logger.info(f"Updated agent instructions for {self._agent.name}")
if model_config: if model_config:
self._agent.model_config = model_config # Note: OpenAI Agents SDK handles model configuration during agent creation
# We can't update model_config after agent is created, but we can update our model name
if "model" in model_config: if "model" in model_config:
self.set_model_name(model_config["model"]) self.set_model_name(model_config["model"])
logger.info(f"Updated model config for {self._agent.name}") logger.info(f"Updated model config for {self._agent.name}")
@@ -270,8 +317,11 @@ class OpenAIAgentService(AIService):
async for event in result.stream_events(): async for event in result.stream_events():
if event.type == "raw_response_event": if event.type == "raw_response_event":
# Handle token-by-token streaming # Handle token-by-token streaming
if hasattr(event.data, "delta") and event.data.delta: # Only check for delta on events that are known to have it
await self.push_frame(LLMTextFrame(text=event.data.delta)) if hasattr(event.data, "delta") and getattr(event.data, "delta", None):
delta_text = getattr(event.data, "delta", "")
if delta_text:
await self.push_frame(LLMTextFrame(text=delta_text))
elif event.type == "run_item_stream_event": elif event.type == "run_item_stream_event":
# Handle completed items # Handle completed items
@@ -286,10 +336,13 @@ class OpenAIAgentService(AIService):
self._accumulated_text = message_text self._accumulated_text = message_text
elif event.item.type == "tool_call_item": elif event.item.type == "tool_call_item":
logger.debug(f"Tool called: {event.item.tool_name}") # Use getattr for safe attribute access
tool_name = getattr(event.item, "tool_name", "unknown")
logger.debug(f"Tool called: {tool_name}")
elif event.item.type == "tool_call_output_item": elif event.item.type == "tool_call_output_item":
logger.debug(f"Tool output: {event.item.output}") output = getattr(event.item, "output", "no output")
logger.debug(f"Tool output: {output}")
elif event.type == "agent_updated_stream_event": elif event.type == "agent_updated_stream_event":
logger.debug(f"Agent updated: {event.new_agent.name}") logger.debug(f"Agent updated: {event.new_agent.name}")
@@ -352,25 +405,33 @@ class OpenAIAgentService(AIService):
logger.warning(f"Could not extract text from message item: {e}") logger.warning(f"Could not extract text from message item: {e}")
return "" return ""
async def add_tool(self, tool_function: Callable): async def add_tool(self, tool_function: ToolLike):
"""Add a tool function to the agent. """Add a tool function to the agent.
Args: Args:
tool_function: A callable function to add as a tool. tool_function: A callable function or Tool object to add as a tool.
""" """
if hasattr(self._agent, "tools"): if hasattr(self._agent, "tools"):
self._agent.tools.append(tool_function) # Cast to Any to handle the type variance issue
logger.info(f"Added tool {tool_function.__name__} to agent {self._agent.name}") tools_list: List[Any] = self._agent.tools
tools_list.append(tool_function)
tool_name = getattr(
tool_function, "__name__", getattr(tool_function, "name", "unknown")
)
logger.info(f"Added tool {tool_name} to agent {self._agent.name}")
async def add_handoff_agent(self, agent: Agent): async def add_handoff_agent(self, agent: AgentLike):
"""Add a handoff agent. """Add a handoff agent.
Args: Args:
agent: Another Agent instance that this agent can hand off to. agent: Another Agent instance or handoff object that this agent can hand off to.
""" """
if hasattr(self._agent, "handoffs"): if hasattr(self._agent, "handoffs"):
self._agent.handoffs.append(agent) # Cast to Any to handle the type variance issue
logger.info(f"Added handoff agent {agent.name} to agent {self._agent.name}") handoffs_list: List[Any] = self._agent.handoffs
handoffs_list.append(agent)
agent_name = getattr(agent, "name", "unknown")
logger.info(f"Added handoff agent {agent_name} to agent {self._agent.name}")
def get_session_context(self) -> Dict[str, Any]: def get_session_context(self) -> Dict[str, Any]:
"""Get the current session context. """Get the current session context.