Save progress
This commit is contained in:
@@ -18,6 +18,8 @@ Requirements:
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
# Import agents SDK for tools and agent creation
|
||||||
|
from agents import Agent, function_tool
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ from pipecat.pipeline.task import PipelineTask
|
|||||||
from pipecat.runner.types import RunnerArguments
|
from pipecat.runner.types import RunnerArguments
|
||||||
from pipecat.runner.utils import create_transport
|
from pipecat.runner.utils import create_transport
|
||||||
from pipecat.services.cartesia.tts import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.openai_agent.agent_service import OpenAIAgentService
|
from pipecat.services.openai_agent.agent_service import OpenAIAgentService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.daily.transport import DailyParams
|
from pipecat.transports.daily.transport import DailyParams
|
||||||
@@ -37,32 +40,46 @@ load_dotenv(override=True)
|
|||||||
|
|
||||||
# Transport configuration
|
# Transport configuration
|
||||||
transport_params = {
|
transport_params = {
|
||||||
"daily": lambda: DailyParams(audio_out_enabled=True),
|
"daily": lambda: DailyParams(audio_out_enabled=True, audio_in_enabled=True),
|
||||||
"twilio": lambda: FastAPIWebsocketParams(audio_out_enabled=True),
|
"twilio": lambda: FastAPIWebsocketParams(audio_out_enabled=True, audio_in_enabled=True),
|
||||||
"webrtc": lambda: TransportParams(audio_out_enabled=True),
|
"webrtc": lambda: TransportParams(audio_out_enabled=True, audio_in_enabled=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_weather_tool():
|
@function_tool
|
||||||
"""Example tool function for weather information."""
|
def get_weather(location: str) -> str:
|
||||||
|
"""Get the current weather for a location.
|
||||||
|
|
||||||
def get_weather(location: str) -> str:
|
Args:
|
||||||
"""Get the current weather for a location.
|
location: The location to get weather for
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
location: The city or location to get weather for.
|
A weather description string
|
||||||
|
"""
|
||||||
|
# Mock weather data - in real usage, integrate with weather API
|
||||||
|
weather_data = {
|
||||||
|
"San Francisco": "Foggy, 65°F",
|
||||||
|
"New York": "Sunny, 72°F",
|
||||||
|
"London": "Rainy, 59°F",
|
||||||
|
"Tokyo": "Partly cloudy, 68°F",
|
||||||
|
}
|
||||||
|
return weather_data.get(location, f"Weather data not available for {location}")
|
||||||
|
|
||||||
Returns:
|
|
||||||
A weather description string.
|
|
||||||
"""
|
|
||||||
# Simulate weather data
|
|
||||||
conditions = ["sunny", "cloudy", "rainy", "snowy", "windy"]
|
|
||||||
temp = random.randint(-10, 35)
|
|
||||||
condition = random.choice(conditions)
|
|
||||||
|
|
||||||
return f"The weather in {location} is {condition} with a temperature of {temp}°C."
|
@function_tool
|
||||||
|
def get_random_fact() -> str:
|
||||||
|
"""Get a random interesting fact.
|
||||||
|
|
||||||
return get_weather
|
Returns:
|
||||||
|
A random fact string
|
||||||
|
"""
|
||||||
|
facts = [
|
||||||
|
"Honey never spoils. Archaeologists have found edible honey in ancient Egyptian tombs.",
|
||||||
|
"Octopuses have three hearts and blue blood.",
|
||||||
|
"The Great Wall of China isn't visible from space with the naked eye.",
|
||||||
|
"Bananas are berries, but strawberries aren't.",
|
||||||
|
]
|
||||||
|
return random.choice(facts)
|
||||||
|
|
||||||
|
|
||||||
def get_random_fact_tool():
|
def get_random_fact_tool():
|
||||||
@@ -89,6 +106,12 @@ def get_random_fact_tool():
|
|||||||
async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
||||||
logger.info("Starting OpenAI Agent bot")
|
logger.info("Starting OpenAI Agent bot")
|
||||||
|
|
||||||
|
# Set up STT for speech recognition
|
||||||
|
stt = DeepgramSTTService(
|
||||||
|
api_key=os.getenv("DEEPGRAM_API_KEY", ""),
|
||||||
|
model="nova-2",
|
||||||
|
)
|
||||||
|
|
||||||
# Set up TTS for voice output
|
# Set up TTS for voice output
|
||||||
tts = CartesiaTTSService(
|
tts = CartesiaTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY", ""),
|
api_key=os.getenv("CARTESIA_API_KEY", ""),
|
||||||
@@ -97,12 +120,12 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|||||||
|
|
||||||
# Create tools for the agent
|
# Create tools for the agent
|
||||||
tools = [
|
tools = [
|
||||||
get_weather_tool(),
|
get_weather,
|
||||||
get_random_fact_tool(),
|
get_random_fact,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Initialize the OpenAI Agent service
|
# Create the agent with tools
|
||||||
agent_service = OpenAIAgentService(
|
agent = Agent(
|
||||||
name="Assistant",
|
name="Assistant",
|
||||||
instructions="""You are a helpful assistant with access to weather information and random facts.
|
instructions="""You are a helpful assistant with access to weather information and random facts.
|
||||||
You can:
|
You can:
|
||||||
@@ -112,6 +135,11 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|||||||
|
|
||||||
Be friendly, informative, and engaging in your responses.""",
|
Be friendly, informative, and engaging in your responses.""",
|
||||||
tools=tools,
|
tools=tools,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the OpenAI Agent service with the pre-configured agent
|
||||||
|
agent_service = OpenAIAgentService(
|
||||||
|
agent=agent,
|
||||||
api_key=os.getenv("OPENAI_API_KEY"),
|
api_key=os.getenv("OPENAI_API_KEY"),
|
||||||
streaming=True,
|
streaming=True,
|
||||||
)
|
)
|
||||||
@@ -119,9 +147,11 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|||||||
# Create the processing pipeline
|
# Create the processing pipeline
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline(
|
||||||
[
|
[
|
||||||
agent_service,
|
transport.input(), # Receive audio input
|
||||||
tts,
|
stt, # Convert speech to text
|
||||||
transport.output(),
|
agent_service, # Process with OpenAI Agent
|
||||||
|
tts, # Convert text to speech
|
||||||
|
transport.output(), # Send audio output
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ dependencies = [
|
|||||||
"pyloudnorm~=0.1.1",
|
"pyloudnorm~=0.1.1",
|
||||||
"resampy~=0.4.3",
|
"resampy~=0.4.3",
|
||||||
"soxr~=0.5.0",
|
"soxr~=0.5.0",
|
||||||
"openai>=1.74.0,<=1.99.1",
|
"openai>=1.74.0,<2.0.0",
|
||||||
# Pinning numba to resolve package dependencies
|
# Pinning numba to resolve package dependencies
|
||||||
"numba==0.61.2",
|
"numba==0.61.2",
|
||||||
"wait_for2>=0.4.1; python_version<'3.12'",
|
"wait_for2>=0.4.1; python_version<'3.12'",
|
||||||
@@ -74,7 +74,7 @@ langchain = [ "langchain~=0.3.20", "langchain-community~=0.3.20", "langchain-ope
|
|||||||
livekit = [ "livekit~=0.22.0", "livekit-api~=0.8.2", "tenacity>=8.2.3,<10.0.0" ]
|
livekit = [ "livekit~=0.22.0", "livekit-api~=0.8.2", "tenacity>=8.2.3,<10.0.0" ]
|
||||||
lmnt = [ "websockets>=13.1,<15.0" ]
|
lmnt = [ "websockets>=13.1,<15.0" ]
|
||||||
local = [ "pyaudio~=0.2.14" ]
|
local = [ "pyaudio~=0.2.14" ]
|
||||||
mcp = [ "mcp[cli]~=1.9.4" ]
|
mcp = [ "mcp[cli]>=1.11.0,<2.0.0" ]
|
||||||
mem0 = [ "mem0ai~=0.1.94" ]
|
mem0 = [ "mem0ai~=0.1.94" ]
|
||||||
mistral = []
|
mistral = []
|
||||||
mlx-whisper = [ "mlx-whisper~=0.4.2" ]
|
mlx-whisper = [ "mlx-whisper~=0.4.2" ]
|
||||||
@@ -83,8 +83,8 @@ nim = []
|
|||||||
neuphonic = [ "websockets>=13.1,<15.0" ]
|
neuphonic = [ "websockets>=13.1,<15.0" ]
|
||||||
noisereduce = [ "noisereduce~=3.0.3" ]
|
noisereduce = [ "noisereduce~=3.0.3" ]
|
||||||
openai = [ "websockets>=13.1,<15.0" ]
|
openai = [ "websockets>=13.1,<15.0" ]
|
||||||
openai-agent = [ "openai-agents~=1.0.0" ]
|
openai-agent = [ "openai-agents~=0.3.0" ]
|
||||||
openpipe = [ "openpipe~=4.50.0" ]
|
# openpipe = [ "openpipe~=4.50.0" ] # Temporarily disabled due to openai version conflict
|
||||||
openrouter = []
|
openrouter = []
|
||||||
perplexity = []
|
perplexity = []
|
||||||
playht = [ "websockets>=13.1,<15.0" ]
|
playht = [ "websockets>=13.1,<15.0" ]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ 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
|
from typing import Any, Awaitable, Callable, Dict, List, Optional, Union, override
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -108,8 +108,7 @@ class OpenAIAgentService(AIService):
|
|||||||
tools=tools or [],
|
tools=tools or [],
|
||||||
input_guardrails=input_guardrails or [],
|
input_guardrails=input_guardrails or [],
|
||||||
output_guardrails=output_guardrails or [],
|
output_guardrails=output_guardrails or [],
|
||||||
model_config=model_config,
|
model=model_config.get("model", "gpt-4o") if model_config else "gpt-4o",
|
||||||
**kwargs,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._streaming = streaming
|
self._streaming = streaming
|
||||||
@@ -141,7 +140,7 @@ class OpenAIAgentService(AIService):
|
|||||||
instructions: Optional[str] = None,
|
instructions: Optional[str] = None,
|
||||||
model_config: Optional[Dict[str, Any]] = None,
|
model_config: Optional[Dict[str, Any]] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
) -> None:
|
||||||
"""Update agent configuration dynamically.
|
"""Update agent configuration dynamically.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -206,7 +205,8 @@ class OpenAIAgentService(AIService):
|
|||||||
|
|
||||||
await super().cancel(frame)
|
await super().cancel(frame)
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
@override
|
||||||
|
async def process_frame(self, frame: Frame, direction: FrameDirection) -> None:
|
||||||
"""Process frames and handle agent interactions.
|
"""Process frames and handle agent interactions.
|
||||||
|
|
||||||
Processes text input frames by running them through the OpenAI Agent
|
Processes text input frames by running them through the OpenAI Agent
|
||||||
|
|||||||
172
test_openai_agent.py
Normal file
172
test_openai_agent.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Simple test script for OpenAI Agent service."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
# Mock the OpenAI API key for testing
|
||||||
|
os.environ["OPENAI_API_KEY"] = "test-key-for-testing"
|
||||||
|
|
||||||
|
from pipecat.frames.frames import TextFrame
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
|
from pipecat.services.openai_agent import OpenAIAgentService
|
||||||
|
|
||||||
|
|
||||||
|
async def test_basic_functionality():
|
||||||
|
"""Test basic OpenAI Agent service functionality."""
|
||||||
|
print("🧪 Testing OpenAI Agent Service...")
|
||||||
|
|
||||||
|
# Create a simple weather tool for testing
|
||||||
|
def get_weather(location: str) -> str:
|
||||||
|
"""Get weather for a location."""
|
||||||
|
return f"The weather in {location} is sunny and 22°C."
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create the service
|
||||||
|
print("📋 Creating OpenAI Agent service...")
|
||||||
|
service = OpenAIAgentService(
|
||||||
|
name="Test Assistant",
|
||||||
|
instructions="You are a helpful test assistant.",
|
||||||
|
tools=[get_weather],
|
||||||
|
api_key="test-key",
|
||||||
|
streaming=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"✅ Service created successfully!")
|
||||||
|
print(f" - Agent name: {service.agent.name}")
|
||||||
|
print(f" - Model name: {service.model_name}")
|
||||||
|
print(f" - Streaming enabled: {service._streaming}")
|
||||||
|
|
||||||
|
# Test basic configuration
|
||||||
|
print("⚙️ Testing configuration updates...")
|
||||||
|
service.update_agent_config(
|
||||||
|
instructions="Updated test instructions",
|
||||||
|
model_config={"model": "gpt-4o", "temperature": 0.5},
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"✅ Configuration updated!")
|
||||||
|
print(f" - New instructions: {service.agent.instructions}")
|
||||||
|
print(f" - New model: {service.model_name}")
|
||||||
|
|
||||||
|
# Test session context
|
||||||
|
print("💾 Testing session context...")
|
||||||
|
service.update_session_context({"user_id": "test-user", "session": "test-session"})
|
||||||
|
context = service.get_session_context()
|
||||||
|
|
||||||
|
print(f"✅ Session context managed!")
|
||||||
|
print(f" - Context keys: {list(context.keys())}")
|
||||||
|
|
||||||
|
# Test adding tools
|
||||||
|
print("🔧 Testing tool management...")
|
||||||
|
|
||||||
|
def get_time() -> str:
|
||||||
|
"""Get current time."""
|
||||||
|
return "The current time is 3:00 PM."
|
||||||
|
|
||||||
|
await service.add_tool(get_time)
|
||||||
|
print(f"✅ Tool added successfully!")
|
||||||
|
|
||||||
|
print("\n🎉 All basic functionality tests passed!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Test failed with error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def test_frame_processing():
|
||||||
|
"""Test frame processing with mocked responses."""
|
||||||
|
print("\n🔄 Testing frame processing...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Mock the Runner to avoid actual API calls
|
||||||
|
with patch("pipecat.services.openai_agent.agent_service.Runner") as mock_runner:
|
||||||
|
# Set up mock responses
|
||||||
|
mock_stream_result = MagicMock()
|
||||||
|
|
||||||
|
# Mock stream events
|
||||||
|
async def mock_stream_events():
|
||||||
|
# Simulate streaming response
|
||||||
|
yield MagicMock(type="raw_response_event", data=MagicMock(delta="Hello "))
|
||||||
|
yield MagicMock(type="raw_response_event", data=MagicMock(delta="from "))
|
||||||
|
yield MagicMock(type="raw_response_event", data=MagicMock(delta="agent!"))
|
||||||
|
|
||||||
|
# Simulate completed message
|
||||||
|
mock_item = MagicMock()
|
||||||
|
mock_item.type = "message_output_item"
|
||||||
|
mock_item.content = "Hello from agent!"
|
||||||
|
yield MagicMock(type="run_item_stream_event", item=mock_item)
|
||||||
|
|
||||||
|
mock_stream_result.stream_events.return_value = mock_stream_events()
|
||||||
|
mock_runner.run_streamed.return_value = mock_stream_result
|
||||||
|
|
||||||
|
# Create service with mocked runner
|
||||||
|
service = OpenAIAgentService(
|
||||||
|
name="Test Assistant",
|
||||||
|
instructions="You are a helpful test assistant.",
|
||||||
|
api_key="test-key",
|
||||||
|
streaming=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Collect output frames
|
||||||
|
output_frames = []
|
||||||
|
|
||||||
|
async def mock_push_frame(frame, direction=FrameDirection.DOWNSTREAM):
|
||||||
|
output_frames.append(frame)
|
||||||
|
print(f" 📤 Frame: {type(frame).__name__}")
|
||||||
|
if hasattr(frame, "text"):
|
||||||
|
print(f" Text: '{frame.text}'")
|
||||||
|
|
||||||
|
service.push_frame = mock_push_frame
|
||||||
|
|
||||||
|
# Process a text frame
|
||||||
|
print("📝 Processing text frame...")
|
||||||
|
text_frame = TextFrame("Hello, how are you?")
|
||||||
|
await service.process_frame(text_frame, FrameDirection.DOWNSTREAM)
|
||||||
|
|
||||||
|
# Wait for async processing
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
|
||||||
|
print(f"✅ Frame processing completed!")
|
||||||
|
print(f" - Generated {len(output_frames)} output frames")
|
||||||
|
|
||||||
|
# Check if we got expected frame types
|
||||||
|
frame_types = [type(frame).__name__ for frame in output_frames]
|
||||||
|
print(f" - Frame types: {frame_types}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Frame processing test failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
"""Run all tests."""
|
||||||
|
print("🚀 Starting OpenAI Agent Service Tests\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run basic functionality tests
|
||||||
|
basic_test = await test_basic_functionality()
|
||||||
|
|
||||||
|
# Run frame processing tests
|
||||||
|
frame_test = await test_frame_processing()
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print(f"\n📊 Test Results:")
|
||||||
|
print(f" - Basic functionality: {'✅ PASS' if basic_test else '❌ FAIL'}")
|
||||||
|
print(f" - Frame processing: {'✅ PASS' if frame_test else '❌ FAIL'}")
|
||||||
|
|
||||||
|
if basic_test and frame_test:
|
||||||
|
print(f"\n🎉 All tests passed! The OpenAI Agent service is working correctly.")
|
||||||
|
else:
|
||||||
|
print(f"\n⚠️ Some tests failed. Please check the output above.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Test suite failed with error: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
33
test_simple_agent.py
Normal file
33
test_simple_agent.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# Test the actual agents package API
|
||||||
|
try:
|
||||||
|
from agents import Agent, run
|
||||||
|
|
||||||
|
# Create a simple agent
|
||||||
|
agent = Agent(
|
||||||
|
name="test-agent",
|
||||||
|
instructions="You are a helpful assistant.",
|
||||||
|
)
|
||||||
|
|
||||||
|
print("✅ Agent created successfully!")
|
||||||
|
print(f"Agent name: {agent.name}")
|
||||||
|
|
||||||
|
# Test a simple conversation
|
||||||
|
async def test_agent():
|
||||||
|
result = await run(agent, "Hello, how are you?")
|
||||||
|
print(f"Agent response: {result}")
|
||||||
|
|
||||||
|
# Run the test
|
||||||
|
asyncio.run(test_agent())
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
Reference in New Issue
Block a user