From 8dd3ac1cb3cebefd28f740705c44bc0d7562d8ce Mon Sep 17 00:00:00 2001 From: Kabi10 Date: Thu, 16 Apr 2026 18:28:16 -0700 Subject: [PATCH 1/2] feat: add OpenAI Python SDK example with Archestra proxy Shows how to route OpenAI requests through the Archestra LLM Proxy with a single base_url change. Includes interactive CLI chat, env template, and setup README. Complements the existing ai-sdk-express (Node.js) example with a Python equivalent using the official openai package. --- openai-python/.env.example | 6 +++ openai-python/README.md | 54 +++++++++++++++++++++++++++ openai-python/main.py | 67 ++++++++++++++++++++++++++++++++++ openai-python/requirements.txt | 2 + 4 files changed, 129 insertions(+) create mode 100644 openai-python/.env.example create mode 100644 openai-python/README.md create mode 100644 openai-python/main.py create mode 100644 openai-python/requirements.txt diff --git a/openai-python/.env.example b/openai-python/.env.example new file mode 100644 index 0000000..e90a2d2 --- /dev/null +++ b/openai-python/.env.example @@ -0,0 +1,6 @@ +# OpenAI API key — https://platform.openai.com/api-keys +OPENAI_API_KEY= + +# Archestra LLM Proxy URL — copy from your LLM Proxy's "Connect" page in Archestra +# Example: http://localhost:9000/v1/openai +ARCHESTRA_PROXY_URL=http://localhost:9000/v1/openai diff --git a/openai-python/README.md b/openai-python/README.md new file mode 100644 index 0000000..781a60f --- /dev/null +++ b/openai-python/README.md @@ -0,0 +1,54 @@ +# OpenAI Python SDK + Archestra + +A minimal Python chat app showing how to route OpenAI requests through the +[Archestra LLM Proxy](https://archestra.ai) for security guardrails, +observability, and policy enforcement — with zero changes to your existing +OpenAI SDK code beyond the `base_url`. + +## How it works + +``` +Your app → Archestra LLM Proxy → OpenAI API + (security policies, + usage logs, rate limits) +``` + +The only change from a standard OpenAI integration is setting `base_url` to +your Archestra proxy URL. The SDK, models, and API surface stay identical. + +## Prerequisites + +- Python 3.9+ +- An Archestra instance running locally (`http://localhost:9000`) or in the cloud +- An OpenAI API key + +## Setup + +```bash +# 1. Copy the env template and fill in your values +cp .env.example .env + +# 2. Install dependencies +pip install -r requirements.txt + +# 3. Run the interactive chat +python main.py +``` + +## Configuration + +| Variable | Description | +|---|---| +| `OPENAI_API_KEY` | Your OpenAI API key | +| `ARCHESTRA_PROXY_URL` | Archestra proxy URL (from LLM Proxy → Connect in the UI) | + +## Using a different model + +Change the `model` parameter in `main.py`: + +```python +response = client.chat.completions.create( + model="gpt-4o-mini", # or any model available in your Archestra setup + messages=history, +) +``` diff --git a/openai-python/main.py b/openai-python/main.py new file mode 100644 index 0000000..aa90ea9 --- /dev/null +++ b/openai-python/main.py @@ -0,0 +1,67 @@ +""" +Archestra LLM Proxy — OpenAI Python SDK example. + +Routes OpenAI requests through the Archestra platform for security +guardrails, observability, and policy enforcement. + +Usage: + cp .env.example .env # fill in OPENAI_API_KEY and ARCHESTRA_PROXY_URL + pip install -r requirements.txt + python main.py +""" + +import os +from openai import OpenAI +from dotenv import load_dotenv + +load_dotenv() + +api_key = os.environ["OPENAI_API_KEY"] +proxy_url = os.environ.get("ARCHESTRA_PROXY_URL", "http://localhost:9000/v1/openai") + +# Point the OpenAI client at the Archestra proxy instead of api.openai.com. +# All other SDK usage stays identical — Archestra is transparent to the caller. +client = OpenAI( + api_key=api_key, + base_url=proxy_url, +) + + +def chat(user_message: str, history: list[dict]) -> str: + """Send a message and return the assistant reply.""" + history.append({"role": "user", "content": user_message}) + + response = client.chat.completions.create( + model="gpt-4o", + messages=history, + ) + + reply = response.choices[0].message.content + history.append({"role": "assistant", "content": reply}) + return reply + + +def main() -> None: + print("Archestra + OpenAI Python chat (type 'quit' to exit)\n") + history: list[dict] = [] + + while True: + try: + user_input = input("You: ").strip() + except (EOFError, KeyboardInterrupt): + print("\nGoodbye!") + break + + if user_input.lower() in {"quit", "exit", "q"}: + print("Goodbye!") + break + + if not user_input: + continue + + reply = chat(user_input, history) + print(f"Assistant: {reply}\n") + + +if __name__ == "__main__": + main() diff --git a/openai-python/requirements.txt b/openai-python/requirements.txt new file mode 100644 index 0000000..75c8117 --- /dev/null +++ b/openai-python/requirements.txt @@ -0,0 +1,2 @@ +openai>=1.0.0 +python-dotenv>=1.0.0 From 5ccee1d9473cdd536b5950a3d02c6784a4c5f5d4 Mon Sep 17 00:00:00 2001 From: Kabi10 Date: Thu, 16 Apr 2026 19:29:23 -0700 Subject: [PATCH 2/2] feat(openai-python): add streaming support with --no-stream flag Adds token-by-token streaming as the default mode (matching the ai-sdk-express example which also uses streamText). Pass --no-stream to use the blocking completion API instead. --- openai-python/main.py | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/openai-python/main.py b/openai-python/main.py index aa90ea9..f17d5d3 100644 --- a/openai-python/main.py +++ b/openai-python/main.py @@ -7,10 +7,12 @@ Usage: cp .env.example .env # fill in OPENAI_API_KEY and ARCHESTRA_PROXY_URL pip install -r requirements.txt - python main.py + python main.py # streaming (default) + python main.py --no-stream # non-streaming """ import os +import sys from openai import OpenAI from dotenv import load_dotenv @@ -27,8 +29,28 @@ ) +def chat_stream(user_message: str, history: list[dict]) -> str: + """Send a message and stream the assistant reply token by token.""" + history.append({"role": "user", "content": user_message}) + + full_reply = "" + with client.chat.completions.create( + model="gpt-4o", + messages=history, + stream=True, + ) as stream: + for chunk in stream: + delta = chunk.choices[0].delta.content or "" + print(delta, end="", flush=True) + full_reply += delta + print() # newline after streaming finishes + + history.append({"role": "assistant", "content": full_reply}) + return full_reply + + def chat(user_message: str, history: list[dict]) -> str: - """Send a message and return the assistant reply.""" + """Send a message and return the complete assistant reply.""" history.append({"role": "user", "content": user_message}) response = client.chat.completions.create( @@ -42,7 +64,10 @@ def chat(user_message: str, history: list[dict]) -> str: def main() -> None: - print("Archestra + OpenAI Python chat (type 'quit' to exit)\n") + use_stream = "--no-stream" not in sys.argv + + mode = "streaming" if use_stream else "non-streaming" + print(f"Archestra + OpenAI Python chat [{mode}] (type 'quit' to exit)\n") history: list[dict] = [] while True: @@ -59,8 +84,12 @@ def main() -> None: if not user_input: continue - reply = chat(user_input, history) - print(f"Assistant: {reply}\n") + if use_stream: + print("Assistant: ", end="") + chat_stream(user_input, history) + else: + reply = chat(user_input, history) + print(f"Assistant: {reply}\n") if __name__ == "__main__":