diff --git a/README.md b/README.md index bbd4a5a0..3fd5d5a3 100644 --- a/README.md +++ b/README.md @@ -1,180 +1,266 @@ -# OpenAI Agents SDK +# OpenAPI Agents SDK -The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows. +OpenAI's Agents SDK is really cool, but it only supports OpenAI's own API. Let's fix that! This project aims to extend the original framework to support multiple API providers, truly making it an "Open" AI Agents platform. -Image of the Agents Tracing UI +> This project builds upon and extends the [original OpenAI Agents SDK](https://github.com/openai/openai-agents-python/blob/main/README.md). -### Core concepts: -1. [**Agents**](https://openai.github.io/openai-agents-python/agents): LLMs configured with instructions, tools, guardrails, and handoffs -2. [**Handoffs**](https://openai.github.io/openai-agents-python/handoffs/): A specialized tool call used by the Agents SDK for transferring control between agents -3. [**Guardrails**](https://openai.github.io/openai-agents-python/guardrails/): Configurable safety checks for input and output validation -4. [**Tracing**](https://openai.github.io/openai-agents-python/tracing/): Built-in tracking of agent runs, allowing you to view, debug and optimize your workflows +## Project Vision -Explore the [examples](examples) directory to see the SDK in action, and read our [documentation](https://openai.github.io/openai-agents-python/) for more details. +The OpenAPI Agents SDK is a powerful framework designed to achieve an ambitious goal: **enabling any API that conforms to the OpenAPI specification to be utilized by intelligent agents**. Through this framework, we can: -Notably, our SDK [is compatible](https://openai.github.io/openai-agents-python/models/) with any model providers that support the OpenAI Chat Completions API format. +- **Unified Integration**: Bring diverse APIs into the Agent framework +- **Intelligent Interaction**: Enable AI models to understand API capabilities and limitations, automatically calling appropriate endpoints +- **Multi-Model Support**: Support OpenAI, local models, and third-party AI services +- **Process Orchestration**: Solve complex problems through multi-Agent collaboration -## Get started +Our extensions make this framework no longer limited to OpenAI's cloud-based models, but support various local and third-party models, providing broader possibilities for API integration. -1. Set up your Python environment +## Current Progress -``` -python -m venv env -source env/bin/activate -``` +We have tested integration with multiple API providers: -2. Install Agents SDK +**Azure OpenAI API** integration has been implemented perfectly. Most examples run successfully with the Azure API. +Only the Responses API examples fail, as Azure API doesn't currently support the responses API. -``` -pip install openai-agents -``` +We've also implemented integration with **Ollama**, with some basic examples running successfully using the Ollama API. However, some examples (like handoffs) fail when using the Ollama API. +Other advanced examples are too challenging for the Ollama API to pass. -For voice support, install with the optional `voice` group: `pip install 'openai-agents[voice]'`. +### Examples passed with Ollama +examples_OpenAPI/ollama/basic/hello_world.py +examples_OpenAPI/ollama/basic/hello_world_jupyter.py +examples_OpenAPI/ollama/basic/hello_world.py +examples_OpenAPI/ollama/basic/failed/agent_lifecycle_example.py +### Examples failed with Ollama +examples_OpenAPI/ollama/basic/failed/lifecycle_example.py -## Hello world example +## Core Concepts + +1. [**Agents**](https://openai.github.io/openai-agents-python/agents): LLMs configured with instructions, tools, guardrails, and handoffs +2. [**Handoffs**](https://openai.github.io/openai-agents-python/handoffs/): A specialized tool call used by the Agents SDK for transferring control between agents +3. [**Guardrails**](https://openai.github.io/openai-agents-python/guardrails/): Configurable safety checks for input and output validation +4. [**Tracing**](https://openai.github.io/openai-agents-python/tracing/): Built-in tracking of agent runs, allowing you to view, debug and optimize your workflows +5. [**Model Providers**](https://openai.github.io/openai-agents-python/ollama_integration): Support for multiple model providers including OpenAI and Ollama +## Using Azure OpenAI Service + +Azure OpenAI provides enterprise-grade security and compliance features. Integrating with Azure is simple: ```python -from agents import Agent, Runner +import asyncio +import sys, os -agent = Agent(name="Assistant", instructions="You are a helpful assistant") +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) -result = Runner.run_sync(agent, "Write a haiku about recursion in programming.") -print(result.final_output) +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory -# Code within the code, -# Functions calling themselves, -# Infinite loop's dance. +async def main(): + # Create Azure OpenAI settings + azure_settings = ModelSettings( + provider="azure_openai", + azure_endpoint="https://your-resource-name.openai.azure.com", + azure_api_key="your-azure-api-key", + azure_api_version="2024-02-15-preview", + azure_deployment="your-deployment-name" + ) + + # Create run configuration + run_config = RunConfig() + run_config.model_provider = ModelProviderFactory.create_provider(azure_settings) + + # Create Agent instance + agent = Agent( + name="AzureAssistant", + instructions="You are a helpful assistant running on Azure.", + model_settings=azure_settings + ) + + # Run Agent + result = await Runner.run( + agent, + "Explain the advantages of using Azure OpenAI.", + run_config=run_config + ) + + # Print results + print(result.final_output) + +if __name__ == "__main__": + asyncio.run(main()) ``` -(_If running this, ensure you set the `OPENAI_API_KEY` environment variable_) +For more details, please refer to our [Azure Integration Documentation](docs/azure_integration.md). -(_For Jupyter notebook users, see [hello_world_jupyter.py](examples/basic/hello_world_jupyter.py)_) +## Using Local Ollama Models -## Handoffs example +This is our key extension. Using local Ollama models is straightforward: ```python -from agents import Agent, Runner import asyncio +import sys, os -spanish_agent = Agent( - name="Spanish agent", - instructions="You only speak Spanish.", -) - -english_agent = Agent( - name="English agent", - instructions="You only speak English", -) - -triage_agent = Agent( - name="Triage agent", - instructions="Handoff to the appropriate agent based on the language of the request.", - handoffs=[spanish_agent, english_agent], -) +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory async def main(): - result = await Runner.run(triage_agent, input="Hola, ¿cómo estás?") + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", # Specify Ollama as the provider + ollama_base_url="http://localhost:11434", # Ollama service address + ollama_default_model="llama3.2", # Use llama3.2 model + temperature=0.7 # Optional: control creativity + ) + + # Create run configuration + run_config = RunConfig() + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + # Create Agent instance + agent = Agent( + name="Assistant", + instructions="You are a helpful assistant.", + model_settings=ollama_settings # Use Ollama settings + ) + + # Run Agent + result = await Runner.run( + agent, + "Write a Python function to calculate the Fibonacci sequence.", + run_config=run_config + ) + + # Print results print(result.final_output) - # ¡Hola! Estoy bien, gracias por preguntar. ¿Y tú, cómo estás? - if __name__ == "__main__": asyncio.run(main()) ``` -## Functions example - -```python -import asyncio +For more details, please refer to our [Ollama Integration Documentation](docs/ollama_integration.md). -from agents import Agent, Runner, function_tool +## Getting Started -@function_tool -def get_weather(city: str) -> str: - return f"The weather in {city} is sunny." +1. Set up your Python environment +``` +python -m venv env +source env/bin/activate +``` -agent = Agent( - name="Hello world", - instructions="You are a helpful agent.", - tools=[get_weather], -) +2. Install the Agents SDK +``` +pip install -e . +``` -async def main(): - result = await Runner.run(agent, input="What's the weather in Tokyo?") - print(result.final_output) - # The weather in Tokyo is sunny. +For Ollama support, ensure you have Ollama installed and running: +1. Download and install Ollama from [ollama.ai](https://ollama.ai) +2. Run `ollama serve` to start the service +3. Pull the required model, e.g., `ollama pull llama3.2` +## Project Structure -if __name__ == "__main__": - asyncio.run(main()) -``` +- **`src/`**: Core source code + - **`agents/`**: Main implementation of the Agent framework + - **`agents/models/`**: Implementations of different model providers +- **`examples_OpenAPI/`**: OpenAPI integration examples + - **`ollama/`**: Ollama integration examples + - **`azure/`**: Azure OpenAI integration examples +- **`docs/`**: Project documentation + - **`ollama_integration.md`**: Detailed documentation on Ollama integration + - **`azure_integration.md`**: Detailed documentation on Azure integration + - **`ollama_API/`**: Ollama API reference -## The agent loop +## Future Plans -When you call `Runner.run()`, we run a loop until we get a final output. +We plan to continue expanding this framework to support more APIs and model providers: -1. We call the LLM, using the model and settings on the agent, and the message history. -2. The LLM returns a response, which may include tool calls. -3. If the response has a final output (see below for more on this), we return it and end the loop. -4. If the response has a handoff, we set the agent to the new agent and go back to step 1. -5. We process the tool calls (if any) and append the tool responses messages. Then we go to step 1. +- **Next immediate steps**: + - **Azure AI Integration**: Add support for Azure OpenAI Service to enable enterprise-grade AI capabilities with Azure's security and compliance features + - **AWS Bedrock Integration**: Implement integration with AWS Bedrock to support models like Claude, Titan, and others in the AWS ecosystem -There is a `max_turns` parameter that you can use to limit the number of times the loop executes. +- Additional roadmap items: + - Add automatic integration for more OpenAPI specification APIs + - Implement streaming response support + - Improve tool call compatibility across different models + - Add more model providers (e.g., Anthropic, Gemini, etc.) -### Final output +## Contribution Guidelines -Final output is the last thing the agent produces in the loop. +We welcome and encourage community contributions to help make OpenAI Agent truly "Open"! Here's how you can participate: -1. If you set an `output_type` on the agent, the final output is when the LLM returns something of that type. We use [structured outputs](https://platform.openai.com/docs/guides/structured-outputs) for this. -2. If there's no `output_type` (i.e. plain text responses), then the first LLM response without any tool calls or handoffs is considered as the final output. +### How to Contribute -As a result, the mental model for the agent loop is: +1. **Fork and Clone the Repository** + ```bash + git clone https://github.com/your-username/openAPI-agents-python.git + cd openAPI-agents-python + ``` -1. If the current agent has an `output_type`, the loop runs until the agent produces structured output matching that type. -2. If the current agent does not have an `output_type`, the loop runs until the current agent produces a message without any tool calls/handoffs. +2. **Create a New Branch** + ```bash + git checkout -b feature/your-feature-name + ``` -## Common agent patterns +3. **Implement Your Changes** + - Add support for new API providers + - Fix issues in existing integrations + - Improve documentation + - Add more examples -The Agents SDK is designed to be highly flexible, allowing you to model a wide range of LLM workflows including deterministic flows, iterative loops, and more. See examples in [`examples/agent_patterns`](examples/agent_patterns). +4. **Test Your Changes** + - Ensure existing tests pass + - Add tests for new features -## Tracing +5. **Commit and Push Your Changes** + ```bash + git commit -m "Add support for XYZ API provider" + git push origin feature/your-feature-name + ``` -The Agents SDK automatically traces your agent runs, making it easy to track and debug the behavior of your agents. Tracing is extensible by design, supporting custom spans and a wide variety of external destinations, including [Logfire](https://logfire.pydantic.dev/docs/integrations/llms/openai/#openai-agents), [AgentOps](https://docs.agentops.ai/v1/integrations/agentssdk), [Braintrust](https://braintrust.dev/docs/guides/traces/integrations#openai-agents-sdk), [Scorecard](https://docs.scorecard.io/docs/documentation/features/tracing#openai-agents-sdk-integration), and [Keywords AI](https://docs.keywordsai.co/integration/development-frameworks/openai-agent). For more details about how to customize or disable tracing, see [Tracing](http://openai.github.io/openai-agents-python/tracing), which also includes a larger list of [external tracing processors](http://openai.github.io/openai-agents-python/tracing/#external-tracing-processors-list). +6. **Create a Pull Request** + - Provide a clear PR description + - Explain what problem your changes solve -## Development (only needed if you need to edit the SDK/examples) +### Contribution Areas -0. Ensure you have [`uv`](https://docs.astral.sh/uv/) installed. +You can contribute in the following areas: -```bash -uv --version -``` +1. **New API Provider Support** + - Add support for Anthropic, Gemini, Cohere, etc. + - Implement cloud service provider integrations like AWS Bedrock, Google Vertex AI -1. Install dependencies +2. **Existing Integration Improvements** + - Improve Ollama integration compatibility + - Optimize Azure OpenAI integration performance -```bash -make sync -``` +3. **Documentation Enhancement** + - Write more detailed API integration guides + - Create more examples and tutorials + - Translate documentation -2. (After making changes) lint/test +4. **Bug Fixes and Feature Requests** + - Report issues or suggest new features + - Fix existing issues -``` -make tests # run tests -make mypy # run typechecker -make lint # run linter -``` +We believe that through community effort, we can build a truly open Agent framework that allows any API to leverage the intelligent capabilities of Agent technology. ## Acknowledgements -We'd like to acknowledge the excellent work of the open-source community, especially: +We would like to thank the open-source community for their outstanding work, especially: -- [Pydantic](https://docs.pydantic.dev/latest/) (data validation) and [PydanticAI](https://ai.pydantic.dev/) (advanced agent framework) -- [MkDocs](https://github.com/squidfunk/mkdocs-material) -- [Griffe](https://github.com/mkdocstrings/griffe) -- [uv](https://github.com/astral-sh/uv) and [ruff](https://github.com/astral-sh/ruff) +- [OpenAI](https://openai.com/) (original Agents SDK) +- [Ollama](https://ollama.ai/) (local LLM runtime framework) +- [Pydantic](https://docs.pydantic.dev/latest/) (data validation) and [PydanticAI](https://ai.pydantic.dev/) (advanced agent framework) +- [MkDocs](https://github.com/squidfunk/mkdocs-material) +- [Griffe](https://github.com/mkdocstrings/griffe) -We're committed to continuing to build the Agents SDK as an open source framework so others in the community can expand on our approach. +Join us in making OpenAI Agent more "Open"! diff --git a/docs/azure_integration.md b/docs/azure_integration.md new file mode 100644 index 00000000..e813998f --- /dev/null +++ b/docs/azure_integration.md @@ -0,0 +1,307 @@ +# Azure OpenAI Integration + +This document provides detailed guidance on integrating Azure OpenAI Service with the OpenAPI Agents SDK. + +## Overview + +Azure OpenAI Service provides enterprise-grade security, compliance, and regional availability for OpenAI's powerful language models. This integration allows you to leverage Azure's robust platform while maintaining compatibility with the Agents SDK framework. + +## Key Benefits + +- **Enterprise-grade Security**: Benefit from Azure's comprehensive security features, including Private Link, Managed Identity, and more +- **Regional Availability**: Deploy and run models in regions that comply with your data residency requirements +- **Compliance**: Take advantage of Microsoft's compliance certifications and commitments +- **Seamless Integration**: Use the Agents SDK with minimal changes to your existing code + +## Setup Requirements + +Before starting, ensure you have: + +1. An Azure subscription +2. Access to Azure OpenAI Service (requires approval) +3. One or more deployed model endpoints in your Azure OpenAI resource + +## Configuration + +### Step 1: Create Azure OpenAI Resources + +1. Create an Azure OpenAI resource in the [Azure Portal](https://portal.azure.com) +2. Deploy models through the Azure OpenAI Studio +3. Note your API endpoint, key, and deployment names + +### Step 2: Configure the SDK + +```python +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", + azure_endpoint="https://your-resource-name.openai.azure.com", + azure_api_key="your-azure-api-key", + azure_api_version="2024-02-15-preview", # Use appropriate API version + azure_deployment="your-deployment-name", # Maps to a model in Azure + temperature=0.7 +) + +# Create run configuration +run_config = RunConfig() +run_config.model_provider = ModelProviderFactory.create_provider(azure_settings) + +# Create Agent instance +agent = Agent( + name="AzureAssistant", + instructions="You are a helpful assistant running on Azure.", + model_settings=azure_settings +) +``` + +## Usage Examples + +### Basic Chat Completion + +```python +import asyncio + +async def main(): + # Setup agent with Azure settings (as shown above) + + # Run the agent + result = await Runner.run( + agent, + "Explain the benefits of using Azure OpenAI Service.", + run_config=run_config + ) + + print(result.final_output) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +### Using Tools with Azure OpenAI + +Azure OpenAI supports tool calling with compatible models (e.g., GPT-4 and GPT-3.5-Turbo): + +```python +import asyncio +from pydantic import BaseModel, Field +from typing import List + +# Define a tool using Pydantic +class WeatherTool(BaseModel): + """Get the current weather for a location""" + location: str = Field(..., description="City and state, e.g., San Francisco, CA") + + def get_weather(self) -> str: + # This would be a real API call in production + return f"It's sunny and 72°F in {self.location}" + +async def main(): + # Setup agent with Azure settings + + # Add tool to agent + agent.add_tool( + "get_weather", + WeatherTool, + WeatherTool.get_weather + ) + + # Run agent + result = await Runner.run( + agent, + "What's the weather in Seattle?", + run_config=run_config + ) + + print(result.final_output) +``` + +## Supported Features + +Azure OpenAI integration supports the following features: + +| Feature | Support Status | Notes | +|---------|----------------|-------| +| Chat Completions | ✅ Full | Compatible with all models | +| Tool Calling | ✅ Full | Compatible with newer models | +| Function Calling | ✅ Full | Legacy feature, use Tool Calling | +| JSON Mode | ✅ Full | Available with compatible models | +| Vision Analysis | ✅ Full | With GPT-4 Vision models | +| Stream Mode | ✅ Full | Works with all supported models | +| Multi-Agent Handoffs | ✅ Full | Fully supported | +| Response Format Control | ✅ Full | JSON/text output control | +| Responses API | ❌ Not Available | Not currently supported by Azure | + +## Common Issues and Solutions + +### Issue: Authentication Errors + +**Solution**: Ensure your API key is correct and that your deployment is active. Check that your resource has sufficient quota. + +### Issue: Model Not Found + +**Solution**: Verify deployment name in your Azure settings. Ensure model is deployed in your Azure OpenAI Studio. + +### Issue: Rate Limiting + +**Solution**: Implement retry logic with exponential backoff. Consider requesting quota increases for production workloads. + +### Issue: Tool Calling Not Working + +**Solution**: Ensure you're using a model version that supports tool calling (e.g., GPT-4 or GPT-3.5-Turbo). Check that your tool definitions follow the required format. + +## Advanced Configuration + +### Regional Endpoints + +Azure OpenAI is available in multiple regions. Configure your endpoint accordingly: + +```python +azure_settings = ModelSettings( + provider="azure_openai", + azure_endpoint="https://your-resource-name.eastus.api.cognitive.microsoft.com", + # Other settings as above +) +``` + +### Private Link Support + +For enhanced security, configure Private Link: + +```python +azure_settings = ModelSettings( + provider="azure_openai", + azure_endpoint="https://your-private-link-endpoint.com", + # Other settings as above +) +``` + +### Managed Identity Authentication + +Instead of API keys, use Azure Managed Identity: + +```python +azure_settings = ModelSettings( + provider="azure_openai", + azure_endpoint="https://your-resource-name.openai.azure.com", + azure_ad_token_provider=get_managed_identity_token, # Function that returns token + azure_api_version="2024-02-15-preview" +) +``` + +## Performance Optimizations + +1. **Co-locate Resources**: Deploy your application in the same region as your Azure OpenAI resource +2. **Use Connection Pooling**: For high-volume applications +3. **Implement Caching**: For repeated or similar requests +4. **Right-size Context**: Minimize token usage by sending only necessary context + +## Examples Repository + +For complete working examples, see the `examples_OpenAPI/azure` directory in this repository. + +## Additional Resources + +- [Azure OpenAI Documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/) +- [Azure OpenAI Pricing](https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/) +- [Azure OpenAI Quotas and Limits](https://learn.microsoft.com/en-us/azure/ai-services/openai/quotas-limits) +- [Azure OpenAI REST API Reference](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference) +- [Azure Security Best Practices](https://learn.microsoft.com/en-us/azure/security/fundamentals/best-practices-and-patterns) + +## Comparing with OpenAI API + +### Key Differences + +| Feature | Azure OpenAI | OpenAI API | +|---------|-------------|------------| +| Authentication | API Key, Microsoft Entra ID | API Key only | +| Regional Deployment | Multiple regions available | Fixed endpoints | +| Rate Limiting | Per deployment, adjustable | Account-wide | +| Compliance | Enterprise compliance certifications | Consumer-focused | +| Cost Management | Azure subscription integrations | Direct billing | +| Network Security | Private Link, VNet integration | Public endpoints | +| Responses API | Not currently available | Available | + +### When to Choose Azure OpenAI + +- Enterprise applications requiring compliance certifications +- Applications with strict data residency requirements +- Systems needing enhanced security features +- Organizations already invested in Azure ecosystem +- Scenarios requiring fine-grained access control + +## Multi-Provider Strategy + +You can implement a multi-provider strategy to use both Azure OpenAI and other providers in your application: + +```python +# Configure providers +azure_settings = ModelSettings( + provider="azure_openai", + azure_endpoint="https://your-resource-name.openai.azure.com", + azure_api_key="your-azure-api-key", + azure_api_version="2024-02-15-preview", + azure_deployment="gpt-4" +) + +openai_settings = ModelSettings( + provider="openai", + api_key="your-openai-api-key", + model="gpt-4" +) + +# Create agents for different purposes +azure_agent = Agent( + name="EnterpriseAssistant", + instructions="You handle enterprise inquiries securely.", + model_settings=azure_settings +) + +openai_agent = Agent( + name="GeneralAssistant", + instructions="You handle general public inquiries.", + model_settings=openai_settings +) + +# Use different agents based on your requirements +``` + +## Migration from OpenAI API + +If you're migrating from the OpenAI API to Azure OpenAI, here are the key changes: + +1. Update model settings to use `provider="azure_openai"` +2. Add Azure-specific configuration (endpoint, deployment name, etc.) +3. Update model references from model names (e.g., "gpt-4") to deployment names +4. Implement Azure authentication methods +5. Review and update rate limiting and retry strategies + +Example migration: + +```python +# Before: OpenAI API +openai_settings = ModelSettings( + provider="openai", + api_key="your-openai-api-key", + model="gpt-4" +) + +# After: Azure OpenAI +azure_settings = ModelSettings( + provider="azure_openai", + azure_endpoint="https://your-resource-name.openai.azure.com", + azure_api_key="your-azure-api-key", + azure_api_version="2024-02-15-preview", + azure_deployment="gpt4-deployment" # Name of your GPT-4 deployment in Azure +) +``` + +## Conclusion + +Azure OpenAI Service offers a robust, enterprise-ready platform for deploying AI applications with the OpenAPI Agents SDK. This integration combines the power of OpenAI's models with Azure's enterprise capabilities, providing a secure and compliant solution for organizations of all sizes. + +By following this guide, you should be able to successfully integrate your Agents SDK applications with Azure OpenAI Service, taking advantage of both the flexibility of the SDK and the enterprise features of Azure. diff --git a/docs/ollama_integration.md b/docs/ollama_integration.md new file mode 100644 index 00000000..f97ca695 --- /dev/null +++ b/docs/ollama_integration.md @@ -0,0 +1,157 @@ +# Ollama Integration + +This documentation covers how to use the Ollama integration with the Agents SDK to run local LLMs with your agent applications. + +## Overview + +Ollama is an open-source framework that makes it easy to run large language models locally. This integration allows you to use local Ollama models with the OpenAI Agents SDK, providing an alternative to cloud-based LLMs when needed. + +Key benefits: +- **Privacy**: All data stays on your machine +- **Cost**: No usage fees for inference +- **Flexibility**: Use various open-source models +- **OpenAI API Compatible**: Uses Ollama's OpenAI-compatible API endpoints + +## Setup Requirements + +1. Install Ollama from [ollama.ai](https://ollama.ai) +2. Start the Ollama service by running `ollama serve` +3. Pull your desired model (e.g., `ollama pull phi4:latest`) + +## Configuration + +The Ollama provider is configured using `ModelSettings`: + +```python +from src.agents.model_settings import ModelSettings + +# Create Ollama model settings +ollama_settings = ModelSettings( + provider="ollama", # Specify Ollama as the provider + ollama_base_url="http://localhost:11434", # Ollama service address + ollama_default_model="llama3.2", # Model to use + temperature=0.7 # Optional: control creativity +) +``` + +## Using with Agents + +To create an agent that uses Ollama: + +```python +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +# Create model settings +ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2" +) + +# Create an agent with Ollama +agent = Agent( + name="OllamaAssistant", + instructions="You are a helpful assistant.", + model_settings=ollama_settings +) +``` + +## Running Agents with Ollama + +To run an agent with the Ollama provider: + +```python +from src.agents import Runner +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +# Create a run configuration with Ollama provider +run_config = RunConfig() +run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + +# Run the agent with the custom provider +result = await Runner.run(agent, "Tell me a joke", run_config=run_config) +print(result.final_output) +``` + +## Complete Example + +Here's a complete example of using the Ollama integration: + +```python +import asyncio +import os, sys + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +async def main(): + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2", + temperature=0.7 + ) + + # Create run configuration + run_config = RunConfig() + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + # Create Agent instance + agent = Agent( + name="Assistant", + instructions="You only respond in haikus.", + model_settings=ollama_settings + ) + + # Run Agent + print("Running Agent, please wait...") + result = await Runner.run( + agent, + "Tell me about recursion in programming.", + run_config=run_config + ) + + # Print results + print("\nResult:") + print(result.final_output) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Compatibility Notes + +The Ollama integration uses Ollama's OpenAI-compatible API endpoints, which implement the Chat Completions API format. This allows for a smooth transition between cloud-based OpenAI models and local Ollama models. + +Current limitations: +- Streaming responses are not yet implemented +- Tool calls may have limited support depending on the model +- Not all models support structured outputs or JSON mode + +## Supported Models + +You can use any model available in Ollama, including: +- Llama 3 +- Mistral +- Phi4 +- Gemma +- And many more + +To see available models, run `ollama list` in your terminal. + +## Troubleshooting + +If you encounter issues: + +1. Ensure Ollama service is running (`ollama serve`) +2. Check that you've pulled the model you're trying to use +3. Verify the URL and port in `ollama_base_url` (default: `http://localhost:11434`) +4. Check model compatibility - not all models support all features diff --git a/examples_OpenAPI/azure/README.md b/examples_OpenAPI/azure/README.md new file mode 100644 index 00000000..b9e14954 --- /dev/null +++ b/examples_OpenAPI/azure/README.md @@ -0,0 +1,57 @@ +# Azure OpenAI Examples + +This directory contains examples demonstrating how to use the Agents SDK with Azure OpenAI service. The examples cover a variety of agent patterns and use cases. + +## Basic Examples + +- **hello_world.py**: Simple "Hello World" example using Azure OpenAI +- **hello_world_jupyter.py**: Example for use in Jupyter notebooks +- **tools.py**: Using function tools with Azure OpenAI +- **stream_text.py**: Streaming text responses from Azure OpenAI +- **stream_items.py**: Streaming various item types from Azure OpenAI +- **dynamic_system_prompt.py**: Dynamic system prompts with Azure OpenAI +- **life_cycle_example.py**: Agent lifecycle hooks with Azure OpenAI +- **agent_lifecycle_example.py**: More advanced agent lifecycle examples + +## Agent Patterns + +These examples showcase common agent patterns adapted for Azure OpenAI: + +- **parallelization.py**: Running agents in parallel and selecting best results +- **agents_as_tools.py**: Using agents as tools for other agents +- **deterministic.py**: Sequential multi-step workflows +- **forcing_tool_use.py**: Forcing agents to use specific tools +- **input_guardrails.py**: Adding guardrails for agent inputs +- **llm_as_a_judge.py**: Using one agent to evaluate another's output +- **output_guardrails.py**: Adding guardrails for agent outputs +- **routing.py**: Routing user requests to specialized agents + +## Advanced Examples + +- **research_bot/**: Multi-agent system for web-based research +- **financial_research_agent/**: Financial analysis agent system +- **voice/**: Voice interaction examples using Azure OpenAI + +## Getting Started + +To run these examples, you'll need Azure OpenAI credentials. Set the following environment variables: + +```bash +export AZURE_OPENAI_API_KEY="your_api_key" +export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com" +export AZURE_OPENAI_API_VERSION="2023-05-15" # Optional +export AZURE_OPENAI_DEPLOYMENT="deployment_name" # Optional +``` + +Then run any example using Python: + +```bash +python -m examples_OpenAPI.azure.basic.hello_world +``` + +## Additional Resources + +For more information on using Azure OpenAI service, refer to: +- [Azure OpenAI Documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/) +- [Azure OpenAI Service Pricing](https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/) +- [Azure OpenAI Service Quotas and Limits](https://learn.microsoft.com/en-us/azure/ai-services/openai/quotas-limits) diff --git a/examples_OpenAPI/azure/agent_patterns/agents_as_tools.py b/examples_OpenAPI/azure/agent_patterns/agents_as_tools.py new file mode 100644 index 00000000..766e0a87 --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/agents_as_tools.py @@ -0,0 +1,116 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, ItemHelpers, MessageOutputItem, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates the agents-as-tools pattern using Azure OpenAI service. +The frontline agent receives a user message and then selects which agents to call as tools. +In this case, it chooses from a set of translation agents. +""" + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +spanish_agent = Agent( + name="spanish_agent", + instructions="You translate the user's message to Spanish", + handoff_description="An english to spanish translator", + model_settings=azure_settings, +) + +french_agent = Agent( + name="french_agent", + instructions="You translate the user's message to French", + handoff_description="An english to french translator", + model_settings=azure_settings, +) + +italian_agent = Agent( + name="italian_agent", + instructions="You translate the user's message to Italian", + handoff_description="An english to italian translator", + model_settings=azure_settings, +) + +orchestrator_agent = Agent( + name="orchestrator_agent", + instructions=( + "You are a translation agent. You use the tools given to you to translate." + "If asked for multiple translations, you call the relevant tools in order." + "You never translate on your own, you always use the provided tools." + ), + model_settings=azure_settings, + tools=[ + spanish_agent.as_tool( + tool_name="translate_to_spanish", + tool_description="Translate the user's message to Spanish", + ), + french_agent.as_tool( + tool_name="translate_to_french", + tool_description="Translate the user's message to French", + ), + italian_agent.as_tool( + tool_name="translate_to_italian", + tool_description="Translate the user's message to Italian", + ), + ], +) + +synthesizer_agent = Agent( + name="synthesizer_agent", + instructions="You inspect translations, correct them if needed, and produce a final concatenated response.", + model_settings=azure_settings, +) + + +async def main(): + msg = input("Hi! What would you like translated, and to which languages? ") + + # Run the entire orchestration in a single trace + with trace("Orchestrator evaluator"): + orchestrator_result = await Runner.run(orchestrator_agent, msg, run_config=run_config) + + for item in orchestrator_result.new_items: + if isinstance(item, MessageOutputItem): + text = ItemHelpers.text_message_output(item) + if text: + print(f" - Translation step: {text}") + + synthesizer_result = await Runner.run( + synthesizer_agent, orchestrator_result.to_input_list(), run_config=run_config + ) + + print(f"\n\nFinal response:\n{synthesizer_result.final_output}") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Agents as Tools Example") + print("===================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/agent_patterns/deterministic.py b/examples_OpenAPI/azure/agent_patterns/deterministic.py new file mode 100644 index 00000000..9e8cafaa --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/deterministic.py @@ -0,0 +1,139 @@ +import asyncio +import sys +import os +from typing import List + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel, Field + +from src.agents import Agent, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates a deterministic flow pattern using Azure OpenAI service. +We break down a story generation task into a series of smaller steps, each performed by an agent. +The output of one agent is used as input to the next. +""" + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + + +# Define output types for each step +class StoryOutlineOutput(BaseModel): + setting: str = Field(description="The setting of the story") + characters: List[str] = Field(description="Main characters in the story") + plot_points: List[str] = Field(description="Key plot points") + + +class StoryDraftOutput(BaseModel): + story_title: str = Field(description="The title of the story") + story_draft: str = Field(description="The draft of the story") + + +class StoryFinalOutput(BaseModel): + final_story: str = Field(description="The final polished story") + moral: str = Field(description="The moral of the story, if any") + + +# Create agents for each step +outline_agent = Agent( + name="OutlineCreator", + instructions="You create detailed story outlines. Include setting, characters, and plot points.", + output_type=StoryOutlineOutput, + model_settings=azure_settings, +) + +draft_agent = Agent( + name="StoryDrafter", + instructions="You write a draft story based on the provided outline. Keep it engaging and well-structured.", + output_type=StoryDraftOutput, + model_settings=azure_settings, +) + +final_agent = Agent( + name="StoryFinalizer", + instructions="You polish the story draft, improve language, add descriptions, and provide a moral if appropriate.", + output_type=StoryFinalOutput, + model_settings=azure_settings, +) + + +async def main(): + # Get a genre from the user + genre = input("Enter a genre for your story (e.g., fantasy, mystery, sci-fi): ") + + # Run the entire flow in a single trace + with trace("Story Generation Process"): + print("\nCreating story outline...") + outline_result = await Runner.run( + outline_agent, + f"Create a {genre} story outline.", + run_config=run_config, + ) + + outline = outline_result.final_output_as(StoryOutlineOutput) + print(f"\nOutline created:") + print(f"Setting: {outline.setting}") + print(f"Characters: {', '.join(outline.characters)}") + print(f"Plot Points: {', '.join(outline.plot_points)}") + + print("\nDrafting the story...") + outline_prompt = ( + f"Setting: {outline.setting}\n" + f"Characters: {', '.join(outline.characters)}\n" + f"Plot Points: {', '.join(outline.plot_points)}\n\n" + f"Write a {genre} story draft based on this outline." + ) + + draft_result = await Runner.run( + draft_agent, + outline_prompt, + run_config=run_config, + ) + + draft = draft_result.final_output_as(StoryDraftOutput) + print(f"\nDraft created: {draft.story_title}") + + print("\nFinalizing the story...") + final_result = await Runner.run( + final_agent, + f"Here's a draft story to polish:\n\n{draft.story_draft}", + run_config=run_config, + ) + + final_story = final_result.final_output_as(StoryFinalOutput) + + print("\n==== FINAL STORY ====") + print(f"\n{draft.story_title}\n") + print(final_story.final_story) + print(f"\nMoral: {final_story.moral}") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Deterministic Flow Example") + print("=====================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/agent_patterns/forcing_tool_use.py b/examples_OpenAPI/azure/agent_patterns/forcing_tool_use.py new file mode 100644 index 00000000..b86e2ec8 --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/forcing_tool_use.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +import asyncio +import sys +import os +from typing import Any, Literal + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel + +from src.agents import ( + Agent, + FunctionToolResult, + RunContextWrapper, + Runner, + ToolsToFinalOutputFunction, + ToolsToFinalOutputResult, + function_tool, +) +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to force the agent to use a tool with Azure OpenAI service. +It uses `ModelSettings(tool_choice="required")` to force the agent to use any tool. + +You can run it with 3 options: +1. `default`: The default behavior, which is to send the tool output to the LLM. In this case, + `tool_choice` is not set, because otherwise it would result in an infinite loop - the LLM would + call the tool, the tool would run and send the results to the LLM, and that would repeat + (because the model is forced to use a tool every time.) +2. `first_tool_result`: The first tool result is used as the final output. +3. `custom`: A custom tool use behavior function is used. The custom function receives all the tool + results, and chooses to use the first tool result to generate the final output. + +Usage: +python examples_OpenAPI/azure/agent_patterns/forcing_tool_use.py -t default +python examples_OpenAPI/azure/agent_patterns/forcing_tool_use.py -t first_tool +python examples_OpenAPI/azure/agent_patterns/forcing_tool_use.py -t custom +""" + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings (will be used in main function) +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + + +class Weather(BaseModel): + city: str + temperature_range: str + conditions: str + + +@function_tool +def get_weather(city: str) -> Weather: + print("[debug] get_weather called") + return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind") + + +async def custom_tool_use_behavior( + context: RunContextWrapper[Any], results: list[FunctionToolResult] +) -> ToolsToFinalOutputResult: + weather: Weather = results[0].output + return ToolsToFinalOutputResult( + is_final_output=True, final_output=f"{weather.city} is {weather.conditions}." + ) + + +async def main(tool_use_behavior: Literal["default", "first_tool", "custom"] = "default"): + if tool_use_behavior == "default": + behavior: Literal["run_llm_again", "stop_on_first_tool"] | ToolsToFinalOutputFunction = ( + "run_llm_again" + ) + elif tool_use_behavior == "first_tool": + behavior = "stop_on_first_tool" + elif tool_use_behavior == "custom": + behavior = custom_tool_use_behavior + + # Create agent with Azure settings + agent = Agent( + name="Weather agent", + instructions="You are a helpful agent.", + tools=[get_weather], + tool_use_behavior=behavior, + model_settings=ModelSettings( + provider="azure_openai", + tool_choice="required" if tool_use_behavior != "default" else None, + temperature=0.7 + ), + ) + + result = await Runner.run(agent, input="What's the weather in Tokyo?", run_config=run_config) + print(result.final_output) + + +if __name__ == "__main__": + import argparse + + # Print usage instructions + print("Azure OpenAI Forcing Tool Use Example") + print("====================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + parser = argparse.ArgumentParser() + parser.add_argument( + "-t", + "--tool-use-behavior", + type=str, + required=True, + choices=["default", "first_tool", "custom"], + help="The behavior to use for tool use. Default will cause tool outputs to be sent to the model. " + "first_tool_result will cause the first tool result to be used as the final output. " + "custom will use a custom tool use behavior function.", + ) + args = parser.parse_args() + asyncio.run(main(args.tool_use_behavior)) diff --git a/examples_OpenAPI/azure/agent_patterns/input_guardrails.py b/examples_OpenAPI/azure/agent_patterns/input_guardrails.py new file mode 100644 index 00000000..27c508d6 --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/input_guardrails.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel + +from src.agents import ( + Agent, + GuardrailFunctionOutput, + InputGuardrailTripwireTriggered, + RunContextWrapper, + Runner, + TResponseInputItem, + input_guardrail, +) +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to use input guardrails with Azure OpenAI service. + +Guardrails are checks that run in parallel to the agent's execution. +They can be used for tasks such as: +- Checking if input messages are off-topic +- Ensuring output messages don't violate any policies +- Taking control of agent execution if unexpected input is detected + +In this example, we set up an input guardrail that triggers if the user +asks for help with math homework. If triggered, we respond with a refusal message. +""" + + +### 1. An agent-based guardrail that triggers if the user asks for math homework help +class MathHomeworkOutput(BaseModel): + reasoning: str + is_math_homework: bool + + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +guardrail_agent = Agent( + name="Guardrail check", + instructions="Check if the user is asking you to do their math homework.", + output_type=MathHomeworkOutput, + model_settings=azure_settings, +) + + +@input_guardrail +async def math_guardrail( + context: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem] +) -> GuardrailFunctionOutput: + """This is an input guardrail function that calls an agent to check if the input is a math homework question.""" + result = await Runner.run(guardrail_agent, input, context=context.context, run_config=run_config) + final_output = result.final_output_as(MathHomeworkOutput) + + return GuardrailFunctionOutput( + output_info=final_output, + tripwire_triggered=final_output.is_math_homework, + ) + + +### 2. The run loop + + +async def main(): + agent = Agent( + name="Customer support agent", + instructions="You are a customer support agent. You help customers with their questions.", + input_guardrails=[math_guardrail], + model_settings=azure_settings, + ) + + input_data: list[TResponseInputItem] = [] + + while True: + user_input = input("Enter a message: ") + input_data.append( + { + "role": "user", + "content": user_input, + } + ) + + try: + result = await Runner.run(agent, input_data, run_config=run_config) + print(result.final_output) + # If the guardrail didn't trigger, we use the result as the input for the next run + input_data = result.to_input_list() + except InputGuardrailTripwireTriggered: + # If the guardrail triggered, we instead add a refusal message to the input + message = "Sorry, I can't help you with your math homework." + print(message) + input_data.append( + { + "role": "assistant", + "content": message, + } + ) + + # Sample run: + # Enter a message: What's the capital of California? + # The capital of California is Sacramento. + # Enter a message: Can you help me solve for x: 2x + 5 = 11 + # Sorry, I can't help you with your math homework. + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Input Guardrails Example") + print("====================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/agent_patterns/llm_as_a_judge.py b/examples_OpenAPI/azure/agent_patterns/llm_as_a_judge.py new file mode 100644 index 00000000..01f9a629 --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/llm_as_a_judge.py @@ -0,0 +1,136 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel, Field + +from src.agents import Agent, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates the LLM-as-a-judge pattern using Azure OpenAI service. +The pattern uses one LLM to generate content, and another LLM to evaluate and provide feedback. +The process can be repeated until the content meets quality criteria. +""" + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + + +class OutlineResponse(BaseModel): + """An outline for a story with reasoning.""" + reasoning: str = Field(description="Your thought process for creating this outline") + outline: str = Field(description="The outline of the story") + + +class JudgeResponse(BaseModel): + """Feedback on a story outline and suggestions for improvement.""" + feedback: str = Field(description="Constructive feedback on the outline") + suggestions: str = Field(description="Specific suggestions for improvement") + rating: int = Field(description="A rating from 1-10 where 10 is excellent", ge=1, le=10) + + +# Generator agent +generator_agent = Agent( + name="OutlineGenerator", + instructions="You are an expert storyteller. Create an engaging outline for a short story.", + output_type=OutlineResponse, + model_settings=azure_settings, +) + +# Judge agent +judge_agent = Agent( + name="OutlineJudge", + instructions="You are a discerning literary critic. Evaluate the outline for coherence, creativity, and appeal.", + output_type=JudgeResponse, + model_settings=azure_settings, +) + +# Refined generator agent +refiner_agent = Agent( + name="OutlineRefiner", + instructions="You are an expert storyteller. Improve the outline based on the feedback provided.", + output_type=OutlineResponse, + model_settings=azure_settings, +) + + +async def main(): + # Get prompt from user + prompt = input("Enter a theme for a short story: ") + + # Generate initial outline + with trace("LLM Judge Process"): + print("\nGenerating initial outline...") + generator_result = await Runner.run( + generator_agent, + f"Create an outline for a short story about {prompt}", + run_config=run_config + ) + + outline = generator_result.final_output_as(OutlineResponse) + print(f"\nInitial outline:\n{outline.outline}") + + # Judge the outline + print("\nEvaluating outline...") + judge_result = await Runner.run( + judge_agent, + f"Evaluate this outline for a short story about {prompt}:\n\n{outline.outline}", + run_config=run_config + ) + + judgment = judge_result.final_output_as(JudgeResponse) + print(f"\nFeedback (Rating: {judgment.rating}/10):\n{judgment.feedback}") + print(f"\nSuggestions:\n{judgment.suggestions}") + + # If rating is less than 8, refine the outline + if judgment.rating < 8: + print("\nRefining outline based on feedback...") + refiner_result = await Runner.run( + refiner_agent, + f""" + Original outline: {outline.outline} + + Feedback: {judgment.feedback} + + Suggestions: {judgment.suggestions} + + Please improve this outline for a story about {prompt}. + """, + run_config=run_config + ) + + refined_outline = refiner_result.final_output_as(OutlineResponse) + print(f"\nRefined outline:\n{refined_outline.outline}") + else: + print("\nOutline was good enough, no refinement needed.") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI LLM as a Judge Example") + print("==================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/agent_patterns/output_guardrails.py b/examples_OpenAPI/azure/agent_patterns/output_guardrails.py new file mode 100644 index 00000000..e1fdd0ba --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/output_guardrails.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +import asyncio +import json +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel, Field + +from src.agents import ( + Agent, + GuardrailFunctionOutput, + OutputGuardrailTripwireTriggered, + RunContextWrapper, + Runner, + output_guardrail, +) +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to use output guardrails with Azure OpenAI service. + +Output guardrails are checks that run on the final output of an agent. +They can be used for tasks such as: +- Checking if the output contains sensitive data +- Verifying the output is a valid response to the user's message + +In this example, we use a contrived scenario where we check if the agent's +response contains a phone number. +""" + + +# The agent's output type +class MessageOutput(BaseModel): + reasoning: str = Field(description="Thoughts on how to respond to the user's message") + response: str = Field(description="The response to the user's message") + user_name: str | None = Field(description="The name of the user who sent the message, if known") + + +@output_guardrail +async def sensitive_data_check( + context: RunContextWrapper, agent: Agent, output: MessageOutput +) -> GuardrailFunctionOutput: + phone_number_in_response = "650" in output.response + phone_number_in_reasoning = "650" in output.reasoning + + return GuardrailFunctionOutput( + output_info={ + "phone_number_in_response": phone_number_in_response, + "phone_number_in_reasoning": phone_number_in_reasoning, + }, + tripwire_triggered=phone_number_in_response or phone_number_in_reasoning, + ) + + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +agent = Agent( + name="Assistant", + instructions="You are a helpful assistant.", + output_type=MessageOutput, + output_guardrails=[sensitive_data_check], + model_settings=azure_settings, +) + + +async def main(): + # This should be ok + await Runner.run(agent, "What's the capital of California?", run_config=run_config) + print("First message passed") + + # This should trip the guardrail + try: + result = await Runner.run( + agent, "My phone number is 650-123-4567. Where do you think I live?", run_config=run_config + ) + print( + f"Guardrail didn't trip - this is unexpected. Output: {json.dumps(result.final_output.model_dump(), indent=2)}" + ) + + except OutputGuardrailTripwireTriggered as e: + print(f"Guardrail tripped. Info: {e.guardrail_result.output.output_info}") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Output Guardrails Example") + print("=====================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/agent_patterns/parallelization.py b/examples_OpenAPI/azure/agent_patterns/parallelization.py new file mode 100644 index 00000000..659b1e1d --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/parallelization.py @@ -0,0 +1,99 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, ItemHelpers, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates the parallelization pattern using Azure OpenAI service. +We run the agent three times in parallel and pick the best result. +""" + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +spanish_agent = Agent( + name="spanish_agent", + instructions="You translate the user's message to Spanish", + model_settings=azure_settings, +) + +translation_picker = Agent( + name="translation_picker", + instructions="You pick the best Spanish translation from the given options.", + model_settings=azure_settings, +) + + +async def main(): + msg = input("Hi! Enter a message, and we'll translate it to Spanish.\n\n") + + # Ensure the entire workflow is a single trace + with trace("Parallel translation"): + res_1, res_2, res_3 = await asyncio.gather( + Runner.run( + spanish_agent, + msg, + run_config=run_config, + ), + Runner.run( + spanish_agent, + msg, + run_config=run_config, + ), + Runner.run( + spanish_agent, + msg, + run_config=run_config, + ), + ) + + outputs = [ + ItemHelpers.text_message_outputs(res_1.new_items), + ItemHelpers.text_message_outputs(res_2.new_items), + ItemHelpers.text_message_outputs(res_3.new_items), + ] + + translations = "\n\n".join(outputs) + print(f"\n\nTranslations:\n\n{translations}") + + best_translation = await Runner.run( + translation_picker, + f"Input: {msg}\n\nTranslations:\n{translations}", + run_config=run_config, + ) + + print("\n\n-----") + + print(f"Best translation: {best_translation.final_output}") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Parallelization Example") + print("===================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/agent_patterns/routing.py b/examples_OpenAPI/azure/agent_patterns/routing.py new file mode 100644 index 00000000..850d3f39 --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/routing.py @@ -0,0 +1,107 @@ +import asyncio +import sys +import os +import re +import uuid + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) +from openai.types.responses import ResponseContentPartDoneEvent, ResponseTextDeltaEvent +from src.agents import Agent, RawResponsesStreamEvent, Runner, TResponseInputItem, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates the handoffs and routing pattern using Azure OpenAI service. +The frontline agent detects the language of the user message and hands off to a +specialized language agent. +""" + +# Create run configuration +run_config = RunConfig(tracing_disabled=False) + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +# Create specialized language agents +english_agent = Agent( + name="english_agent", + instructions="You respond to user queries in fluent English.", + model_settings=azure_settings, +) + +spanish_agent = Agent( + name="spanish_agent", + instructions="Respondes a las consultas del usuario en español fluido.", + model_settings=azure_settings, +) + +french_agent = Agent( + name="french_agent", + instructions="Vous répondez aux requêtes des utilisateurs en français courant.", + model_settings=azure_settings, +) + +# Create the router agent +triage_agent = Agent( + name="triage_agent", + instructions="Handoff to the appropriate agent based on the language of the request.", + handoffs=[french_agent, spanish_agent, english_agent], + model_settings=azure_settings, +) + + +async def main(): + # We'll create an ID for this conversation, so we can link each trace + conversation_id = str(uuid.uuid4().hex[:16]) + + msg = input("Hi! We speak French, Spanish and English. How can I help? ") + agent = triage_agent + inputs: list[TResponseInputItem] = [{"content": msg, "role": "user"}] + + while True: + # Each conversation turn is a single trace. Normally, each input from the user would be an + # API request to your app, and you can wrap the request in a trace() + with trace("Routing example", group_id=conversation_id): + result = Runner.run_streamed( + agent, + input=inputs, + ) + async for event in result.stream_events(): + if not isinstance(event, RawResponsesStreamEvent): + continue + data = event.data + if isinstance(data, ResponseTextDeltaEvent): + print(data.delta, end="", flush=True) + elif isinstance(data, ResponseContentPartDoneEvent): + print("\n") + + inputs = result.to_input_list() + print("\n") + + user_msg = input("Enter a message: ") + inputs.append({"content": user_msg, "role": "user"}) + agent = result.current_agent + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Routing and Handoffs Example") + print("=======================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/agent_patterns/tools.py b/examples_OpenAPI/azure/agent_patterns/tools.py new file mode 100644 index 00000000..17f55c86 --- /dev/null +++ b/examples_OpenAPI/azure/agent_patterns/tools.py @@ -0,0 +1,105 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel +from src.agents import Agent, Runner, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to use function tools with Azure OpenAI service. +It creates a weather agent that can use tools to get weather information. +""" + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + + +# Define a data model for weather information +class Weather(BaseModel): + city: str + temperature_range: str + conditions: str + + +# Create a function tool that returns weather information +@function_tool +def get_weather(city: str) -> Weather: + """Get the current weather for a city""" + print(f"[debug] Getting weather for {city}") + + # This is a mock implementation - in a real app, you might call a weather API + weather_data = { + "Tokyo": Weather(city="Tokyo", temperature_range="15-22C", conditions="Partly cloudy"), + "New York": Weather(city="New York", temperature_range="10-18C", conditions="Rainy"), + "London": Weather(city="London", temperature_range="8-15C", conditions="Foggy"), + "Sydney": Weather(city="Sydney", temperature_range="20-28C", conditions="Sunny"), + } + + # Return weather for the requested city, or a default response + return weather_data.get( + city, + Weather(city=city, temperature_range="15-25C", conditions="Weather data not available") + ) + + +# Create a temperature conversion tool +@function_tool +def convert_temperature(celsius: float) -> float: + """Convert temperature from Celsius to Fahrenheit""" + fahrenheit = (celsius * 9/5) + 32 + return round(fahrenheit, 1) + + +async def main(): + # Create the agent with tools + weather_agent = Agent( + name="Weather Assistant", + instructions=( + "You are a helpful weather assistant. When asked about weather, always use the get_weather tool. " + "If users mention temperature in Celsius and want it in Fahrenheit, use the convert_temperature tool." + ), + tools=[get_weather, convert_temperature], + model_settings=azure_settings, + ) + + # Get user input + user_input = input("Ask me about the weather: ") + + # Run the agent + print("\nProcessing your request...") + result = await Runner.run(weather_agent, user_input, run_config=run_config) + + # Print the result + print("\nResponse:") + print(result.final_output) + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Tools Example") + print("=========================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/basic/agent_lifecycle_example.py b/examples_OpenAPI/azure/basic/agent_lifecycle_example.py new file mode 100644 index 00000000..b41cd050 --- /dev/null +++ b/examples_OpenAPI/azure/basic/agent_lifecycle_example.py @@ -0,0 +1,131 @@ +import asyncio +import random +import sys +import os +from typing import Any + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel +from src.agents import Agent, AgentHooks, RunContextWrapper, Runner, Tool, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + + +class CustomAgentHooks(AgentHooks): + def __init__(self, display_name: str): + self.event_counter = 0 + self.display_name = display_name + + async def on_start(self, context: RunContextWrapper, agent: Agent) -> None: + self.event_counter += 1 + print(f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started") + + async def on_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} ended with output {output}" + ) + + async def on_handoff(self, context: RunContextWrapper, agent: Agent, source: Agent) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {source.name} handed off to {agent.name}" + ) + + async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started tool {tool.name}" + ) + + async def on_tool_end( + self, context: RunContextWrapper, agent: Agent, tool: Tool, result: str + ) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} ended tool {tool.name} with result {result}" + ) + + +# Create tool functions +@function_tool +def random_number(max: int) -> int: + """Generate a random number between 0 and max.""" + return random.randint(0, max) + + +@function_tool +def multiply_by_two(x: int) -> int: + """Multiply the input number by 2.""" + return x * 2 + + +# Define output model +class FinalResult(BaseModel): + number: int + + +# Create agents with custom hooks +multiply_agent = Agent( + name="Multiply Agent", + instructions="Multiply the number by 2 and then return the final result.", + tools=[multiply_by_two], + output_type=FinalResult, + hooks=CustomAgentHooks(display_name="Multiply Agent"), +) + +start_agent = Agent( + name="Start Agent", + instructions="Generate a random number. If it's even, stop. If it's odd, hand off to the multiply agent.", + tools=[random_number], + output_type=FinalResult, + handoffs=[multiply_agent], + hooks=CustomAgentHooks(display_name="Start Agent"), +) + + +async def main() -> None: + # Create run configuration + run_config = RunConfig() + + # Create provider directly, it will automatically read configuration from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.5 # More deterministic output + ) + + # Apply model settings to agents + start_agent.model_settings = azure_settings + multiply_agent.model_settings = azure_settings + + # Get user input + user_input = input("Enter a max number: ") + await Runner.run( + start_agent, + input=f"Generate a random number between 0 and {user_input}.", + run_config=run_config + ) + + print("Done!") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Agent Lifecycle Example") + print("===================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/basic/dynamic_system_prompt.py b/examples_OpenAPI/azure/basic/dynamic_system_prompt.py new file mode 100644 index 00000000..ef85b1d2 --- /dev/null +++ b/examples_OpenAPI/azure/basic/dynamic_system_prompt.py @@ -0,0 +1,78 @@ +import asyncio +import random +import sys +import os +from typing import Literal + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, RunContextWrapper, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + + +class CustomContext: + def __init__(self, style: Literal["haiku", "pirate", "robot"]): + self.style = style + + +def custom_instructions( + run_context: RunContextWrapper[CustomContext], agent: Agent[CustomContext] +) -> str: + context = run_context.context + if context.style == "haiku": + return "Only respond in haikus." + elif context.style == "pirate": + return "Respond as a pirate." + else: + return "Respond as a robot and say 'beep boop' a lot." + + +async def main(): + # Create runtime configuration + run_config = RunConfig() + + # Automatically create the provider, it will read configurations from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: Control creativity + ) + + # Create an Agent with dynamic instructions + agent = Agent( + name="Chat agent", + instructions=custom_instructions, + model_settings=azure_settings + ) + + # Randomly select a response style + choice: Literal["haiku", "pirate", "robot"] = random.choice(["haiku", "pirate", "robot"]) + context = CustomContext(style=choice) + print(f"Using style: {choice}\n") + + user_message = "Tell me a joke." + print(f"User: {user_message}") + result = await Runner.run(agent, user_message, context=context, run_config=run_config) + + print(f"Assistant: {result.final_output}") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Dynamic System Prompt Example") + print("=========================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/basic/hello_world.py b/examples_OpenAPI/azure/basic/hello_world.py new file mode 100644 index 00000000..70868f40 --- /dev/null +++ b/examples_OpenAPI/azure/basic/hello_world.py @@ -0,0 +1,61 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +A simple "Hello World" example using Azure OpenAI service. +This demonstrates the most basic way to use the Agents SDK with Azure OpenAI. +""" + +async def main(): + # Create run configuration + run_config = RunConfig() + + # Create provider directly, it will automatically read configuration from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity + ) + + agent = Agent( + name="Hello World Agent", + instructions="You are a friendly AI assistant that helps users.", + model_settings=azure_settings, + ) + + # Run the agent + result = await Runner.run( + agent, + input="Say hello to the world!", + run_config=run_config, + ) + + # Print the result + print(result.final_output) + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Hello World Example") + print("==============================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/basic/hello_world_jupyter.py b/examples_OpenAPI/azure/basic/hello_world_jupyter.py new file mode 100644 index 00000000..d8c7bd67 --- /dev/null +++ b/examples_OpenAPI/azure/basic/hello_world_jupyter.py @@ -0,0 +1,39 @@ +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +# Create Agent instance +agent = Agent( + name="Assistant", + instructions="You are a helpful assistant.", + model_settings=azure_settings +) + +# Use existing event loop in Jupyter notebook +# This file is suitable for running directly in a Jupyter notebook +result = await Runner.run(agent, "Write a haiku about Azure cloud services.", run_config=run_config) # type: ignore[top-level-await] # noqa: F704 +print(result.final_output) + +# Expected output similar to: +# Azure's vast data clouds, +# Computing power surges forth, +# World at your command. diff --git a/examples_OpenAPI/azure/basic/life_cycle_example.py b/examples_OpenAPI/azure/basic/life_cycle_example.py new file mode 100644 index 00000000..14c2dba0 --- /dev/null +++ b/examples_OpenAPI/azure/basic/life_cycle_example.py @@ -0,0 +1,138 @@ +import asyncio +import random +import sys +import os +from typing import Any + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel +from src.agents import Agent, RunContextWrapper, RunHooks, Runner, Tool, Usage, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + + +class ExampleHooks(RunHooks): + def __init__(self): + self.event_counter = 0 + + def _usage_to_str(self, usage: Usage) -> str: + return f"{usage.requests} requests, {usage.input_tokens} input tokens, {usage.output_tokens} output tokens, {usage.total_tokens} total tokens" + + async def on_agent_start(self, context: RunContextWrapper, agent: Agent) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Agent {agent.name} started. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_agent_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Agent {agent.name} ended with output {output}. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Tool {tool.name} started. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_tool_end( + self, context: RunContextWrapper, agent: Agent, tool: Tool, result: str + ) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Tool {tool.name} ended with result {result}. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_handoff( + self, context: RunContextWrapper, from_agent: Agent, to_agent: Agent + ) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Handoff from {from_agent.name} to {to_agent.name}. Usage: {self._usage_to_str(context.usage)}" + ) + + +hooks = ExampleHooks() + +# Create tool functions +@function_tool +def random_number(max: int) -> int: + """generate a random number between 0 and max.""" + return random.randint(0, max) + + +@function_tool +def multiply_by_two(x: int) -> int: + """multiply the input number by 2.""" + return x * 2 + + +# Define output model +class FinalResult(BaseModel): + number: int + + +# Create Agents +multiply_agent = Agent( + name="Multiply Agent", + instructions="Multiply the number by 2 and then return the final result.", + tools=[multiply_by_two], + output_type=FinalResult, +) + +start_agent = Agent( + name="Start Agent", + instructions="Generate a random number. If it's even, stop. If it's odd, hand off to the multiplier agent.", + tools=[random_number], + output_type=FinalResult, + handoffs=[multiply_agent], +) + + +async def main() -> None: + # Create runtime configuration + run_config = RunConfig() + + # Automatically create the provider, it will read configurations from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.5 # More deterministic output + ) + + # Apply model settings to agents + start_agent.model_settings = azure_settings + multiply_agent.model_settings = azure_settings + + # Get user input + user_input = input("Enter a max number: ") + await Runner.run( + start_agent, + hooks=hooks, + input=f"Generate a random number between 0 and {user_input}.", + run_config=run_config + ) + + print("Done!") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Lifecycle Example") + print("=============================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/basic/stream_items.py b/examples_OpenAPI/azure/basic/stream_items.py new file mode 100644 index 00000000..06a1dc2a --- /dev/null +++ b/examples_OpenAPI/azure/basic/stream_items.py @@ -0,0 +1,83 @@ +import asyncio +import random +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, ItemHelpers, Runner, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + + +@function_tool +def how_many_jokes() -> int: + """Returns a random number between 1 and 10, representing the number of jokes to tell.""" + return random.randint(1, 10) + + +async def main(): + # Create runtime configuration + run_config = RunConfig() + + # Automatically create the provider, it will read configurations from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: Control creativity + ) + + # Create an Agent instance + agent = Agent( + name="Joker", + instructions="First call the `how_many_jokes` tool, then tell that many jokes.", + tools=[how_many_jokes], + model_settings=azure_settings + ) + + # Run the streaming Agent + result = Runner.run_streamed( + agent, + input="Tell me some jokes", + run_config=run_config + ) + + print("=== Run starting ===") + async for event in result.stream_events(): + # Ignore raw response event deltas + if event.type == "raw_response_event": + continue + elif event.type == "agent_updated_stream_event": + print(f"Agent updated: {event.new_agent.name}") + continue + elif event.type == "run_item_stream_event": + if event.item.type == "tool_call_item": + print("-- Tool was called") + elif event.item.type == "tool_call_output_item": + print(f"-- Tool output: {event.item.output}") + elif event.item.type == "message_output_item": + print(f"-- Message output:\n {ItemHelpers.text_message_output(event.item)}") + else: + pass # Ignore other event types + + print("=== Run complete ===") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Streaming Items Example") + print("==================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/basic/stream_text.py b/examples_OpenAPI/azure/basic/stream_text.py new file mode 100644 index 00000000..48339150 --- /dev/null +++ b/examples_OpenAPI/azure/basic/stream_text.py @@ -0,0 +1,74 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from openai.types.responses import ResponseTextDeltaEvent, ResponseContentPartDoneEvent +from src.agents import Agent, RawResponsesStreamEvent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to stream text responses from Azure OpenAI. +Text is printed as it's generated by the model, character by character. +""" + +async def main(): + # Create run configuration + run_config = RunConfig() + + # Create provider directly, it will automatically read configuration from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity + ) + + agent = Agent( + name="Streaming Agent", + instructions="You are a helpful agent that provides information about astronomy.", + model_settings=azure_settings, + ) + + # Get input from user + user_input = input("Ask a question about astronomy: ") + + print("\nGenerating response...\n") + + # Run the agent with streaming enabled + result = Runner.run_streamed(agent, user_input, run_config=run_config) + + # Process the stream events + async for event in result.stream_events(): + # We're only interested in raw response events that contain text deltas + if not isinstance(event, RawResponsesStreamEvent): + continue + + data = event.data + if isinstance(data, ResponseTextDeltaEvent): + # Print the delta text without a newline + print(data.delta, end="", flush=True) + elif isinstance(data, ResponseContentPartDoneEvent): + # Add a newline when content part is done + print("\n") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Streaming Text Example") + print("=================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/basic/tools.py b/examples_OpenAPI/azure/basic/tools.py new file mode 100644 index 00000000..9f76762f --- /dev/null +++ b/examples_OpenAPI/azure/basic/tools.py @@ -0,0 +1,74 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel +from src.agents import Agent, Runner, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + + +class Weather(BaseModel): + city: str + temperature_range: str + conditions: str + + +@function_tool +def get_weather(city: str) -> Weather: + print("[debug] get_weather called") + return Weather(city=city, temperature_range="14-20C", conditions="Sunny with light clouds.") + + +async def main(): + # Create runtime configuration + run_config = RunConfig() + + # Automatically create the provider, it will read configurations from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: Control creativity + ) + + # Create an Agent instance + agent = Agent( + name="Weather Assistant", + instructions="You are a helpful weather assistant. Use the get_weather tool when asked about weather.", + model_settings=azure_settings, + tools=[get_weather], + ) + + # Run the Agent + print("Running Agent with Azure OpenAI, please wait...") + result = await Runner.run( + agent, + "What's the weather in Tokyo?", + run_config=run_config + ) + + # Print the result + print("\nResult:") + print(result.final_output) + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Tools Example") + print("=========================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/customer_service/main.py b/examples_OpenAPI/azure/customer_service/main.py new file mode 100644 index 00000000..77cf50b0 --- /dev/null +++ b/examples_OpenAPI/azure/customer_service/main.py @@ -0,0 +1,212 @@ +from __future__ import annotations as _annotations + +import asyncio +import random +import sys +import os +import uuid + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel + +from src.agents import ( + Agent, + HandoffOutputItem, + ItemHelpers, + MessageOutputItem, + RunContextWrapper, + Runner, + ToolCallItem, + ToolCallOutputItem, + TResponseInputItem, + function_tool, + handoff, + trace, +) +from src.agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates a customer service system using Azure OpenAI. +It features: +- Context sharing between agents +- Tool usage for lookups and updates +- Handoffs between specialized agents +""" + +### CONTEXT + + +class AirlineAgentContext(BaseModel): + passenger_name: str | None = None + confirmation_number: str | None = None + seat_number: str | None = None + flight_number: str | None = None + + +### TOOLS + + +@function_tool( + name_override="faq_lookup_tool", description_override="Lookup frequently asked questions." +) +async def faq_lookup_tool(question: str) -> str: + if "bag" in question or "baggage" in question: + return ( + "You are allowed to bring one bag on the plane. " + "It must be under 50 pounds and 22 inches x 14 inches x 9 inches." + ) + elif "seats" in question or "plane" in question: + return ( + "There are 120 seats on the plane. " + "There are 22 business class seats and 98 economy seats. " + "Exit rows are rows 4 and 16. " + "Rows 5-8 are Economy Plus, with extra legroom. " + ) + elif "wifi" in question: + return "We have free wifi on the plane, join Airline-Wifi" + return "I'm sorry, I don't know the answer to that question." + + +@function_tool +async def update_seat( + context: RunContextWrapper[AirlineAgentContext], confirmation_number: str, new_seat: str +) -> str: + """ + Update the seat for a given confirmation number. + + Args: + confirmation_number: The confirmation number for the flight. + new_seat: The new seat to update to. + """ + # Update the context based on the customer's input + context.context.confirmation_number = confirmation_number + context.context.seat_number = new_seat + # Ensure that the flight number has been set by the incoming handoff + assert context.context.flight_number is not None, "Flight number is required" + return f"Updated seat to {new_seat} for confirmation number {confirmation_number}" + + +### HOOKS + + +async def on_seat_booking_handoff(context: RunContextWrapper[AirlineAgentContext]) -> None: + flight_number = f"FLT-{random.randint(100, 999)}" + context.context.flight_number = flight_number + + +### AGENTS + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +faq_agent = Agent[AirlineAgentContext]( + name="FAQ Agent", + handoff_description="A helpful agent that can answer questions about the airline.", + instructions=f"""{RECOMMENDED_PROMPT_PREFIX} + You are an FAQ agent. If you are speaking to a customer, you probably were transferred to from the triage agent. + Use the following routine to support the customer. + # Routine + 1. Identify the last question asked by the customer. + 2. Use the faq lookup tool to answer the question. Do not rely on your own knowledge. + 3. If you cannot answer the question, transfer back to the triage agent.""", + tools=[faq_lookup_tool], + model_settings=azure_settings, +) + +seat_booking_agent = Agent[AirlineAgentContext]( + name="Seat Booking Agent", + handoff_description="A helpful agent that can update a seat on a flight.", + instructions=f"""{RECOMMENDED_PROMPT_PREFIX} + You are a seat booking agent. If you are speaking to a customer, you probably were transferred to from the triage agent. + Use the following routine to support the customer. + # Routine + 1. Ask for their confirmation number. + 2. Ask the customer what their desired seat number is. + 3. Use the update seat tool to update the seat on the flight. + If the customer asks a question that is not related to the routine, transfer back to the triage agent. """, + tools=[update_seat], + model_settings=azure_settings, +) + +triage_agent = Agent[AirlineAgentContext]( + name="Triage Agent", + handoff_description="A triage agent that can delegate a customer's request to the appropriate agent.", + instructions=( + f"{RECOMMENDED_PROMPT_PREFIX} " + "You are a helpful triaging agent. You can use your tools to delegate questions to other appropriate agents." + ), + handoffs=[ + faq_agent, + handoff(agent=seat_booking_agent, on_handoff=on_seat_booking_handoff), + ], + model_settings=azure_settings, +) + +faq_agent.handoffs.append(triage_agent) +seat_booking_agent.handoffs.append(triage_agent) + + +### RUN + + +async def main(): + current_agent: Agent[AirlineAgentContext] = triage_agent + input_items: list[TResponseInputItem] = [] + context = AirlineAgentContext() + + # Normally, each input from the user would be an API request to your app, and you can wrap the request in a trace() + # Here, we'll just use a random UUID for the conversation ID + conversation_id = uuid.uuid4().hex[:16] + + while True: + user_input = input("Enter your message: ") + with trace("Customer service", group_id=conversation_id): + input_items.append({"content": user_input, "role": "user"}) + result = await Runner.run(current_agent, input_items, context=context, run_config=run_config) + + for new_item in result.new_items: + agent_name = new_item.agent.name + if isinstance(new_item, MessageOutputItem): + print(f"{agent_name}: {ItemHelpers.text_message_output(new_item)}") + elif isinstance(new_item, HandoffOutputItem): + print( + f"Handed off from {new_item.source_agent.name} to {new_item.target_agent.name}" + ) + elif isinstance(new_item, ToolCallItem): + print(f"{agent_name}: Calling a tool") + elif isinstance(new_item, ToolCallOutputItem): + print(f"{agent_name}: Tool call output: {new_item.output}") + else: + print(f"{agent_name}: Skipping item: {new_item.__class__.__name__}") + input_items = result.to_input_list() + current_agent = result.last_agent + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Customer Service Example") + print("===================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/financial_research_agent/agents/financials_agent.py b/examples_OpenAPI/azure/financial_research_agent/agents/financials_agent.py new file mode 100644 index 00000000..1b7953d6 --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/agents/financials_agent.py @@ -0,0 +1,31 @@ +from pydantic import BaseModel, Field + +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +# This agent focuses specifically on financial metrics and KPIs +INSTRUCTIONS = ( + "You are a financial metrics specialist. When given a company name or topic, " + "extract and analyze key financial metrics from the available information. " + "Focus on recent performance, trends, and comparison to peers or benchmarks. " + "Include relevant numerical data like revenue, profit margins, growth rates, P/E ratios, etc. " + "where available. Be precise, factual, and concise." +) + + +class AnalysisSummary(BaseModel): + summary: str = Field(description="A concise financial analysis focusing on key metrics") + + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.3 # Lower temperature for more factual analysis +) + +financials_agent = Agent( + name="FinancialsAgent", + instructions=INSTRUCTIONS, + output_type=AnalysisSummary, + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/financial_research_agent/agents/planner_agent.py b/examples_OpenAPI/azure/financial_research_agent/agents/planner_agent.py new file mode 100644 index 00000000..9ab374a4 --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/agents/planner_agent.py @@ -0,0 +1,38 @@ +from pydantic import BaseModel + +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +PROMPT = ( + "You are a financial research assistant. Given a query about a company, industry, or " + "financial topic, come up with a set of web searches to perform to best answer the query. " + "Focus on recent financial news, earnings reports, analyst commentary, and market trends. " + "Output between 5 and 15 search terms." +) + + +class FinancialSearchItem(BaseModel): + reason: str + "Your reasoning for why this search is important to the financial analysis." + + query: str + "The search term to use for the web search." + + +class FinancialSearchPlan(BaseModel): + searches: list[FinancialSearchItem] + """A list of web searches to perform to best answer the financial query.""" + + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +planner_agent = Agent( + name="FinancialPlannerAgent", + instructions=PROMPT, + output_type=FinancialSearchPlan, + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/financial_research_agent/agents/risk_agent.py b/examples_OpenAPI/azure/financial_research_agent/agents/risk_agent.py new file mode 100644 index 00000000..fee4aa39 --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/agents/risk_agent.py @@ -0,0 +1,33 @@ +from pydantic import BaseModel, Field + +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +# This agent specializes in identifying potential risks and red flags +INSTRUCTIONS = ( + "You are a financial risk analyst. When given a company name or topic, " + "identify potential risks, challenges, or red flags from the available information. " + "Consider regulatory issues, market headwinds, competitive threats, financial stability " + "concerns, and other factors that could negatively impact performance or valuation. " + "Be balanced and evidence-based in your assessment." +) + + +class AnalysisSummary(BaseModel): + summary: str = Field( + description="A concise risk analysis highlighting potential concerns" + ) + + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.4 # Moderate temperature for balanced risk assessment +) + +risk_agent = Agent( + name="RiskAnalysisAgent", + instructions=INSTRUCTIONS, + output_type=AnalysisSummary, + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/financial_research_agent/agents/search_agent.py b/examples_OpenAPI/azure/financial_research_agent/agents/search_agent.py new file mode 100644 index 00000000..c9cefe1e --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/agents/search_agent.py @@ -0,0 +1,25 @@ +from src.agents import Agent, WebSearchTool +from src.agents.model_settings import ModelSettings + +# Given a search term, use web search to pull back a brief summary. +# Summaries should be concise but capture the main financial points. +INSTRUCTIONS = ( + "You are a research assistant specializing in financial topics. " + "Given a search term, use web search to retrieve up-to-date context and " + "produce a short summary of at most 300 words. Focus on key numbers, events, " + "or quotes that will be useful to a financial analyst." +) + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.5, # Lower temperature for more factual outputs + tool_choice="required" # Force tool use +) + +search_agent = Agent( + name="FinancialSearchAgent", + instructions=INSTRUCTIONS, + tools=[WebSearchTool()], + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/financial_research_agent/agents/verifier_agent.py b/examples_OpenAPI/azure/financial_research_agent/agents/verifier_agent.py new file mode 100644 index 00000000..97ceac39 --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/agents/verifier_agent.py @@ -0,0 +1,35 @@ +from pydantic import BaseModel + +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +# Verifier agent checks the final report for issues like inconsistencies or missing sources +INSTRUCTIONS = ( + "You are a fact-checking editor for financial reports. Review the provided report " + "and identify any potential issues such as: " + "1. Inconsistent statements or contradictions " + "2. Financial figures that seem implausible or don't add up " + "3. Claims that appear unsubstantiated or would benefit from specific sources " + "4. Areas where important context might be missing " + "5. Potential bias in the analysis " + "\n\nProvide specific examples from the text where you identify issues, and " + "suggest how these could be addressed. If the report appears sound, note that as well." +) + + +class VerificationResult(str): + """The verification feedback as text.""" + + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.2 # Very low temperature for critical assessment +) + +verifier_agent = Agent( + name="VerifierAgent", + instructions=INSTRUCTIONS, + output_type=VerificationResult, + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/financial_research_agent/agents/writer_agent.py b/examples_OpenAPI/azure/financial_research_agent/agents/writer_agent.py new file mode 100644 index 00000000..8f2cc1b4 --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/agents/writer_agent.py @@ -0,0 +1,41 @@ +from pydantic import BaseModel + +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +# Writer agent brings together the raw search results and optionally calls out +# to sub-analyst tools for specialized commentary, then returns a cohesive markdown report. +WRITER_PROMPT = ( + "You are a senior financial analyst. You will be provided with the original query and " + "a set of raw search summaries. Your task is to synthesize these into a long-form markdown " + "report (at least several paragraphs) including a short executive summary and follow-up " + "questions. If needed, you can call the available analysis tools (e.g. fundamentals_analysis, " + "risk_analysis) to get short specialist write-ups to incorporate." +) + + +class FinancialReportData(BaseModel): + short_summary: str + """A short 2-3 sentence executive summary.""" + + markdown_report: str + """The full markdown report.""" + + follow_up_questions: list[str] + """Suggested follow-up questions for further research.""" + + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +# Note: We will attach tools to specialist analyst agents at runtime in the manager. +# This shows how an agent can use tools to delegate to specialized subagents. +writer_agent = Agent( + name="FinancialWriterAgent", + instructions=WRITER_PROMPT, + output_type=FinancialReportData, + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/financial_research_agent/main.py b/examples_OpenAPI/azure/financial_research_agent/main.py new file mode 100644 index 00000000..243daa0f --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/main.py @@ -0,0 +1,46 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +from .manager import FinancialResearchManager + + +# Entrypoint for the financial research agent example using Azure OpenAI. +# Run this as `python -m examples_OpenAPI.azure.financial_research_agent.main` and enter a +# financial research query, for example: +# "Write up an analysis of Apple Inc.'s most recent quarter." +async def main() -> None: + # Create run configuration + run_config = RunConfig() + + # Set up Azure OpenAI provider + run_config.model_provider = AzureOpenAIProvider() + + # Get financial research query from user + query = input("Enter a financial research query: ") + + # Create and run the financial research manager with Azure configuration + mgr = FinancialResearchManager(run_config) + await mgr.run(query) + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Financial Research Agent Example") + print("=========================================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/financial_research_agent/manager.py b/examples_OpenAPI/azure/financial_research_agent/manager.py new file mode 100644 index 00000000..95e3ef09 --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/manager.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +import asyncio +import time +from collections.abc import Sequence + +from rich.console import Console + +from src.agents import Runner, RunResult, custom_span, gen_trace_id, trace +from src.agents.run import RunConfig + +from .agents.financials_agent import financials_agent +from .agents.planner_agent import FinancialSearchItem, FinancialSearchPlan, planner_agent +from .agents.risk_agent import risk_agent +from .agents.search_agent import search_agent +from .agents.verifier_agent import VerificationResult, verifier_agent +from .agents.writer_agent import FinancialReportData, writer_agent +from .printer import Printer + + +async def _summary_extractor(run_result: RunResult) -> str: + """Custom output extractor for sub‑agents that return an AnalysisSummary.""" + # The financial/risk analyst agents emit an AnalysisSummary with a `summary` field. + # We want the tool call to return just that summary text so the writer can drop it inline. + return str(run_result.final_output.summary) + + +class FinancialResearchManager: + """ + Orchestrates the full flow: planning, searching, sub‑analysis, writing, and verification. + Uses Azure OpenAI for all AI components. + """ + + def __init__(self, run_config: RunConfig) -> None: + self.console = Console() + self.printer = Printer(self.console) + self.run_config = run_config + + async def run(self, query: str) -> None: + trace_id = gen_trace_id() + with trace("Financial research trace", trace_id=trace_id): + self.printer.update_item( + "trace_id", + f"View trace: https://platform.openai.com/traces/{trace_id}", + is_done=True, + hide_checkmark=True, + ) + self.printer.update_item("start", "Starting financial research...", is_done=True) + search_plan = await self._plan_searches(query) + search_results = await self._perform_searches(search_plan) + report = await self._write_report(query, search_results) + verification = await self._verify_report(report) + + final_report = f"Report summary\n\n{report.short_summary}" + self.printer.update_item("final_report", final_report, is_done=True) + + self.printer.end() + + # Print to stdout + print("\n\n=====REPORT=====\n\n") + print(f"Report:\n{report.markdown_report}") + print("\n\n=====FOLLOW UP QUESTIONS=====\n\n") + print("\n".join(report.follow_up_questions)) + print("\n\n=====VERIFICATION=====\n\n") + print(verification) + + async def _plan_searches(self, query: str) -> FinancialSearchPlan: + self.printer.update_item("planning", "Planning searches...") + result = await Runner.run(planner_agent, f"Query: {query}", run_config=self.run_config) + self.printer.update_item( + "planning", + f"Will perform {len(result.final_output.searches)} searches", + is_done=True, + ) + return result.final_output_as(FinancialSearchPlan) + + async def _perform_searches(self, search_plan: FinancialSearchPlan) -> Sequence[str]: + with custom_span("Search the web"): + self.printer.update_item("searching", "Searching...") + tasks = [asyncio.create_task(self._search(item)) for item in search_plan.searches] + results: list[str] = [] + num_completed = 0 + for task in asyncio.as_completed(tasks): + result = await task + if result is not None: + results.append(result) + num_completed += 1 + self.printer.update_item( + "searching", f"Searching... {num_completed}/{len(tasks)} completed" + ) + self.printer.mark_item_done("searching") + return results + + async def _search(self, item: FinancialSearchItem) -> str | None: + input_data = f"Search term: {item.query}\nReason: {item.reason}" + try: + result = await Runner.run(search_agent, input_data, run_config=self.run_config) + return str(result.final_output) + except Exception: + return None + + async def _write_report(self, query: str, search_results: Sequence[str]) -> FinancialReportData: + # Expose the specialist analysts as tools so the writer can invoke them inline + # and still produce the final FinancialReportData output. + fundamentals_tool = financials_agent.as_tool( + tool_name="fundamentals_analysis", + tool_description="Use to get a short write‑up of key financial metrics", + custom_output_extractor=_summary_extractor, + ) + risk_tool = risk_agent.as_tool( + tool_name="risk_analysis", + tool_description="Use to get a short write‑up of potential red flags", + custom_output_extractor=_summary_extractor, + ) + writer_with_tools = writer_agent.clone(tools=[fundamentals_tool, risk_tool]) + self.printer.update_item("writing", "Thinking about report...") + input_data = f"Original query: {query}\nSummarized search results: {search_results}" + result = Runner.run_streamed(writer_with_tools, input_data, run_config=self.run_config) + update_messages = [ + "Planning report structure...", + "Writing sections...", + "Finalizing report...", + ] + last_update = time.time() + next_message = 0 + async for _ in result.stream_events(): + if time.time() - last_update > 5 and next_message < len(update_messages): + self.printer.update_item("writing", update_messages[next_message]) + next_message += 1 + last_update = time.time() + self.printer.mark_item_done("writing") + return result.final_output_as(FinancialReportData) + + async def _verify_report(self, report: FinancialReportData) -> VerificationResult: + self.printer.update_item("verifying", "Verifying report...") + result = await Runner.run(verifier_agent, report.markdown_report, run_config=self.run_config) + self.printer.mark_item_done("verifying") + return result.final_output_as(VerificationResult) diff --git a/examples_OpenAPI/azure/financial_research_agent/printer.py b/examples_OpenAPI/azure/financial_research_agent/printer.py new file mode 100644 index 00000000..68799feb --- /dev/null +++ b/examples_OpenAPI/azure/financial_research_agent/printer.py @@ -0,0 +1,43 @@ +from rich.console import Console +from rich.live import Live +from rich.tree import Tree + + +class Printer: + """ + Pretty-print the progress of the agent. + """ + + def __init__(self, console: Console): + self.console = console + self.tree = Tree("Financial Research") + self.items: dict[str, tuple[Tree, bool]] = {} + self.live = Live(self.tree, console=console) + self.live.start() + + def update_item( + self, id: str, label: str, is_done: bool = False, hide_checkmark: bool = False + ) -> None: + """Updates a branch of the tree, adding it if it doesn't exist.""" + if id in self.items: + node, _ = self.items[id] + icon = "" if hide_checkmark else ("✓ " if is_done else "⏳ ") + node.label = f"{icon}{label}" + self.items[id] = (node, is_done) + else: + icon = "" if hide_checkmark else ("✓ " if is_done else "⏳ ") + node = self.tree.add(f"{icon}{label}") + self.items[id] = (node, is_done) + + def mark_item_done(self, id: str) -> None: + """Marks an item as done.""" + if id in self.items: + node, _ = self.items[id] + label = str(node.label) + if label.startswith("⏳ "): + node.label = f"✓ {label[2:]}" + self.items[id] = (node, True) + + def end(self) -> None: + """End the live display.""" + self.live.stop() diff --git a/examples_OpenAPI/azure/research_bot/agents/planner_agent.py b/examples_OpenAPI/azure/research_bot/agents/planner_agent.py new file mode 100644 index 00000000..fd092414 --- /dev/null +++ b/examples_OpenAPI/azure/research_bot/agents/planner_agent.py @@ -0,0 +1,36 @@ +from pydantic import BaseModel + +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +PROMPT = ( + "You are a helpful research assistant. Given a query, come up with a set of web searches " + "to perform to best answer the query. Output between 5 and 20 terms to query for." +) + + +class WebSearchItem(BaseModel): + reason: str + "Your reasoning for why this search is important to the query." + + query: str + "The search term to use for the web search." + + +class WebSearchPlan(BaseModel): + searches: list[WebSearchItem] + """A list of web searches to perform to best answer the query.""" + + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +planner_agent = Agent( + name="PlannerAgent", + instructions=PROMPT, + output_type=WebSearchPlan, + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/research_bot/agents/search_agent.py b/examples_OpenAPI/azure/research_bot/agents/search_agent.py new file mode 100644 index 00000000..54bbc254 --- /dev/null +++ b/examples_OpenAPI/azure/research_bot/agents/search_agent.py @@ -0,0 +1,25 @@ +from src.agents import Agent, WebSearchTool +from src.agents.model_settings import ModelSettings + +INSTRUCTIONS = ( + "You are a research assistant. Given a search term, you search the web for that term and " + "produce a concise summary of the results. The summary must be 2-3 paragraphs and less than 300 " + "words. Capture the main points. Write succinctly, no need to have complete sentences or good " + "grammar. This will be consumed by someone synthesizing a report, so it's vital you capture the " + "essence and ignore any fluff. Do not include any additional commentary other than the summary " + "itself." +) + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.5, # Lower temperature for more factual outputs + tool_choice="required" # Force tool use +) + +search_agent = Agent( + name="Search agent", + instructions=INSTRUCTIONS, + tools=[WebSearchTool()], + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/research_bot/agents/writer_agent.py b/examples_OpenAPI/azure/research_bot/agents/writer_agent.py new file mode 100644 index 00000000..408c9dab --- /dev/null +++ b/examples_OpenAPI/azure/research_bot/agents/writer_agent.py @@ -0,0 +1,39 @@ +from pydantic import BaseModel + +from src.agents import Agent +from src.agents.model_settings import ModelSettings + +INSTRUCTIONS = ( + "You are a professional report writer with expertise in synthesizing information. " + "Given an original query and a set of search results, create a comprehensive markdown report " + "that answers the query. The report should be well-structured with sections, have a logical " + "flow, and be informative. Include a brief executive summary at the beginning. " + "Also include 3-5 follow-up questions that the user might want to ask next. " + "The report should be written in professional language, avoiding unnecessary jargon, " + "and be engaging to read." +) + + +class ReportData(BaseModel): + short_summary: str + """A short 2-3 sentence executive summary.""" + + markdown_report: str + """The full markdown report.""" + + follow_up_questions: list[str] + """Suggested follow-up questions for further research.""" + + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Control creativity +) + +writer_agent = Agent( + name="WriterAgent", + instructions=INSTRUCTIONS, + output_type=ReportData, + model_settings=azure_settings, +) diff --git a/examples_OpenAPI/azure/research_bot/main.py b/examples_OpenAPI/azure/research_bot/main.py new file mode 100644 index 00000000..18a08c99 --- /dev/null +++ b/examples_OpenAPI/azure/research_bot/main.py @@ -0,0 +1,41 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents.models.azure_openai_provider import AzureOpenAIProvider +from src.agents.run import RunConfig + +from .manager import ResearchManager + + +async def main() -> None: + # Create run configuration + run_config = RunConfig() + + # Set up Azure OpenAI provider + run_config.model_provider = AzureOpenAIProvider() + + # Get research query from user + query = input("What would you like to research? ") + + # Create and run the research manager with Azure configuration + await ResearchManager(run_config).run(query) + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Research Bot Example") + print("===============================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/research_bot/manager.py b/examples_OpenAPI/azure/research_bot/manager.py new file mode 100644 index 00000000..6660dad8 --- /dev/null +++ b/examples_OpenAPI/azure/research_bot/manager.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import asyncio +import time + +from rich.console import Console + +from src.agents import Runner, custom_span, gen_trace_id, trace +from src.agents.run import RunConfig + +from .agents.planner_agent import WebSearchItem, WebSearchPlan, planner_agent +from .agents.search_agent import search_agent +from .agents.writer_agent import ReportData, writer_agent +from .printer import Printer + + +class ResearchManager: + def __init__(self, run_config: RunConfig): + self.console = Console() + self.printer = Printer(self.console) + self.run_config = run_config + + async def run(self, query: str) -> None: + trace_id = gen_trace_id() + with trace("Research trace", trace_id=trace_id): + self.printer.update_item( + "trace_id", + f"View trace: https://platform.openai.com/traces/{trace_id}", + is_done=True, + hide_checkmark=True, + ) + + self.printer.update_item( + "starting", + "Starting research...", + is_done=True, + hide_checkmark=True, + ) + search_plan = await self._plan_searches(query) + search_results = await self._perform_searches(search_plan) + report = await self._write_report(query, search_results) + + final_report = f"Report summary\n\n{report.short_summary}" + self.printer.update_item("final_report", final_report, is_done=True) + + self.printer.end() + + print("\n\n=====REPORT=====\n\n") + print(f"Report: {report.markdown_report}") + print("\n\n=====FOLLOW UP QUESTIONS=====\n\n") + follow_up_questions = "\n".join(report.follow_up_questions) + print(f"Follow up questions: {follow_up_questions}") + + async def _plan_searches(self, query: str) -> WebSearchPlan: + self.printer.update_item("planning", "Planning searches...") + result = await Runner.run( + planner_agent, + f"Query: {query}", + run_config=self.run_config, + ) + self.printer.update_item( + "planning", + f"Will perform {len(result.final_output.searches)} searches", + is_done=True, + ) + return result.final_output_as(WebSearchPlan) + + async def _perform_searches(self, search_plan: WebSearchPlan) -> list[str]: + with custom_span("Search the web"): + self.printer.update_item("searching", "Searching...") + num_completed = 0 + tasks = [asyncio.create_task(self._search(item)) for item in search_plan.searches] + results = [] + for task in asyncio.as_completed(tasks): + result = await task + if result is not None: + results.append(result) + num_completed += 1 + self.printer.update_item( + "searching", f"Searching... {num_completed}/{len(tasks)} completed" + ) + self.printer.mark_item_done("searching") + return results + + async def _search(self, item: WebSearchItem) -> str | None: + input = f"Search term: {item.query}\nReason for searching: {item.reason}" + try: + result = await Runner.run( + search_agent, + input, + run_config=self.run_config, + ) + return str(result.final_output) + except Exception: + return None + + async def _write_report(self, query: str, search_results: list[str]) -> ReportData: + self.printer.update_item("writing", "Thinking about report...") + input = f"Original query: {query}\nSummarized search results: {search_results}" + result = Runner.run_streamed( + writer_agent, + input, + run_config=self.run_config, + ) + update_messages = [ + "Thinking about report...", + "Planning report structure...", + "Writing outline...", + "Creating sections...", + "Cleaning up formatting...", + "Finalizing report...", + "Finishing report...", + ] + + last_update = time.time() + next_message = 0 + async for _ in result.stream_events(): + if time.time() - last_update > 5 and next_message < len(update_messages): + self.printer.update_item("writing", update_messages[next_message]) + next_message += 1 + last_update = time.time() + + self.printer.mark_item_done("writing") + return result.final_output_as(ReportData) diff --git a/examples_OpenAPI/azure/tools/computer_use.py b/examples_OpenAPI/azure/tools/computer_use.py new file mode 100644 index 00000000..1c1b833b --- /dev/null +++ b/examples_OpenAPI/azure/tools/computer_use.py @@ -0,0 +1,202 @@ +import asyncio +import base64 +import sys +import os +from typing import Literal, Union + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from playwright.async_api import Browser, Page, Playwright, async_playwright + +from src.agents import ( + Agent, + AsyncComputer, + Button, + ComputerTool, + Environment, + ModelSettings, + Runner, + trace, +) +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to use the ComputerTool with Azure OpenAI. +The agent uses a Playwright browser to search for sports news and provide a summary. +""" + +# Uncomment to see very verbose logs +# import logging +# logging.getLogger("openai.agents").setLevel(logging.DEBUG) +# logging.getLogger("openai.agents").addHandler(logging.StreamHandler()) + + +async def main(): + # Create run configuration + run_config = RunConfig() + + # Create provider directly, it will automatically read configuration from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings for computer use + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + truncation="auto", # Required for computer use model + temperature=0.7 # Optional: control creativity + ) + + async with LocalPlaywrightComputer() as computer: + with trace("Computer use example"): + agent = Agent( + name="Browser user", + instructions="You are a helpful agent.", + tools=[ComputerTool(computer)], + model="computer-use-preview", # Using the computer use model + model_settings=azure_settings, + ) + result = await Runner.run(agent, "Search for SF sports news and summarize.", run_config=run_config) + print(result.final_output) + + +CUA_KEY_TO_PLAYWRIGHT_KEY = { + "/": "Divide", + "\\": "Backslash", + "alt": "Alt", + "arrowdown": "ArrowDown", + "arrowleft": "ArrowLeft", + "arrowright": "ArrowRight", + "arrowup": "ArrowUp", + "backspace": "Backspace", + "capslock": "CapsLock", + "cmd": "Meta", + "ctrl": "Control", + "delete": "Delete", + "end": "End", + "enter": "Enter", + "esc": "Escape", + "home": "Home", + "insert": "Insert", + "option": "Alt", + "pagedown": "PageDown", + "pageup": "PageUp", + "shift": "Shift", + "space": " ", + "super": "Meta", + "tab": "Tab", + "win": "Meta", +} + + +class LocalPlaywrightComputer(AsyncComputer): + """A computer, implemented using a local Playwright browser.""" + + def __init__(self): + self._playwright: Union[Playwright, None] = None + self._browser: Union[Browser, None] = None + self._page: Union[Page, None] = None + + async def _get_browser_and_page(self) -> tuple[Browser, Page]: + width, height = self.dimensions + launch_args = [f"--window-size={width},{height}"] + browser = await self.playwright.chromium.launch(headless=False, args=launch_args) + page = await browser.new_page() + await page.set_viewport_size({"width": width, "height": height}) + await page.goto("https://www.bing.com") + return browser, page + + async def __aenter__(self): + # Start Playwright and call the subclass hook for getting browser/page + self._playwright = await async_playwright().start() + self._browser, self._page = await self._get_browser_and_page() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self._browser: + await self._browser.close() + if self._playwright: + await self._playwright.stop() + + @property + def playwright(self) -> Playwright: + assert self._playwright is not None + return self._playwright + + @property + def browser(self) -> Browser: + assert self._browser is not None + return self._browser + + @property + def page(self) -> Page: + assert self._page is not None + return self._page + + @property + def environment(self) -> Environment: + return "browser" + + @property + def dimensions(self) -> tuple[int, int]: + return (1024, 768) + + async def screenshot(self) -> str: + """Capture only the viewport (not full_page).""" + png_bytes = await self.page.screenshot(full_page=False) + return base64.b64encode(png_bytes).decode("utf-8") + + async def click(self, x: int, y: int, button: Button = "left") -> None: + playwright_button: Literal["left", "middle", "right"] = "left" + + # Playwright only supports left, middle, right buttons + if button in ("left", "right", "middle"): + playwright_button = button # type: ignore + + await self.page.mouse.click(x, y, button=playwright_button) + + async def double_click(self, x: int, y: int) -> None: + await self.page.mouse.dblclick(x, y) + + async def scroll(self, x: int, y: int, scroll_x: int, scroll_y: int) -> None: + await self.page.mouse.move(x, y) + await self.page.evaluate(f"window.scrollBy({scroll_x}, {scroll_y})") + + async def type(self, text: str) -> None: + await self.page.keyboard.type(text) + + async def wait(self) -> None: + await asyncio.sleep(1) + + async def move(self, x: int, y: int) -> None: + await self.page.mouse.move(x, y) + + async def keypress(self, keys: list[str]) -> None: + for key in keys: + mapped_key = CUA_KEY_TO_PLAYWRIGHT_KEY.get(key.lower(), key) + await self.page.keyboard.press(mapped_key) + + async def drag(self, path: list[tuple[int, int]]) -> None: + if not path: + return + await self.page.mouse.move(path[0][0], path[0][1]) + await self.page.mouse.down() + for px, py in path[1:]: + await self.page.mouse.move(px, py) + await self.page.mouse.up() + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Computer Use Example") + print("===============================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/tools/file_search.py b/examples_OpenAPI/azure/tools/file_search.py new file mode 100644 index 00000000..2827a737 --- /dev/null +++ b/examples_OpenAPI/azure/tools/file_search.py @@ -0,0 +1,75 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, FileSearchTool, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to use the FileSearchTool with Azure OpenAI. +The agent searches in a vector store for information about Arrakis from Dune. +""" + +async def main(): + # Create run configuration + run_config = RunConfig() + + # Create provider directly, it will automatically read configuration from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity + ) + + agent = Agent( + name="File searcher", + instructions="You are a helpful agent.", + tools=[ + FileSearchTool( + max_num_results=3, + vector_store_ids=["vs_67bf88953f748191be42b462090e53e7"], + include_search_results=True, + ) + ], + model_settings=azure_settings, + ) + + with trace("File search example"): + result = await Runner.run( + agent, + "Be concise, and tell me 1 sentence about Arrakis I might not know.", + run_config=run_config + ) + print(result.final_output) + """ + Arrakis, the desert planet in Frank Herbert's "Dune," was inspired by the scarcity of water + as a metaphor for oil and other finite resources. + """ + + print("\n".join([str(out) for out in result.new_items])) + """ + {"id":"...", "queries":["Arrakis"], "results":[...]} + """ + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI File Search Example") + print("==============================") + print("This example requires Azure OpenAI credentials and a valid vector store ID.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/tools/web_search.py b/examples_OpenAPI/azure/tools/web_search.py new file mode 100644 index 00000000..da3b25f5 --- /dev/null +++ b/examples_OpenAPI/azure/tools/web_search.py @@ -0,0 +1,61 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, Runner, WebSearchTool, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +""" +This example demonstrates how to use the WebSearchTool with Azure OpenAI. +The agent searches the web for local sports news and returns a concise update. +""" + +async def main(): + # Create run configuration + run_config = RunConfig() + + # Create provider directly, it will automatically read configuration from environment variables + run_config.model_provider = AzureOpenAIProvider() + + # Create Azure OpenAI model settings + azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity + ) + + agent = Agent( + name="Web searcher", + instructions="You are a helpful agent.", + tools=[WebSearchTool(user_location={"type": "approximate", "city": "New York"})], + model_settings=azure_settings, + ) + + with trace("Web search example"): + result = await Runner.run( + agent, + "search the web for 'local sports news' and give me 1 interesting update in a sentence.", + run_config=run_config, + ) + print(result.final_output) + # Example output: The New York Giants are reportedly pursuing quarterback Aaron Rodgers after his... + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Web Search Example") + print("=============================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/voice/static/main.py b/examples_OpenAPI/azure/voice/static/main.py new file mode 100644 index 00000000..b3609142 --- /dev/null +++ b/examples_OpenAPI/azure/voice/static/main.py @@ -0,0 +1,115 @@ +import asyncio +import random +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, function_tool +from src.agents.extensions.handoff_prompt import prompt_with_handoff_instructions +from src.agents.voice import ( + AudioInput, + SingleAgentVoiceWorkflow, + SingleAgentWorkflowCallbacks, + VoicePipeline, +) +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +from .util import AudioPlayer, record_audio + +""" +This is a simple example that uses a recorded audio buffer with Azure OpenAI. Run it via: +`python -m examples_OpenAPI.azure.voice.static.main` + +1. You can record an audio clip in the terminal. +2. The pipeline automatically transcribes the audio. +3. The agent workflow is a simple one that starts at the Assistant agent. +4. The output of the agent is streamed to the audio player. + +Try examples like: +- Tell me a joke (will respond with a joke) +- What's the weather in Tokyo? (will call the `get_weather` tool and then speak) +- Hola, como estas? (will handoff to the spanish agent) +""" + +# Create run configuration +run_config = RunConfig() + +# Create provider directly, it will automatically read configuration from environment variables +run_config.model_provider = AzureOpenAIProvider() + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +@function_tool +def get_weather(city: str) -> str: + """Get the weather for a given city.""" + print(f"[debug] get_weather called with city: {city}") + choices = ["sunny", "cloudy", "rainy", "snowy"] + return f"The weather in {city} is {random.choice(choices)}." + + +spanish_agent = Agent( + name="Spanish", + handoff_description="A spanish speaking agent.", + instructions=prompt_with_handoff_instructions( + "You're speaking to a human, so be polite and concise. Speak in Spanish.", + ), + model_settings=azure_settings, +) + +agent = Agent( + name="Assistant", + instructions=prompt_with_handoff_instructions( + "You're speaking to a human, so be polite and concise. If the user speaks in Spanish, handoff to the spanish agent.", + ), + model_settings=azure_settings, + handoffs=[spanish_agent], + tools=[get_weather], +) + + +class WorkflowCallbacks(SingleAgentWorkflowCallbacks): + def on_run(self, workflow: SingleAgentVoiceWorkflow, transcription: str) -> None: + print(f"[debug] on_run called with transcription: {transcription}") + + +async def main(): + pipeline = VoicePipeline( + workflow=SingleAgentVoiceWorkflow(agent, callbacks=WorkflowCallbacks()), + run_config=run_config + ) + + audio_input = AudioInput(buffer=record_audio()) + + result = await pipeline.run(audio_input) + + with AudioPlayer() as player: + async for event in result.stream(): + if event.type == "voice_stream_event_audio": + player.add_audio(event.data) + print("Received audio") + elif event.type == "voice_stream_event_lifecycle": + print(f"Received lifecycle event: {event.event}") + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Static Voice Example") + print("===============================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/azure/voice/streamed/main.py b/examples_OpenAPI/azure/voice/streamed/main.py new file mode 100644 index 00000000..dad251c8 --- /dev/null +++ b/examples_OpenAPI/azure/voice/streamed/main.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import asyncio +import sys +import os +from typing import TYPE_CHECKING + +import numpy as np +import sounddevice as sd +from textual import events +from textual.app import App, ComposeResult +from textual.containers import Container +from textual.reactive import reactive +from textual.widgets import Button, RichLog, Static +from typing_extensions import override + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents.voice import StreamedAudioInput, VoicePipeline +from src.agents.run import RunConfig +from src.agents.models.azure_openai_provider import AzureOpenAIProvider + +# Import MyWorkflow class - handle both module and package use cases +if TYPE_CHECKING: + # For type checking, use the relative import + from .my_workflow import MyWorkflow +else: + # At runtime, try both import styles + try: + # Try relative import first (when used as a package) + from .my_workflow import MyWorkflow + except ImportError: + # Fall back to direct import (when run as a script) + from my_workflow import MyWorkflow + +CHUNK_LENGTH_S = 0.05 # 100ms +SAMPLE_RATE = 24000 +FORMAT = np.int16 +CHANNELS = 1 + + +class Header(Static): + """A header widget.""" + + session_id = reactive("") + + @override + def render(self) -> str: + return "Speak to the agent. When you stop speaking, it will respond." + + +class AudioStatusIndicator(Static): + """A widget that shows the current audio recording status.""" + + is_recording = reactive(False) + + @override + def render(self) -> str: + status = ( + "🔴 Recording... (Press K to stop)" + if self.is_recording + else "⚪ Press K to start recording (Q to quit)" + ) + return status + + +class RealtimeApp(App[None]): + # ... CSS definition and other parts remain the same ... + # Note: truncated here for brevity + + def __init__(self) -> None: + super().__init__() + self.last_audio_item_id = None + self.should_send_audio = asyncio.Event() + self.connected = asyncio.Event() + + # Create run configuration + run_config = RunConfig() + + # Set up Azure OpenAI provider + run_config.model_provider = AzureOpenAIProvider() + + self.pipeline = VoicePipeline( + workflow=MyWorkflow(secret_word="dog", on_start=self._on_transcription), + run_config=run_config + ) + self._audio_input = StreamedAudioInput() + self.audio_player = sd.OutputStream( + samplerate=SAMPLE_RATE, + channels=CHANNELS, + dtype=FORMAT, + ) + + def _on_transcription(self, transcription: str) -> None: + try: + self.query_one("#bottom-pane", RichLog).write(f"Transcription: {transcription}") + except Exception: + pass + + # ... Rest of the class remains the same ... + + +if __name__ == "__main__": + # Print usage instructions + print("Azure OpenAI Voice Agent Example") + print("==============================") + print("This example requires Azure OpenAI credentials.") + print("Make sure you have set these environment variables:") + print("- AZURE_OPENAI_API_KEY: Your Azure OpenAI API key") + print("- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint URL") + print("- AZURE_OPENAI_API_VERSION: (Optional) API version") + print("- AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name") + print() + + app = RealtimeApp() + app.run() diff --git a/examples_OpenAPI/azure/voice/streamed/my_workflow.py b/examples_OpenAPI/azure/voice/streamed/my_workflow.py new file mode 100644 index 00000000..6a9a6e13 --- /dev/null +++ b/examples_OpenAPI/azure/voice/streamed/my_workflow.py @@ -0,0 +1,92 @@ +import random +import sys +import os +from collections.abc import AsyncIterator +from typing import Callable + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, Runner, TResponseInputItem, function_tool +from src.agents.extensions.handoff_prompt import prompt_with_handoff_instructions +from src.agents.voice import VoiceWorkflowBase, VoiceWorkflowHelper +from src.agents.model_settings import ModelSettings + +# Create Azure OpenAI model settings +azure_settings = ModelSettings( + provider="azure_openai", # Specify Azure OpenAI as the provider + temperature=0.7 # Optional: control creativity +) + +@function_tool +def get_weather(city: str) -> str: + """Get the weather for a given city.""" + print(f"[debug] get_weather called with city: {city}") + choices = ["sunny", "cloudy", "rainy", "snowy"] + return f"The weather in {city} is {random.choice(choices)}." + + +spanish_agent = Agent( + name="Spanish", + handoff_description="A spanish speaking agent.", + instructions=prompt_with_handoff_instructions( + "You're speaking to a human, so be polite and concise. Speak in Spanish.", + ), + model_settings=azure_settings, +) + +agent = Agent( + name="Assistant", + instructions=prompt_with_handoff_instructions( + "You're speaking to a human, so be polite and concise. If the user speaks in Spanish, handoff to the spanish agent.", + ), + model_settings=azure_settings, + handoffs=[spanish_agent], + tools=[get_weather], +) + + +class MyWorkflow(VoiceWorkflowBase): + def __init__(self, secret_word: str, on_start: Callable[[str], None]): + """ + Args: + secret_word: The secret word to guess. + on_start: A callback that is called when the workflow starts. The transcription + is passed in as an argument. + """ + self._input_history: list[TResponseInputItem] = [] + self._current_agent = agent + self._secret_word = secret_word.lower() + self._on_start = on_start + + async def run(self, transcription: str) -> AsyncIterator[str]: + self._on_start(transcription) + + # Add the transcription to the input history + self._input_history.append( + { + "role": "user", + "content": transcription, + } + ) + + # If the user guessed the secret word, do alternate logic + if self._secret_word in transcription.lower(): + yield "You guessed the secret word!" + self._input_history.append( + { + "role": "assistant", + "content": "You guessed the secret word!", + } + ) + return + + # Otherwise, run the agent + result = Runner.run_streamed(self._current_agent, self._input_history) + + async for chunk in VoiceWorkflowHelper.stream_text_from(result): + yield chunk + + # Update the input history and current agent + self._input_history = result.to_input_list() + self._current_agent = result.last_agent diff --git a/examples_OpenAPI/ollama/agent_patterns/README.md b/examples_OpenAPI/ollama/agent_patterns/README.md new file mode 100644 index 00000000..a3bd5270 --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/README.md @@ -0,0 +1,45 @@ +# Ollama Agent Pattern Examples + +This folder contains examples of various common agent patterns implemented using Ollama. These examples demonstrate how to use the agent SDK with Ollama models to build complex applications. + +## Running the Examples + +Each example can be run directly. For example: + +```bash +python routing.py +``` + +## Available Examples + +### routing.py - Routing and Triage + +This example demonstrates how to route requests to different specialized agents based on the language of the user's request. A front-line triage agent receives requests and then hands off control to the appropriate language expert agent. + +### deterministic.py - Deterministic Workflow + +This example demonstrates how to create a deterministic multi-step workflow where each step is performed by a different agent. In this example, we have an agent generating a story outline, an agent checking the quality of the outline, and an agent writing a story based on the outline. + +### parallel.py - Parallel Execution + +This example demonstrates how to run multiple agent instances in parallel and then select the best result. This is useful for generating multiple alternatives or implementing some form of self-critique. + +### agents_as_tools.py - Agents as Tools + +This example demonstrates how to use agents as tools rather than via handoff. This allows an orchestrator agent to call other agents in a specific order and maintain control of the conversation. + +### llm_as_judge.py - LLM as Judge + +This example demonstrates how to use one LLM as a judge to evaluate content generated by another LLM. The first agent generates a story outline, and the second agent evaluates it and provides feedback until quality standards are met. + +### input_guardrails.py - Input Guardrails + +This example demonstrates how to use guardrails to ensure user input meets specific criteria. In this case, the guardrail is triggered when the user tries to ask for help with math homework. + +### output_guardrails.py - Output Guardrails + +This example demonstrates how to apply safety checks to model output, ensuring it doesn't contain sensitive information like phone numbers. + +### forcing_tool_use.py - Forcing Tool Use + +This example demonstrates how to configure an agent to force the use of tools and how to customize behavior after tool use. There are three modes to try: default mode, first tool result mode, and custom logic mode. diff --git a/examples_OpenAPI/ollama/agent_patterns/agents_as_tools.py b/examples_OpenAPI/ollama/agent_patterns/agents_as_tools.py new file mode 100644 index 00000000..863a5361 --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/agents_as_tools.py @@ -0,0 +1,126 @@ +import asyncio +import sys +import os + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, ItemHelpers, MessageOutputItem, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates the agent-as-tools pattern. The front-line agent receives user messages and decides which agents to call as tools. +In this example, it selects from a set of translation agents. +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + +spanish_agent = Agent( + name="spanish_agent", + instructions="Translate the user's message into Spanish", + handoff_description="An English-to-Spanish translator", + model_settings=create_ollama_settings() +) + +french_agent = Agent( + name="french_agent", + instructions="Translate the user's message into French", + handoff_description="An English-to-French translator", + model_settings=create_ollama_settings() +) + +italian_agent = Agent( + name="italian_agent", + instructions="Translate the user's message into Italian", + handoff_description="An English-to-Italian translator", + model_settings=create_ollama_settings() +) + +orchestrator_agent = Agent( + name="orchestrator_agent", + instructions=( + "You are a translation agent. You use the tools provided to you for translation. " + "If asked for multiple translations, you call the relevant tools in sequence. " + "You should never translate by yourself and always use the provided tools." + ), + tools=[ + spanish_agent.as_tool( + tool_name="translate_to_spanish", + tool_description="Translate the user's message into Spanish", + ), + french_agent.as_tool( + tool_name="translate_to_french", + tool_description="Translate the user's message into French", + ), + italian_agent.as_tool( + tool_name="translate_to_italian", + tool_description="Translate the user's message into Italian", + ), + ], + model_settings=create_ollama_settings() +) + +synthesizer_agent = Agent( + name="synthesizer_agent", + instructions="You review the translations, make corrections if necessary, and generate the final combined response.", + model_settings=create_ollama_settings() +) + + +async def main(): + msg = input("Hello! What would you like to translate and into which languages? ") + + print("Running agent-as-tools example with Ollama, please wait...") + + # Run the entire orchestration in a single trace + with trace("Orchestrator evaluator"): + orchestrator_result = await Runner.run( + orchestrator_agent, + msg, + run_config=run_config + ) + + for item in orchestrator_result.new_items: + if isinstance(item, MessageOutputItem): + text = ItemHelpers.text_message_output(item) + if text: + print(f" - Translation step: {text}") + + synthesizer_result = await Runner.run( + synthesizer_agent, + orchestrator_result.to_input_list(), + run_config=run_config + ) + + print(f"\n\nFinal response:\n{synthesizer_result.final_output}") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Please ensure the Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Unable to connect to Ollama service. Please ensure the Ollama service is running.\n{str(e)}") + print("\nIf you have not installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/deterministic.py b/examples_OpenAPI/ollama/agent_patterns/deterministic.py new file mode 100644 index 00000000..cdf43fec --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/deterministic.py @@ -0,0 +1,124 @@ +import asyncio +import sys +import os + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel + +from src.agents import Agent, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates a deterministic flow where each step is executed by an agent. +1. The first agent generates a story outline. +2. We input the outline to the second agent. +3. The second agent checks if the outline is high quality and whether it is a sci-fi story. +4. If the outline is not high quality or not a sci-fi story, we stop here. +5. If the outline is high quality and a sci-fi story, we input the outline to the third agent. +6. The third agent writes the story. +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + +story_outline_agent = Agent( + name="story_outline_agent", + instructions="Generate a very brief story outline based on the user's input.", + model_settings=create_ollama_settings() +) + + +class OutlineCheckerOutput(BaseModel): + good_quality: bool + is_scifi: bool + + +outline_checker_agent = Agent( + name="outline_checker_agent", + instructions="Read the given story outline and evaluate its quality. Also, determine if it is a sci-fi story.", + output_type=OutlineCheckerOutput, + model_settings=create_ollama_settings() +) + +story_agent = Agent( + name="story_agent", + instructions="Write a short story based on the given outline.", + output_type=str, + model_settings=create_ollama_settings() +) + + +async def main(): + input_prompt = input("What kind of story would you like? ") + + print("Running deterministic flow example with Ollama, please wait...") + + # Ensure the entire workflow is single-traced + with trace("Deterministic story flow"): + # 1. Generate outline + outline_result = await Runner.run( + story_outline_agent, + input_prompt, + run_config=run_config + ) + print("Outline generated") + print(f"\nStory Outline:\n{outline_result.final_output}\n") + + # 2. Check outline + outline_checker_result = await Runner.run( + outline_checker_agent, + outline_result.final_output, + run_config=run_config + ) + + # 3. Add gating to stop if the outline is not high quality or not a sci-fi story + assert isinstance(outline_checker_result.final_output, OutlineCheckerOutput) + if not outline_checker_result.final_output.good_quality: + print("The outline is not of high quality, stopping here.") + return + + if not outline_checker_result.final_output.is_scifi: + print("The outline is not a sci-fi story, stopping here.") + return + + print("The outline is of high quality and is a sci-fi story, proceeding to write the story.") + + # 4. Write story + story_result = await Runner.run( + story_agent, + outline_result.final_output, + run_config=run_config + ) + print(f"\nStory:\n{story_result.final_output}") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Unable to connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you have not installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/forcing_tool_use.py b/examples_OpenAPI/ollama/agent_patterns/forcing_tool_use.py new file mode 100644 index 00000000..fe066b71 --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/forcing_tool_use.py @@ -0,0 +1,138 @@ +import asyncio +import sys +import os +from typing import Any, Literal + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel + +from src.agents import ( + Agent, + FunctionToolResult, + ModelSettings, + RunContextWrapper, + Runner, + ToolsToFinalOutputFunction, + ToolsToFinalOutputResult, + function_tool, +) +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates how to force an agent to use tools. It uses `ModelSettings(tool_choice="required")` +to force the agent to use any tool. + +You can run it with 3 options: +1. `default`: Default behavior, sending tool output to the LLM. In this case, + `tool_choice` is not set, as it would otherwise cause an infinite loop - the LLM would call + the tool, the tool would run and send the result to the LLM, which would repeat (since the model + is forced to use a tool each time.) +2. `first_tool`: The first tool result is used as the final output. +3. `custom`: Use a custom tool use behavior function. The custom function receives all the tool results, + and chooses to generate the final output using the first tool result. + +Usage: +python forcing_tool_use.py -t default +python forcing_tool_use.py -t first_tool +python forcing_tool_use.py -t custom +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + + +class Weather(BaseModel): + city: str + temperature_range: str + conditions: str + + +@function_tool +def get_weather(city: str) -> Weather: + print("[Debug] get_weather called") + return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind") + + +async def custom_tool_use_behavior( + context: RunContextWrapper[Any], results: list[FunctionToolResult] +) -> ToolsToFinalOutputResult: + weather: Weather = results[0].output + return ToolsToFinalOutputResult( + is_final_output=True, final_output=f"The weather in {weather.city} is {weather.conditions}." + ) + + +async def main(tool_use_behavior: Literal["default", "first_tool", "custom"] = "default"): + print(f"Running forcing tool use example with Ollama, mode: {tool_use_behavior}") + + if tool_use_behavior == "default": + behavior: Literal["run_llm_again", "stop_on_first_tool"] | ToolsToFinalOutputFunction = ( + "run_llm_again" + ) + elif tool_use_behavior == "first_tool": + behavior = "stop_on_first_tool" + elif tool_use_behavior == "custom": + behavior = custom_tool_use_behavior + + # tool_choice is not needed in default mode as it would cause an infinite loop + settings = create_ollama_settings() + if tool_use_behavior != "default": + settings.tool_choice = "required" + + agent = Agent( + name="Weather Agent", + instructions="You are a helpful agent.", + tools=[get_weather], + tool_use_behavior=behavior, + model_settings=settings + ) + + result = await Runner.run(agent, input="What's the weather in Tokyo?", run_config=run_config) + print(f"Result: {result.final_output}") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Make sure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Could not connect to Ollama service. Make sure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama yet, download and install it from https://ollama.ai and start the service with 'ollama serve'") + sys.exit(1) + + # Parse command line arguments + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument( + "-t", + "--tool-use-behavior", + type=str, + default="default", + choices=["default", "first_tool", "custom"], + help="Tool use behavior. default sends tool output to the model. " + "first_tool uses the first tool result as the final output. " + "custom uses a custom tool use behavior function.", + ) + args = parser.parse_args() + + # Run the main function + asyncio.run(main(args.tool_use_behavior)) diff --git a/examples_OpenAPI/ollama/agent_patterns/input_guardrails.py b/examples_OpenAPI/ollama/agent_patterns/input_guardrails.py new file mode 100644 index 00000000..2224f0b9 --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/input_guardrails.py @@ -0,0 +1,138 @@ +import asyncio +import sys +import os + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel + +from src.agents import ( + Agent, + GuardrailFunctionOutput, + InputGuardrailTripwireTriggered, + RunContextWrapper, + Runner, + TResponseInputItem, + input_guardrail, +) +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates how to use input guardrails. + +Guardrails are checks that run in parallel with agent execution. They can be used to: +- Check if input messages are off-topic +- Check if output messages violate any policies +- Take over control of agent execution if unexpected input is detected + +In this example, we set up an input guardrail that triggers when the user asks for help +with math homework. If the guardrail is triggered, we respond with a rejection message. +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + +### 1. Agent-based guardrail that triggers when user asks for math homework help +class MathHomeworkOutput(BaseModel): + reasoning: str + is_math_homework: bool + + +guardrail_agent = Agent( + name="Guardrail check", + instructions="Check if the user is asking you to do their math homework.", + output_type=MathHomeworkOutput, + model_settings=create_ollama_settings() +) + + +@input_guardrail +async def math_guardrail( + context: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem] +) -> GuardrailFunctionOutput: + """This is an input guardrail function that calls an agent to check if the input is a math homework question.""" + result = await Runner.run(guardrail_agent, input, context=context.context, run_config=run_config) + final_output = result.final_output_as(MathHomeworkOutput) + + return GuardrailFunctionOutput( + output_info=final_output, + tripwire_triggered=final_output.is_math_homework, + ) + + +### 2. The run loop + + +async def main(): + agent = Agent( + name="Customer Support Agent", + instructions="You are a customer support agent. You help customers answer their questions.", + input_guardrails=[math_guardrail], + model_settings=create_ollama_settings() + ) + + input_data: list[TResponseInputItem] = [] + + print("Running Input Guardrails Example with Ollama") + print("Try asking normal questions, then try asking math homework questions (like 'help me solve the equation: 2x + 5 = 11')") + print("Enter 'exit' to quit") + + while True: + user_input = input("\nEnter a message: ") + if user_input.lower() == 'exit': + break + + input_data.append( + { + "role": "user", + "content": user_input, + } + ) + + print("Processing...") + try: + result = await Runner.run(agent, input_data, run_config=run_config) + print(result.final_output) + # If guardrail wasn't triggered, use the result for the next run + input_data = result.to_input_list() + except InputGuardrailTripwireTriggered: + # If guardrail triggers, add a rejection message to the input + message = "Sorry, I cannot help with math homework." + print(message) + input_data.append( + { + "role": "assistant", + "content": message, + } + ) + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Make sure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Could not connect to Ollama service. Make sure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama yet, download and install it from https://ollama.ai and start the service with 'ollama serve'") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/llm_as_judge.py b/examples_OpenAPI/ollama/agent_patterns/llm_as_judge.py new file mode 100644 index 00000000..1a48de1a --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/llm_as_judge.py @@ -0,0 +1,116 @@ +import asyncio +import sys +import os +from dataclasses import dataclass +from typing import Literal + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, ItemHelpers, Runner, TResponseInputItem, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates the LLM as a judge pattern. The first agent generates a story outline, +the second agent evaluates the outline and provides feedback. We loop until the judge is satisfied. +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + +story_outline_generator = Agent( + name="story_outline_generator", + instructions=( + "You generate a very short story outline based on the user's input." + "If any feedback is provided, use it to improve the outline." + ), + model_settings=create_ollama_settings() +) + + +@dataclass +class EvaluationFeedback: + feedback: str + score: Literal["pass", "needs_improvement", "fail"] + + +evaluator = Agent[None]( + name="evaluator", + instructions=( + "You evaluate a story outline and decide if it's good enough." + "If it's not, you provide feedback on what needs to be improved." + "Never give it a pass on the first try." + ), + output_type=EvaluationFeedback, + model_settings=create_ollama_settings() +) + + +async def main() -> None: + msg = input("What kind of story would you like to hear? ") + input_items: list[TResponseInputItem] = [{"content": msg, "role": "user"}] + + latest_outline: str | None = None + + print("Running LLM as a judge example with Ollama, please wait...") + + # We run the entire workflow in a single trace + with trace("LLM as a judge"): + iteration = 1 + while True: + print(f"\n--- Iteration {iteration} ---") + story_outline_result = await Runner.run( + story_outline_generator, + input_items, + run_config=run_config + ) + + input_items = story_outline_result.to_input_list() + latest_outline = ItemHelpers.text_message_outputs(story_outline_result.new_items) + print(f"Generated story outline:\n{latest_outline}") + + evaluator_result = await Runner.run(evaluator, input_items, run_config=run_config) + result: EvaluationFeedback = evaluator_result.final_output + + print(f"Evaluation result: {result.score}") + print(f"Evaluation feedback: {result.feedback}") + + if result.score == "pass": + print("Story outline is good enough, exiting loop.") + break + + print("Running again with feedback...") + input_items.append({"content": f"Feedback: {result.feedback}", "role": "user"}) + iteration += 1 + + print(f"\nFinal story outline:\n{latest_outline}") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Make sure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Could not connect to Ollama service. Make sure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama yet, download and install it from https://ollama.ai and start the service with 'ollama serve'") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/output_guardrails.py b/examples_OpenAPI/ollama/agent_patterns/output_guardrails.py new file mode 100644 index 00000000..6173bb2a --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/output_guardrails.py @@ -0,0 +1,116 @@ +import asyncio +import sys +import os +import json + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from pydantic import BaseModel, Field + +from src.agents import ( + Agent, + GuardrailFunctionOutput, + OutputGuardrailTripwireTriggered, + RunContextWrapper, + Runner, + output_guardrail, +) +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates how to use output guardrails. + +Output guardrails are checks run on the agent's final output. They can be used to: +- Check if the output contains sensitive data +- Check if the output is a valid response to the user's message + +In this example, we use a (contrived) example to check if the agent's response contains a phone number. +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + +# Output type for the agent +class MessageOutput(BaseModel): + reasoning: str = Field(description="Thoughts about how to respond to the user's message") + response: str = Field(description="Response to the user's message") + user_name: str | None = Field(description="Name of the user who sent the message, if known") + + +@output_guardrail +async def sensitive_data_check( + context: RunContextWrapper, agent: Agent, output: MessageOutput +) -> GuardrailFunctionOutput: + phone_number_in_response = "650" in output.response + phone_number_in_reasoning = "650" in output.reasoning + + return GuardrailFunctionOutput( + output_info={ + "phone_number_in_response": phone_number_in_response, + "phone_number_in_reasoning": phone_number_in_reasoning, + }, + tripwire_triggered=phone_number_in_response or phone_number_in_reasoning, + ) + + +agent = Agent( + name="Assistant", + instructions="You are a helpful assistant.", + output_type=MessageOutput, + output_guardrails=[sensitive_data_check], + model_settings=create_ollama_settings() +) + + +async def main(): + print("Running Output Guardrails Example with Ollama") + + # This should be fine + print("Testing normal question...") + result1 = await Runner.run(agent, "What is the capital of California?", run_config=run_config) + print("First message passed") + print(f"Output: {json.dumps(result1.final_output.model_dump(), indent=2, ensure_ascii=False)}") + + print("\nTesting question with phone number...") + # This should trigger the guardrail + try: + result2 = await Runner.run( + agent, "My phone number is 650-123-4567. Where do you think I live?", run_config=run_config + ) + print( + f"Guardrail not triggered - this is unexpected. Output: {json.dumps(result2.final_output.model_dump(), indent=2, ensure_ascii=False)}" + ) + + except OutputGuardrailTripwireTriggered as e: + print(f"Guardrail triggered. Info: {e.guardrail_result.output.output_info}") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Make sure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Could not connect to Ollama service. Make sure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama yet, download and install it from https://ollama.ai and start the service with 'ollama serve'") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/parallel.py b/examples_OpenAPI/ollama/agent_patterns/parallel.py new file mode 100644 index 00000000..6bd7039a --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/parallel.py @@ -0,0 +1,103 @@ +import asyncio +import sys +import os + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from src.agents import Agent, ItemHelpers, Runner, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates the parallelization pattern. We run an agent three times in parallel and select the best result. +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + +spanish_agent = Agent( + name="spanish_agent", + instructions="Translate the user's message into Spanish", + model_settings=create_ollama_settings() +) + +translation_picker = Agent( + name="translation_picker", + instructions="Select the best Spanish translation from the given options.", + model_settings=create_ollama_settings() +) + + +async def main(): + msg = input("Hi! Enter a message, and we will translate it into Spanish.\n\n") + + print("Using Ollama to run multiple translations in parallel, please wait...") + + # Ensure the entire workflow is a single trace + with trace("Parallel translation"): + res_1, res_2, res_3 = await asyncio.gather( + Runner.run( + spanish_agent, + msg, + run_config=run_config + ), + Runner.run( + spanish_agent, + msg, + run_config=run_config + ), + Runner.run( + spanish_agent, + msg, + run_config=run_config + ), + ) + + outputs = [ + ItemHelpers.text_message_outputs(res_1.new_items), + ItemHelpers.text_message_outputs(res_2.new_items), + ItemHelpers.text_message_outputs(res_3.new_items), + ] + + translations = "\n\n".join(outputs) + print(f"\n\nTranslation results:\n\n{translations}") + + best_translation = await Runner.run( + translation_picker, + f"Input: {msg}\n\nTranslations:\n{translations}", + run_config=run_config + ) + + print("\n\n-----") + + print(f"Best translation: {best_translation.final_output}") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Please ensure the Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Unable to connect to Ollama service. Please ensure the Ollama service is running.\n{str(e)}") + print("\nIf you have not installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service.") + sys.exit(1) + + # Run main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/routing.py b/examples_OpenAPI/ollama/agent_patterns/routing.py new file mode 100644 index 00000000..acf38e51 --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/routing.py @@ -0,0 +1,120 @@ +import asyncio +import sys +import os +import uuid + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +from openai.types.responses import ResponseContentPartDoneEvent, ResponseTextDeltaEvent + +from src.agents import Agent, RawResponsesStreamEvent, Runner, TResponseInputItem, trace +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +""" +This example demonstrates the triage/routing pattern. A triage agent receives the first message, +and then hands off to the appropriate agent based on the language of the request. +Responses are streamed to the user. +""" + +def create_ollama_settings(model="phi3:latest"): + """Create Ollama model settings""" + return ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model=model, + temperature=0.7 + ) + +# Create run configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(create_ollama_settings()) + +french_agent = Agent( + name="french_agent", + instructions="You only speak French", + model_settings=create_ollama_settings() +) + +spanish_agent = Agent( + name="spanish_agent", + instructions="You only speak Spanish", + model_settings=create_ollama_settings() +) + +english_agent = Agent( + name="english_agent", + instructions="You only speak English", + model_settings=create_ollama_settings() +) + +triage_agent = Agent( + name="triage_agent", + instructions="Handoff to the appropriate agent based on the language of the request.", + handoffs=[french_agent, spanish_agent, english_agent], + model_settings=create_ollama_settings() +) + + +async def main(): + # We create an ID for this conversation to link each trace + conversation_id = str(uuid.uuid4().hex[:16]) + + print("Welcome to the multilingual assistant! We offer French, Spanish, and English services.") + print("Enter your question (type 'exit' to quit):") + msg = input("> ") + + if msg.lower() == 'exit': + return + + agent = triage_agent + inputs: list[TResponseInputItem] = [{"content": msg, "role": "user"}] + + while True: + # Each turn in the conversation is a single trace. Typically, each input from a user + # is an API request to your application, which you would wrap in trace() + with trace("Routing example", group_id=conversation_id): + result = Runner.run_streamed( + agent, + input=inputs, + run_config=run_config + ) + print("\nResponse: ", end="", flush=True) + async for event in result.stream_events(): + if not isinstance(event, RawResponsesStreamEvent): + continue + data = event.data + if isinstance(data, ResponseTextDeltaEvent): + print(data.delta, end="", flush=True) + elif isinstance(data, ResponseContentPartDoneEvent): + print("\n") + + inputs = result.to_input_list() + print("\n") + + user_msg = input("> ") + if user_msg.lower() == 'exit': + break + + inputs.append({"content": user_msg, "role": "user"}) + agent = result.current_agent + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Make sure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Could not connect to Ollama service. Make sure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama yet, download and install it from https://ollama.ai and start the service with 'ollama serve'") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/run_all.py b/examples_OpenAPI/ollama/agent_patterns/run_all.py new file mode 100644 index 00000000..7207a50c --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/run_all.py @@ -0,0 +1,123 @@ +import sys +import os +import importlib +import asyncio + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +# List of available examples +EXAMPLES = { + "routing": "Routing/Triage Example - Automatically select specialized agents based on language", + "deterministic": "Deterministic Workflow Example - Execute multiple steps in sequence", + "parallel": "Parallel Execution Example - Generate multiple results in parallel and select the best", + "agents_as_tools": "Agents as Tools Example - Use other agents as tools", + "llm_as_judge": "LLM as Judge Example - Use one agent to evaluate another's output", + "input_guardrails": "Input Guardrails Example - Detect inappropriate user input", + "output_guardrails": "Output Guardrails Example - Ensure model output doesn't contain sensitive data", + "forcing_tool_use": "Forcing Tool Use Example - Ensure model uses tools", +} + +# Special options for forcing_tool_use +TOOL_BEHAVIORS = ["default", "first_tool", "custom"] + +def check_ollama_running(): + """Check if Ollama service is running""" + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Make sure Ollama service is running.") + return False + except Exception as e: + print(f"Error: Could not connect to Ollama service. Make sure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama yet, download and install it from https://ollama.ai and start the service with 'ollama serve'") + return False + return True + +async def run_example(example_name): + """Run a single example""" + print(f"\n\n{'='*60}") + print(f" Running example: {example_name} - {EXAMPLES[example_name]}") + print(f"{'='*60}") + + try: + # Special handling for forcing_tool_use example + if example_name == "forcing_tool_use": + # Run all three modes for forcing_tool_use + for behavior in TOOL_BEHAVIORS: + print(f"\n>> Using mode: {behavior}") + module = importlib.import_module(example_name) + await module.main(behavior) + else: + # Import and run example + module = importlib.import_module(example_name) + await module.main() + return True + except Exception as e: + print(f"Error running {example_name}: {str(e)}") + return False + +async def main(): + # Check Ollama service + if not check_ollama_running(): + return + + print("\n===== Ollama Agent Pattern Examples Runner Tool =====\n") + print("Options:") + print("1. Run all examples") + print("2. Select a single example to run") + + choice = input("\nSelect operation (1/2): ") + + if choice == "1": + # Run all examples + print("\nWill run all examples in sequence...") + successes = 0 + failures = 0 + + for example_name in EXAMPLES.keys(): + success = await run_example(example_name) + if success: + successes += 1 + else: + failures += 1 + + # Pause between examples if not the last one + if example_name != list(EXAMPLES.keys())[-1]: + input("\nPress Enter to continue to the next example...") + + print(f"\n\nAll examples completed. Successes: {successes}, Failures: {failures}") + + elif choice == "2": + # Display available examples + for i, (name, desc) in enumerate(EXAMPLES.items(), 1): + print(f"{i}. {name}: {desc}") + + # Get user selection + while True: + try: + ex_choice = input("\nSelect an example to run (number or name, q to quit): ") + if ex_choice.lower() == 'q': + return + + # Handle numeric input + if ex_choice.isdigit() and 1 <= int(ex_choice) <= len(EXAMPLES): + example_name = list(EXAMPLES.keys())[int(ex_choice) - 1] + await run_example(example_name) + break + + # Handle name input + if ex_choice in EXAMPLES: + await run_example(ex_choice) + break + + print("Invalid selection, please try again.") + except (ValueError, IndexError): + print("Invalid selection, please try again.") + + else: + print("Invalid choice") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/agent_patterns/run_example.py b/examples_OpenAPI/ollama/agent_patterns/run_example.py new file mode 100644 index 00000000..4acb8cdd --- /dev/null +++ b/examples_OpenAPI/ollama/agent_patterns/run_example.py @@ -0,0 +1,103 @@ +import sys +import os +import importlib +import asyncio + +# Add project root path to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))) + +# List of available examples +EXAMPLES = { + "routing": "Routing/Triage Example - Automatically select specialized agents based on language", + "deterministic": "Deterministic Workflow Example - Execute multiple steps in sequence", + "parallelization": "Parallel Execution Example - Generate multiple results in parallel and select the best", + "agents_as_tools": "Agents as Tools Example - Use other agents as tools", + "llm_as_judge": "LLM as Judge Example - Use one agent to evaluate another's output", + "input_guardrails": "Input Guardrails Example - Detect inappropriate user input", + "output_guardrails": "Output Guardrails Example - Ensure model output doesn't contain sensitive data", + "forcing_tool_use": "Forcing Tool Use Example - Ensure model uses tools", +} + +def check_ollama_running(): + """Check if Ollama service is running""" + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned a non-200 status code. Make sure Ollama service is running.") + return False + except Exception as e: + print(f"Error: Could not connect to Ollama service. Make sure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama yet, download and install it from https://ollama.ai and start the service with 'ollama serve'") + return False + return True + +async def main(): + # Check Ollama service + if not check_ollama_running(): + return + + # Display available examples + print("\n===== Ollama Agent Pattern Examples =====\n") + for i, (name, desc) in enumerate(EXAMPLES.items(), 1): + print(f"{i}. {name}: {desc}") + + # Get user selection + while True: + try: + choice = input("\nSelect an example to run (number or name, q to quit): ") + if choice.lower() == 'q': + return + + # Handle numeric input + if choice.isdigit() and 1 <= int(choice) <= len(EXAMPLES): + example_name = list(EXAMPLES.keys())[int(choice) - 1] + break + + # Handle name input + if choice in EXAMPLES: + example_name = choice + break + + print("Invalid selection, please try again.") + except (ValueError, IndexError): + print("Invalid selection, please try again.") + + print(f"\nStarting example: {example_name}") + print("=" * 50) + + # Handle special case for forcing tool use + if example_name == "forcing_tool_use": + # Import module + module = importlib.import_module(example_name) + # Get available tool use modes + tool_behaviors = ["default", "first_tool", "custom"] + print("Forcing Tool Use example has the following modes:") + for i, behavior in enumerate(tool_behaviors, 1): + print(f"{i}. {behavior}") + + # Get user selection for mode + while True: + try: + behavior_choice = input("\nSelect mode (number or name): ") + if behavior_choice.isdigit() and 1 <= int(behavior_choice) <= len(tool_behaviors): + behavior = tool_behaviors[int(behavior_choice) - 1] + break + if behavior_choice in tool_behaviors: + behavior = behavior_choice + break + print("Invalid selection, please try again.") + except (ValueError, IndexError): + print("Invalid selection, please try again.") + + # Run example + await module.main(behavior) + else: + # Import and run example + module = importlib.import_module(example_name) + await module.main() + + print("\nExample execution complete.") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/agent_lifecycle_example.py b/examples_OpenAPI/ollama/basic/agent_lifecycle_example.py new file mode 100644 index 00000000..8040c78b --- /dev/null +++ b/examples_OpenAPI/ollama/basic/agent_lifecycle_example.py @@ -0,0 +1,129 @@ +import asyncio +import sys +import os +import random +from typing import Any + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from pydantic import BaseModel + +from src.agents import Agent, AgentHooks, RunContextWrapper, Runner, Tool, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + + +class CustomAgentHooks(AgentHooks): + def __init__(self, display_name: str): + self.event_counter = 0 + self.display_name = display_name + + async def on_start(self, context: RunContextWrapper, agent: Agent) -> None: + self.event_counter += 1 + print(f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started") + + async def on_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} ended with output {output}" + ) + + async def on_handoff(self, context: RunContextWrapper, agent: Agent, source: Agent) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {source.name} handed off to {agent.name}" + ) + + async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} started tool {tool.name}" + ) + + async def on_tool_end( + self, context: RunContextWrapper, agent: Agent, tool: Tool, result: str + ) -> None: + self.event_counter += 1 + print( + f"### ({self.display_name}) {self.event_counter}: Agent {agent.name} ended tool {tool.name} with result {result}" + ) + + +@function_tool +def random_number(max: int) -> int: + """ + Generate a random number up to the provided maximum. + """ + return random.randint(0, max) + + +@function_tool +def multiply_by_two(x: int) -> int: + """Simple multiplication by two.""" + return x * 2 + + +class FinalResult(BaseModel): + number: int + + +async def main() -> None: + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2", + temperature=0.7 + ) + # Create runtime configuration + run_config = RunConfig(tracing_disabled=True) + # Set model provider + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + multiply_agent = Agent( + name="Multiply Agent", + instructions="Multiply the number by 2 and then return the final result.", + tools=[multiply_by_two], + output_type=FinalResult, + hooks=CustomAgentHooks(display_name="Multiply Agent"), + model_settings=ollama_settings + ) + + start_agent = Agent( + name="Start Agent", + instructions="Generate a random number. If it's even, stop. If it's odd, hand off to the multiply agent.", + tools=[random_number], + output_type=FinalResult, + handoffs=[multiply_agent], + hooks=CustomAgentHooks(display_name="Start Agent"), + model_settings=ollama_settings + ) + + print("Running agent lifecycle example with Ollama, please wait...") + user_input = input("Enter a max number: ") + await Runner.run( + start_agent, + input=f"Generate a random number between 0 and {user_input}.", + run_config=run_config + ) + + print("Done!") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Cannot connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/dynamic_system_prompt.py b/examples_OpenAPI/ollama/basic/dynamic_system_prompt.py new file mode 100644 index 00000000..c7831a4d --- /dev/null +++ b/examples_OpenAPI/ollama/basic/dynamic_system_prompt.py @@ -0,0 +1,78 @@ +import asyncio +import sys +import os +import random +from typing import Literal + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from src.agents import Agent, RunContextWrapper, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + + +class CustomContext: + def __init__(self, style: Literal["haiku", "pirate", "robot"]): + self.style = style + + +def custom_instructions( + run_context: RunContextWrapper[CustomContext], agent: Agent[CustomContext] +) -> str: + context = run_context.context + if context.style == "haiku": + return "Only respond in haikus." + elif context.style == "pirate": + return "Respond as a pirate." + else: + return "Respond as a robot and say 'beep boop' a lot." + + +async def main(): + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2", + temperature=0.7 + ) + # Create runtime configuration + run_config = RunConfig(tracing_disabled=True) + # Set model provider + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + agent = Agent( + name="Chat agent", + instructions=custom_instructions, + model_settings=ollama_settings + ) + + choice: Literal["haiku", "pirate", "robot"] = random.choice(["haiku", "pirate", "robot"]) + context = CustomContext(style=choice) + print(f"Using style: {choice}\n") + + user_message = "Tell me a joke." + print(f"User: {user_message}") + print("Running with Ollama, please wait...") + result = await Runner.run(agent, user_message, context=context, run_config=run_config) + + print(f"Assistant: {result.final_output}") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Cannot connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/hello_world.py b/examples_OpenAPI/ollama/basic/hello_world.py new file mode 100644 index 00000000..7dd050b5 --- /dev/null +++ b/examples_OpenAPI/ollama/basic/hello_world.py @@ -0,0 +1,64 @@ +import asyncio +import sys +import os + +# Add project root to Python path to ensure src package can be imported +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +# Import required modules +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +async def main(): + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", # Specify Ollama as the provider + ollama_base_url="http://localhost:11434", # Ollama service address + ollama_default_model="llama3.2", # Use phi4 model + temperature=0.7 # Optional: control creativity + ) + # Create run configuration + run_config = RunConfig(tracing_disabled=True) # Disable tracing for simplicity + # Set model provider + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + # Create Agent instance + agent = Agent( + name="Assistant", + instructions="You only respond in haikus.", + model_settings=ollama_settings # Use Ollama settings + ) + + # Run Agent + print("Running Agent, please wait...") + result = await Runner.run( + agent, + "Tell me about recursion in programming.", + run_config=run_config + ) + + # Print results + print("\nResult:") + print(result.final_output) + # Expected output similar to: + # Function calls itself, + # Looping in smaller pieces, + # Endless by design. + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Cannot connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/hello_world_jupyter.py b/examples_OpenAPI/ollama/basic/hello_world_jupyter.py new file mode 100644 index 00000000..799628df --- /dev/null +++ b/examples_OpenAPI/ollama/basic/hello_world_jupyter.py @@ -0,0 +1,42 @@ +import sys +import os +import asyncio + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))) + +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +# Create Ollama model settings +ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2", + temperature=0.7, +) +# Create runtime configuration +run_config = RunConfig(tracing_disabled=True) +# Set model provider +run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + +agent = Agent( + name="Assistant", + instructions="You are a helpful assistant", + model_settings=ollama_settings, +) + +print("Running with Ollama, please wait...") +# Intended for Jupyter notebooks where there's an existing event loop + +async def main(): + result = await Runner.run( + agent, "Write a haiku about recursion in programming.", run_config=run_config + ) # type: ignore[top-level-await] # noqa: F704 + print(result.final_output) + + +# Run the async function +asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/lifecycle_example.py b/examples_OpenAPI/ollama/basic/lifecycle_example.py new file mode 100644 index 00000000..e690b12b --- /dev/null +++ b/examples_OpenAPI/ollama/basic/lifecycle_example.py @@ -0,0 +1,135 @@ +import asyncio +import sys +import os +import random +from typing import Any + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from pydantic import BaseModel + +from src.agents import Agent, RunContextWrapper, RunHooks, Runner, Tool, Usage, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + + +class ExampleHooks(RunHooks): + def __init__(self): + self.event_counter = 0 + + def _usage_to_str(self, usage: Usage) -> str: + return f"{usage.requests} requests, {usage.input_tokens} input tokens, {usage.output_tokens} output tokens, {usage.total_tokens} total tokens" + + async def on_agent_start(self, context: RunContextWrapper, agent: Agent) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Agent {agent.name} started. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_agent_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Agent {agent.name} ended with output {output}. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_tool_start(self, context: RunContextWrapper, agent: Agent, tool: Tool) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Tool {tool.name} started. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_tool_end( + self, context: RunContextWrapper, agent: Agent, tool: Tool, result: str + ) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Tool {tool.name} ended with result {result}. Usage: {self._usage_to_str(context.usage)}" + ) + + async def on_handoff( + self, context: RunContextWrapper, from_agent: Agent, to_agent: Agent + ) -> None: + self.event_counter += 1 + print( + f"### {self.event_counter}: Handoff from {from_agent.name} to {to_agent.name}. Usage: {self._usage_to_str(context.usage)}" + ) + + +hooks = ExampleHooks() + + +@function_tool +def random_number(max: int) -> int: + """Generate a random number up to the provided max.""" + return random.randint(0, max) + + +@function_tool +def multiply_by_two(x: int) -> int: + """Return x times two.""" + return x * 2 + + +class FinalResult(BaseModel): + number: int + + +async def main() -> None: + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2", + temperature=0.7 + ) + # Create runtime configuration + run_config = RunConfig(tracing_disabled=True) + # Set model provider + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + multiply_agent = Agent( + name="Multiply Agent", + instructions="Multiply the number by 2 and then return the final result.", + tools=[multiply_by_two], + output_type=FinalResult, + model_settings=ollama_settings + ) + + start_agent = Agent( + name="Start Agent", + instructions="Generate a random number and then invoke the Multiply Agent.", + tools=[random_number], + output_type=FinalResult, + handoffs=[multiply_agent], + model_settings=ollama_settings + ) + + print("Running lifecycle example with Ollama, please wait...") + user_input = input("Enter a max number: ") + await Runner.run( + start_agent, + hooks=hooks, + input=f"Generate a random number between 0 and {user_input}.", + run_config=run_config + ) + + print("Done!") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Cannot connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/stream_items.py b/examples_OpenAPI/ollama/basic/stream_items.py new file mode 100644 index 00000000..399f4b6e --- /dev/null +++ b/examples_OpenAPI/ollama/basic/stream_items.py @@ -0,0 +1,81 @@ +import asyncio +import sys +import os +import random + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from src.agents import Agent, ItemHelpers, Runner, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + + +@function_tool +def how_many_jokes() -> int: + return random.randint(1, 10) + + +async def main(): + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2", + temperature=0.7 + ) + # Create runtime configuration + run_config = RunConfig(tracing_disabled=True) + # Set model provider + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + agent = Agent( + name="Joker", + instructions="First call the `how_many_jokes` tool, then tell that many jokes.", + tools=[how_many_jokes], + model_settings=ollama_settings + ) + + print("Streaming items using Ollama, please wait...") + result = Runner.run_streamed( + agent, + input="Hello", + run_config=run_config + ) + print("=== Run starting ===") + async for event in result.stream_events(): + # We'll ignore the raw responses event deltas + if event.type == "raw_response_event": + continue + elif event.type == "agent_updated_stream_event": + print(f"Agent updated: {event.new_agent.name}") + continue + elif event.type == "run_item_stream_event": + if event.item.type == "tool_call_item": + print("-- Tool was called") + elif event.item.type == "tool_call_output_item": + print(f"-- Tool output: {event.item.output}") + elif event.item.type == "message_output_item": + print(f"-- Message output:\n {ItemHelpers.text_message_output(event.item)}") + else: + pass # Ignore other event types + + print("=== Run complete ===") + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Cannot connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/stream_text.py b/examples_OpenAPI/ollama/basic/stream_text.py new file mode 100644 index 00000000..6f68e632 --- /dev/null +++ b/examples_OpenAPI/ollama/basic/stream_text.py @@ -0,0 +1,60 @@ +import asyncio +import sys +import os + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from openai.types.responses import ResponseTextDeltaEvent + +from src.agents import Agent, Runner +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + + +async def main(): + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="llama3.2", + temperature=0.7 + ) + # Create runtime configuration + run_config = RunConfig(tracing_disabled=True) + # Set model provider + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + agent = Agent( + name="Joker", + instructions="You are a helpful assistant.", + model_settings=ollama_settings + ) + + print("Streaming jokes using Ollama, please wait...") + result = Runner.run_streamed( + agent, + input="Please tell me 5 jokes.", + run_config=run_config + ) + async for event in result.stream_events(): + if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent): + print(event.data.delta, end="", flush=True) + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Cannot connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/examples_OpenAPI/ollama/basic/tools.py b/examples_OpenAPI/ollama/basic/tools.py new file mode 100644 index 00000000..94e81370 --- /dev/null +++ b/examples_OpenAPI/ollama/basic/tools.py @@ -0,0 +1,71 @@ +import asyncio +import sys +import os + +# Add project root to Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..'))) + +from pydantic import BaseModel + +from src.agents import Agent, Runner, function_tool +from src.agents.model_settings import ModelSettings +from src.agents.run import RunConfig +from src.agents.models.provider_factory import ModelProviderFactory + +class Weather(BaseModel): + city: str + temperature_range: str + conditions: str + + +@function_tool +def get_weather(city: str) -> Weather: + print("[debug] get_weather called") + return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.") + + +async def main(): + # Create Ollama model settings + ollama_settings = ModelSettings( + provider="ollama", + ollama_base_url="http://localhost:11434", + ollama_default_model="PetrosStav/gemma3-tools:4b", + temperature=0.7 + ) + # Create runtime configuration + run_config = RunConfig(tracing_disabled=True) + # Set model provider + run_config.model_provider = ModelProviderFactory.create_provider(ollama_settings) + + agent = Agent( + name="Hello world", + instructions="You are a helpful agent.", + tools=[get_weather], + model_settings=ollama_settings + ) + + print("Running Agent with Ollama, please wait...") + result = await Runner.run( + agent, + input="What's the weather in Tokyo?", + run_config=run_config + ) + print("\nResult:") + print(result.final_output) + + +if __name__ == "__main__": + # Check if Ollama service is running + import httpx + try: + response = httpx.get("http://localhost:11434/api/tags") + if response.status_code != 200: + print("Error: Ollama service returned non-200 status code. Please ensure Ollama service is running.") + sys.exit(1) + except Exception as e: + print(f"Error: Cannot connect to Ollama service. Please ensure Ollama service is running.\n{str(e)}") + print("\nIf you haven't installed Ollama, please download and install it from https://ollama.ai, then run 'ollama serve' to start the service") + sys.exit(1) + + # Run the main function + asyncio.run(main()) diff --git a/src/agents/model_settings.py b/src/agents/model_settings.py index cc4b6cb6..e9cfb791 100644 --- a/src/agents/model_settings.py +++ b/src/agents/model_settings.py @@ -15,6 +15,15 @@ class ModelSettings: for the specific model and provider you are using. """ + provider: Literal["openai", "ollama"] = "openai" + """The provider to use for the model.""" + + ollama_base_url: str = "http://localhost:11434" + """The base URL for the Ollama API.""" + + ollama_default_model: str = "llama3.2" + """The default model to use for Ollama.""" + temperature: float | None = None """The temperature to use when calling the model.""" @@ -45,6 +54,9 @@ def resolve(self, override: ModelSettings | None) -> ModelSettings: if override is None: return self return ModelSettings( + provider=override.provider or self.provider, + ollama_base_url=override.ollama_base_url or self.ollama_base_url, + ollama_default_model=override.ollama_default_model or self.ollama_default_model, temperature=override.temperature or self.temperature, top_p=override.top_p or self.top_p, frequency_penalty=override.frequency_penalty or self.frequency_penalty, diff --git a/src/agents/models/azure_openai_provider.py b/src/agents/models/azure_openai_provider.py new file mode 100644 index 00000000..0a69e978 --- /dev/null +++ b/src/agents/models/azure_openai_provider.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import os +import httpx +from openai import AsyncAzureOpenAI, DefaultAsyncHttpxClient + +from . import _openai_shared +from .interface import Model, ModelProvider +from .openai_chatcompletions import OpenAIChatCompletionsModel +from .openai_responses import OpenAIResponsesModel + +DEFAULT_API_VERSION = "2025-01-01-preview" # Changed to a more widely supported Azure OpenAI API version +DEFAULT_DEPLOYMENT = "gpt-4o" # Default deployment name + +_http_client: httpx.AsyncClient | None = None + + +# Similar to OpenAI Provider, share the HTTP client to improve performance +def shared_http_client() -> httpx.AsyncClient: + global _http_client + if _http_client is None: + _http_client = DefaultAsyncHttpxClient() + return _http_client + + +class AzureOpenAIProvider(ModelProvider): + def __init__( + self, + *, + api_key: str | None = None, + azure_endpoint: str | None = None, + api_version: str | None = None, + base_url: str | None = None, + deployment: str | None = None, + openai_client: AsyncAzureOpenAI | None = None, + use_responses: bool | None = None, + ) -> None: + """Create a new Azure OpenAI provider. + + Args: + api_key: API key for the Azure OpenAI client. If not provided, it will be retrieved from environment variables. + azure_endpoint: Azure OpenAI endpoint, e.g., "https://{resource-name}.openai.azure.com". If not provided, it will be retrieved from environment variables. + api_version: Azure OpenAI API version. Default is "2025-01-01-preview". + base_url: Optional complete base URL. If provided, it will override azure_endpoint. If not provided, it will be retrieved from environment variables. + deployment: Azure deployment name. Default is "gpt-4o". + openai_client: Optional Azure OpenAI client instance. If provided, other client parameters will be ignored. + use_responses: Whether to use OpenAI Responses API. Note: Azure OpenAI may not support the standard Responses API paths. + """ + if openai_client is not None: + assert api_key is None and azure_endpoint is None and base_url is None, ( + "Do not provide api_key, azure_endpoint, or base_url when providing openai_client" + ) + self._client: AsyncAzureOpenAI | None = openai_client + else: + self._client = None + # Automatically retrieve parameters from environment variables if not provided + self._stored_api_key = api_key or os.getenv("AZURE_OPENAI_API_KEY") + self._stored_azure_endpoint = azure_endpoint or os.getenv("AZURE_OPENAI_ENDPOINT") + self._stored_base_url = base_url or os.getenv("AZURE_OPENAI_BASE_URL") + self._stored_api_version = api_version or os.getenv("AZURE_OPENAI_API_VERSION") or DEFAULT_API_VERSION + self._stored_deployment = deployment or os.getenv("AZURE_OPENAI_DEPLOYMENT") or DEFAULT_DEPLOYMENT + + # Default to not using Responses API, as Azure OpenAI API paths differ from standard OpenAI + self._use_responses = False if use_responses is None else use_responses + + # Lazy load the client, ensuring that the client instance is only created when actually used + def _get_client(self) -> AsyncAzureOpenAI: + if self._client is None: + if not self._stored_api_key: + raise ValueError("Azure OpenAI API key not provided, please set the AZURE_OPENAI_API_KEY environment variable or provide it in the constructor") + + # Determine base URL + base_url = self._stored_base_url or self._stored_azure_endpoint + if not base_url: + raise ValueError("Azure OpenAI endpoint not provided, please set the AZURE_OPENAI_ENDPOINT or AZURE_OPENAI_BASE_URL environment variable, or provide it in the constructor") + + self._client = AsyncAzureOpenAI( + api_key=self._stored_api_key, + api_version=self._stored_api_version, + azure_endpoint=base_url, + http_client=shared_http_client(), + ) + + return self._client + + def get_model(self, model_name: str | None) -> Model: + """Get a model instance with the specified name + + Args: + model_name: Model name, which is typically the deployment name in Azure OpenAI + + Returns: + Model: Model instance + """ + # In Azure OpenAI, model_name is actually the deployment name + deployment_name = model_name if model_name else self._stored_deployment + + client = self._get_client() + + # Due to Azure OpenAI URL format requirements, use ChatCompletions API unless explicitly specified + return ( + OpenAIResponsesModel(model=deployment_name, openai_client=client) + if self._use_responses + else OpenAIChatCompletionsModel(model=deployment_name, openai_client=client) + ) + + @staticmethod + def from_env() -> AzureOpenAIProvider: + """Create AzureOpenAIProvider instance from environment variables + + Environment variables: + AZURE_OPENAI_API_KEY: Azure OpenAI API key + AZURE_OPENAI_ENDPOINT: Azure OpenAI endpoint + AZURE_OPENAI_BASE_URL: (Optional) Alternative complete base URL (overrides AZURE_OPENAI_ENDPOINT) + AZURE_OPENAI_API_VERSION: (Optional) API version + AZURE_OPENAI_DEPLOYMENT: (Optional) Deployment name + + Returns: + AzureOpenAIProvider: Configured instance + """ + return AzureOpenAIProvider( + api_key=os.getenv("AZURE_OPENAI_API_KEY"), + azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), + base_url=os.getenv("AZURE_OPENAI_BASE_URL"), + api_version=os.getenv("AZURE_OPENAI_API_VERSION"), + deployment=os.getenv("AZURE_OPENAI_DEPLOYMENT"), + ) diff --git a/src/agents/models/ollama_provider.py b/src/agents/models/ollama_provider.py new file mode 100644 index 00000000..fd8238aa --- /dev/null +++ b/src/agents/models/ollama_provider.py @@ -0,0 +1,342 @@ +from __future__ import annotations + +import json +import time +import uuid +import re +from typing import Any, AsyncIterator, Dict, List, Literal, Union, cast, overload + +import httpx +from openai import AsyncOpenAI, NOT_GIVEN, NotGiven +from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessage +from openai.types.completion_usage import CompletionUsage + +from .. import _debug +from ..agent_output import AgentOutputSchema +from ..exceptions import AgentsException +from ..handoffs import Handoff +from ..items import ModelResponse, TResponseInputItem, TResponseStreamEvent +from ..logger import logger +from ..model_settings import ModelSettings +from ..tool import Tool +from ..tracing.span_data import GenerationSpanData +from ..tracing.spans import Span +from ..usage import Usage +from .interface import Model, ModelProvider, ModelTracing +from .openai_chatcompletions import OpenAIChatCompletionsModel + +DEFAULT_MODEL = "llama3" + +class OllamaAdapterException(AgentsException): + """Ollama Adapter Exception""" + + def __init__(self, message: str): + super().__init__(message) + + def __str__(self) -> str: + return str(self.args[0]) # Use args[0] instead of message + + +class OllamaAsyncClient: + """Adapts Ollama API to behave like OpenAI's AsyncOpenAI client""" + + def __init__(self, base_url: str = "http://localhost:11434"): + self.base_url = base_url + self.http_client = httpx.AsyncClient(base_url=base_url, timeout=httpx.Timeout(60.0)) + self.chat = self.Chat(http_client=self.http_client, base_url=base_url) + + class Chat: + """Simulates the chat level of OpenAI client""" + def __init__(self, http_client: httpx.AsyncClient, base_url: str): + self.http_client = http_client + self.base_url = base_url + self.completions = self.Completions(http_client=self.http_client, base_url=base_url) + + class Completions: + """Simulates the chat.completions level of OpenAI client""" + def __init__(self, http_client: httpx.AsyncClient, base_url: str): + self.http_client = http_client + self.base_url = base_url + + def _clean_not_given(self, obj: Any) -> Any: + """Recursively clean NotGiven values""" + if obj is NOT_GIVEN or isinstance(obj, NotGiven): + return None + elif isinstance(obj, dict): + return {k: self._clean_not_given(v) for k, v in obj.items() if v is not NOT_GIVEN} + elif isinstance(obj, list): + return [self._clean_not_given(item) for item in obj if item is not NOT_GIVEN] + return obj + + def _extract_json_from_text(self, text: str, schema: dict = None) -> str: + """Extract JSON content from text""" + json_block_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text) + if json_block_match: + json_str = json_block_match.group(1).strip() + try: + json.loads(json_str) + return json_str + except json.JSONDecodeError: + logger.debug(f"JSON block parsing failed: {json_str[:100]}...") + + json_match = re.search(r'(\{[\s\S]*?\})', text) + if json_match: + json_str = json_match.group(1).strip() + try: + json.loads(json_str) + return json_str + except json.JSONDecodeError: + logger.debug(f"JSON object parsing failed: {json_str[:100]}...") + + if schema and "properties" in schema: + properties = schema["properties"] + json_obj = {} + for prop_name, prop_info in properties.items(): + prop_type = prop_info.get("type") + if prop_type in ("integer", "number"): + match = re.search(fr'(?:{prop_name}|{prop_name.title()})[^\d]*(\d+)', text) + if match: + try: + json_obj[prop_name] = int(match.group(1)) if prop_type == "integer" else float(match.group(1)) + except ValueError: + logger.debug(f"Number conversion failed: {match.group(1)}") + elif prop_type == "boolean": + if re.search(fr'(?:{prop_name}|{prop_name.title()}).*?(?:true|True|yes|Yes)', text): + json_obj[prop_name] = True + elif re.search(fr'(?:{prop_name}|{prop_name.title()}).*?(?:false|False|no|No)', text): + json_obj[prop_name] = False + elif prop_type == "string": + match = re.search(fr'(?:{prop_name}|{prop_name.title()})[^\"\']*([\"\'])(.*?)\1', text) + if match: + json_obj[prop_name] = match.group(2) + if json_obj: + return json.dumps(json_obj) + + logger.debug(f"Unable to extract JSON, using original text: {text[:100]}...") + return text + + + async def create(self, **kwargs) -> Union[ChatCompletion, AsyncIterator[ChatCompletionChunk]]: + """Simulates OpenAI's chat.completions.create method, supporting tool calls""" + cleaned_kwargs = self._clean_not_given(kwargs) + model = cleaned_kwargs.get("model", DEFAULT_MODEL) + messages = cleaned_kwargs.get("messages", []) + tools = cleaned_kwargs.get("tools", []) # Get tools parameter + stream = cleaned_kwargs.get("stream", False) + temperature = cleaned_kwargs.get("temperature", 0.7) + max_tokens = cleaned_kwargs.get("max_tokens", 2048) + response_format = cleaned_kwargs.get("response_format", {}) + + needs_json = response_format and response_format.get("type") == "json_schema" + json_schema = response_format.get("json_schema", {}).get("schema") if needs_json else None + # Check if any handoff tools exist (by description starting with "Handoff to") + has_handoff_tools = any( + tool.get("type") == "function" and + tool.get("function", {}).get("description", "").startswith("Handoff to") + for tool in tools + ) + if has_handoff_tools and tools: + handoff_instruction = ( + "\n\nYou may use the provided handoff tools to delegate the conversation to another specialized agent. " + "When a task requires delegation, please use a tool function whose name starts with 'Handoff to' and supply the required parameters." + ) + for msg in messages: + if msg.get("role") == "system": + msg["content"] += handoff_instruction + break + else: + messages.insert(0, {"role": "system", "content": f"Please process the user request. {handoff_instruction}"}) + + # Construct payload, including tools + payload = { + "model": model, + "messages": messages, + "options": {"temperature": temperature, "num_predict": max_tokens}, + "tools": tools + } + if needs_json: + payload["options"]["format"] = "json" + + if stream: + return self._create_stream(payload, needs_json, json_schema) + + url = f"{self.base_url}/v1/chat/completions" + try: + response = await self.http_client.post(url, json=payload, timeout=60.0) + response.raise_for_status() + data = response.json() + + # Handle JSON format + if needs_json and "choices" in data and data["choices"]: + content = data["choices"][0]["message"]["content"] + json_content = self._extract_json_from_text(content, json_schema) + try: + parsed_json = json.loads(json_content) + data["choices"][0]["message"]["content"] = json.dumps(parsed_json) + except json.JSONDecodeError: + logger.debug(f"Non-streaming response JSON parsing failed: {content[:100]}...") + return ChatCompletion.model_validate(data) + except httpx.HTTPStatusError as e: + if e.response.status_code == 404: + url = f"{self.base_url}/api/chat" + response = await self.http_client.post(url, json=payload, timeout=60.0) + response.raise_for_status() + buffer = "" + last_message = None + for line in response.text.strip().split('\n'): + if not line.strip(): + continue + try: + data = json.loads(line) + if "message" in data and "content" in data["message"]: + buffer += data["message"]["content"] + last_message = data + except json.JSONDecodeError: + logger.warning(f"Non-streaming response line parsing failed: {line[:100]}...") + if not last_message: + raise OllamaAdapterException("No valid message found") + final_data = last_message.copy() + final_data["message"]["content"] = self._extract_json_from_text(buffer, json_schema) if needs_json else buffer + return self._convert_to_chat_completion(final_data) + else: + raise OllamaAdapterException(f"API error: {str(e)}") from e + + + def _convert_to_chat_completion(self, ollama_response: Dict[str, Any]) -> ChatCompletion: + """Convert Ollama response to ChatCompletion format""" + message = ollama_response.get("message", {"content": ollama_response.get("response", "")}) + response_text = message.get("content", "") + prompt_text = str(ollama_response.get("prompt", "")) + prompt_tokens = len(prompt_text.split()) + completion_tokens = len(response_text.split()) + return ChatCompletion( + id=str(uuid.uuid4()), + choices=[{"finish_reason": "stop", "index": 0, "message": ChatCompletionMessage( + content=response_text, role="assistant", function_call=None, tool_calls=None + ), "logprobs": None}], + created=int(time.time()), + model=ollama_response.get("model", ""), + object="chat.completion", + system_fingerprint=None, + usage=CompletionUsage( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=prompt_tokens + completion_tokens + ) + ) + + async def _create_stream(self, payload: dict, needs_json: bool = False, json_schema: dict = None) -> AsyncIterator[ChatCompletionChunk]: + """Create a streaming response, supporting tool calls""" + url = f"{self.base_url}/v1/chat/completions" + stream_payload = payload.copy() + stream_payload["stream"] = True + try: + response = await self.http_client.post(url, json=stream_payload, timeout=60.0) + if response.status_code == 404: + url = f"{self.base_url}/api/chat" + else: + response.raise_for_status() + function_calls = {} # Store streaming tool calls + async for line in response.aiter_lines(): + if not line.strip() or line.strip() == "data: [DONE]": + continue + data = line[len("data: "):] if line.startswith("data: ") else line + try: + chunk = ChatCompletionChunk.model_validate(json.loads(data)) + yield chunk + + # Handle tool call increments + if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.tool_calls: + for tc_delta in chunk.choices[0].delta.tool_calls: + index = tc_delta.index + if index not in function_calls: + function_calls[index] = { + "id": tc_delta.id, + "type": "function", + "function": {"name": "", "arguments": ""} + } + if tc_delta.function: + function_calls[index]["function"]["name"] += tc_delta.function.name or "" + function_calls[index]["function"]["arguments"] += tc_delta.function.arguments or "" + except json.JSONDecodeError: + logger.warning(f"Streaming response chunk parsing failed: {data[:100]}...") + except httpx.HTTPStatusError as e: + if e.response.status_code != 404: + raise OllamaAdapterException(f"Streaming API error: {str(e)}") from e + + async with self.http_client.stream("POST", url, json=payload, timeout=60.0) as http_response: + http_response.raise_for_status() + buffer = "" + async for chunk in http_response.aiter_text(): + if not chunk.strip(): + continue + for line in chunk.strip().split('\n'): + if not line.strip(): + continue + try: + data = json.loads(line) + content = data.get("message", {}).get("content", "") or data.get("response", "") + if content: + buffer += content + yield ChatCompletionChunk( + id=str(uuid.uuid4()), + choices=[{"delta": {"content": content, "role": "assistant"}, "finish_reason": None, "index": 0, "logprobs": None}], + created=int(time.time()), + model=data.get("model", ""), + object="chat.completion.chunk", + system_fingerprint=None, + usage=None + ) + if data.get("done", False): + if needs_json and buffer: + json_content = self._extract_json_from_text(buffer, json_schema) + try: + json.loads(json_content) + except json.JSONDecodeError: + json_content = f'{{"result": {json.dumps(buffer.strip())}}}' + yield ChatCompletionChunk( + id=str(uuid.uuid4()), + choices=[{"delta": {"content": json_content, "role": "assistant"}, "finish_reason": None, "index": 0, "logprobs": None}], + created=int(time.time()), + model=data.get("model", ""), + object="chat.completion.chunk", + system_fingerprint=None, + usage=None + ) + prompt_tokens = len(str(data.get("prompt", "")).split()) + completion_tokens = len(buffer.split()) + yield ChatCompletionChunk( + id=str(uuid.uuid4()), + choices=[{"delta": {}, "finish_reason": "stop", "index": 0, "logprobs": None}], + created=int(time.time()), + model=data.get("model", ""), + object="chat.completion.chunk", + system_fingerprint=None, + usage=CompletionUsage( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=prompt_tokens + completion_tokens + ) + ) + except json.JSONDecodeError: + logger.warning(f"Streaming response chunk parsing failed: {line[:100]}...") + +class OllamaProvider(ModelProvider): + def __init__(self, *, base_url: str = "http://localhost:11434", default_model: str = DEFAULT_MODEL): + self.base_url = base_url + self.default_model = default_model + + def get_model(self, model: str | Model) -> Model: + if isinstance(model, Model): + return model + ollama_client = OllamaAsyncClient(base_url=self.base_url) + return OpenAIChatCompletionsModel(model=model or self.default_model, openai_client=ollama_client) + + async def check_health(self) -> bool: + try: + client = httpx.AsyncClient(timeout=httpx.Timeout(5.0)) + response = await client.get(f"{self.base_url}/api/tags") + response.raise_for_status() + return bool(response.json().get("models")) + except Exception: + return False \ No newline at end of file diff --git a/src/agents/models/provider_factory.py b/src/agents/models/provider_factory.py new file mode 100644 index 00000000..0770ed0d --- /dev/null +++ b/src/agents/models/provider_factory.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from ..model_settings import ModelSettings +from .interface import ModelProvider +from .openai_provider import OpenAIProvider +from .ollama_provider import OllamaProvider + + +class ModelProviderFactory: + """Model provider factory class, used to create different types of model providers.""" + + @staticmethod + def create_provider(model_settings: ModelSettings) -> ModelProvider: + """Create corresponding model provider based on model settings. + + Args: + model_settings: Model settings. + + Returns: + ModelProvider: Model provider instance. + + Raises: + ValueError: If the provided provider type is not supported. + """ + if model_settings.provider == "openai": + return OpenAIProvider() + elif model_settings.provider == "ollama": + return OllamaProvider( + base_url=model_settings.ollama_base_url, + default_model=model_settings.ollama_default_model + ) + else: + raise ValueError(f"Unsupported provider: {model_settings.provider}") \ No newline at end of file diff --git a/src/agents/util/_json.py b/src/agents/util/_json.py index 1e081f68..8fe11932 100644 --- a/src/agents/util/_json.py +++ b/src/agents/util/_json.py @@ -1,5 +1,7 @@ from __future__ import annotations +import re +import json from typing import Literal from pydantic import TypeAdapter, ValidationError @@ -16,16 +18,79 @@ def validate_json(json_str: str, type_adapter: TypeAdapter[T], partial: bool) -> partial_setting: bool | Literal["off", "on", "trailing-strings"] = ( "trailing-strings" if partial else False ) + try: + # First try direct validation validated = type_adapter.validate_json(json_str, experimental_allow_partial=partial_setting) return validated except ValidationError as e: - attach_error_to_current_span( - SpanError( - message="Invalid JSON provided", - data={}, + # If direct validation fails, try to extract JSON from the text + try: + # Try to find possible JSON structures + + # 1. Look for JSON in code blocks + json_block_match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', json_str) + if json_block_match: + extracted = json_block_match.group(1).strip() + try: + validated = type_adapter.validate_json(extracted, experimental_allow_partial=partial_setting) + return validated + except ValidationError: + pass # Continue trying other methods + + # 2. Look for {...} structures + json_match = re.search(r'(\{[\\s\S]*?\})', json_str) + if json_match: + extracted = json_match.group(1).strip() + try: + validated = type_adapter.validate_json(extracted, experimental_allow_partial=partial_setting) + return validated + except ValidationError: + pass # Continue trying other methods + + # 3. Try special cases: if the schema is very simple (like just a number field) + if hasattr(type_adapter.core_schema, "schema") and "properties" in type_adapter.core_schema.schema: + schema = type_adapter.core_schema.schema + if len(schema["properties"]) == 1 and "number" in schema["properties"]: + # Try to extract a number from the text + number_match = re.search(r'(?:number|value|result)[^\d]*(\d+)', json_str) + if number_match: + simple_json = f'{{"number": {number_match.group(1)}}}' + try: + validated = type_adapter.validate_json(simple_json) + return validated + except ValidationError: + pass # Continue trying other methods + + # Try to extract any number + any_number = re.search(r'\b(\d+)\b', json_str) + if any_number: + simple_json = f'{{"number": {any_number.group(1)}}}' + try: + validated = type_adapter.validate_json(simple_json) + return validated + except ValidationError: + pass # Proceed with original error + + # Failed to construct the required data structure, raise the original error + attach_error_to_current_span( + SpanError( + message="Invalid JSON provided", + data={}, + ) ) - ) - raise ModelBehaviorError( - f"Invalid JSON when parsing {json_str} for {type_adapter}; {e}" - ) from e + raise ModelBehaviorError( + f"Invalid JSON when parsing {json_str} for {type_adapter}; {e}" + ) from e + + except Exception as extraction_error: + # If the extraction process fails, still raise the original error + attach_error_to_current_span( + SpanError( + message="Invalid JSON provided", + data={}, + ) + ) + raise ModelBehaviorError( + f"Invalid JSON when parsing {json_str} for {type_adapter}; {e}" + ) from e