From 4b2a1fcd939e9042e2eb25c6038fa2b0c2038a71 Mon Sep 17 00:00:00 2001 From: VC Date: Wed, 20 May 2026 20:57:36 -0400 Subject: [PATCH] Add Tuning Engines model provider sample --- openai_agents/model_providers/README.md | 29 +++++++- .../run_tuning_engines_worker.py | 74 +++++++++++++++++++ .../run_tuning_engines_workflow.py | 29 ++++++++ .../workflows/tuning_engines_workflow.py | 29 ++++++++ 4 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 openai_agents/model_providers/run_tuning_engines_worker.py create mode 100644 openai_agents/model_providers/run_tuning_engines_workflow.py create mode 100644 openai_agents/model_providers/workflows/tuning_engines_workflow.py diff --git a/openai_agents/model_providers/README.md b/openai_agents/model_providers/README.md index df8f286e..64e16d23 100644 --- a/openai_agents/model_providers/README.md +++ b/openai_agents/model_providers/README.md @@ -30,6 +30,32 @@ The example uses Anthropic Claude by default but can be modified to use other Li Find more LiteLLM providers at: https://docs.litellm.ai/docs/providers +#### Tuning Engines +Uses a custom `ModelProvider` to route OpenAI Agents SDK model calls through the Tuning Engines OpenAI-compatible gateway. + +Set your Tuning Engines inference key and, optionally, the tenant model alias to use: + +```bash +export TUNING_ENGINES_API_KEY="sk-te-..." +export TUNING_ENGINES_MODEL="your-model-alias" +# Optional, defaults to https://api.tuningengines.com/v1 +export TUNING_ENGINES_BASE_URL="https://api.tuningengines.com/v1" +``` + +Start the Tuning Engines provider worker: + +```bash +uv run openai_agents/model_providers/run_tuning_engines_worker.py +``` + +Then run the example in a separate terminal: + +```bash +uv run openai_agents/model_providers/run_tuning_engines_workflow.py +``` + +Use a model alias that is available to the configured Tuning Engines inference key. + ### Extra #### GPT-OSS with Ollama @@ -63,5 +89,4 @@ uv run openai_agents/model_providers/run_gpt_oss_workflow.py - **Custom Example Agent** - Custom OpenAI client integration - **Custom Example Global** - Global default client configuration -- **Custom Example Provider** - Custom ModelProvider pattern -- **LiteLLM Provider** - Interactive model/API key input \ No newline at end of file +- **LiteLLM Provider** - Interactive model/API key input diff --git a/openai_agents/model_providers/run_tuning_engines_worker.py b/openai_agents/model_providers/run_tuning_engines_worker.py new file mode 100644 index 00000000..807de875 --- /dev/null +++ b/openai_agents/model_providers/run_tuning_engines_worker.py @@ -0,0 +1,74 @@ +import asyncio +import os +from datetime import timedelta +from typing import Optional + +from agents import ( + Model, + ModelProvider, + OpenAIChatCompletionsModel, + set_tracing_disabled, +) +from openai import AsyncOpenAI +from temporalio.client import Client +from temporalio.contrib.openai_agents import ModelActivityParameters, OpenAIAgentsPlugin +from temporalio.worker import Worker + +from openai_agents.model_providers.workflows.tuning_engines_workflow import ( + TuningEnginesWorkflow, +) + +TUNING_ENGINES_BASE_URL = os.environ.get( + "TUNING_ENGINES_BASE_URL", "https://api.tuningengines.com/v1" +) +TUNING_ENGINES_MODEL = os.environ.get("TUNING_ENGINES_MODEL") +TUNING_ENGINES_API_KEY = os.environ.get("TUNING_ENGINES_API_KEY") + +if not TUNING_ENGINES_API_KEY: + raise RuntimeError("Set TUNING_ENGINES_API_KEY before starting this worker") + +tuning_engines_client = AsyncOpenAI( + base_url=TUNING_ENGINES_BASE_URL, + api_key=TUNING_ENGINES_API_KEY, +) + + +class TuningEnginesModelProvider(ModelProvider): + def get_model(self, model_name: Optional[str]) -> Model: + model = OpenAIChatCompletionsModel( + model=TUNING_ENGINES_MODEL or model_name or "tuning-engines-default", + openai_client=tuning_engines_client, + ) + return model + + +async def main(): + # Disable Agents SDK tracing — the default exporter sends traces to OpenAI's + # backend, which requires an OpenAI API key not available in these samples. + # Call here rather than in the workflow because it's a global side effect. + set_tracing_disabled(disabled=True) + + client = await Client.connect( + "localhost:7233", + plugins=[ + OpenAIAgentsPlugin( + model_params=ModelActivityParameters( + start_to_close_timeout=timedelta(seconds=30) + ), + model_provider=TuningEnginesModelProvider(), + ), + ], + ) + + worker = Worker( + client, + task_queue="openai-agents-model-providers-task-queue", + workflows=[ + TuningEnginesWorkflow, + ], + ) + await worker.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/openai_agents/model_providers/run_tuning_engines_workflow.py b/openai_agents/model_providers/run_tuning_engines_workflow.py new file mode 100644 index 00000000..ac4cdadb --- /dev/null +++ b/openai_agents/model_providers/run_tuning_engines_workflow.py @@ -0,0 +1,29 @@ +import asyncio + +from temporalio.client import Client +from temporalio.contrib.openai_agents import OpenAIAgentsPlugin + +from openai_agents.model_providers.workflows.tuning_engines_workflow import ( + TuningEnginesWorkflow, +) + + +async def main(): + client = await Client.connect( + "localhost:7233", + plugins=[ + OpenAIAgentsPlugin(), + ], + ) + + result = await client.execute_workflow( + TuningEnginesWorkflow.run, + "Explain why a governed model gateway is useful in production.", + id="tuning-engines-workflow-id", + task_queue="openai-agents-model-providers-task-queue", + ) + print(f"Result: {result}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/openai_agents/model_providers/workflows/tuning_engines_workflow.py b/openai_agents/model_providers/workflows/tuning_engines_workflow.py new file mode 100644 index 00000000..f9039b3a --- /dev/null +++ b/openai_agents/model_providers/workflows/tuning_engines_workflow.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from agents import Agent, Runner, function_tool +from temporalio import workflow + + +@workflow.defn +class TuningEnginesWorkflow: + @workflow.run + async def run(self, prompt: str) -> str: + @function_tool + def summarize_gateway_policy(topic: str): + return ( + f"For {topic}, keep model access scoped, log usage, and route " + "through approved tenant model aliases." + ) + + agent = Agent( + name="Assistant", + instructions=( + "You explain production AI gateway tradeoffs clearly and use the " + "policy summary tool when governance is relevant." + ), + model="tuning-engines-default", + tools=[summarize_gateway_policy], + ) + + result = await Runner.run(agent, prompt) + return result.final_output