From bdc13421c6d852974edb0eee25aa8238c216c6be Mon Sep 17 00:00:00 2001 From: Wojciech Rebisz <147821486+Wojciech-Rebisz@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:29:07 +0100 Subject: [PATCH 1/3] Rename agent to openai_reponses_agent --- .gitignore | 3 + agents/base/llamastack_agent/README.md | 144 ------------------ .../examples/_interactive_chat.py | 5 - .../llamastack_agent/examples/ai_service.py | 8 - .../examples/execute_ai_service_locally.py | 36 ----- .../src/llamastack_agent_base/__init__.py | 18 --- .../base/llamastack_agent/tests/__init__.py | 0 .../Dockerfile | 2 +- .../LICENSE | 0 agents/base/openai_responses_agent/README.md | 123 +++++++++++++++ .../deploy.sh | 16 +- .../examples/_interactive_chat.py | 5 + .../examples/ai_service.py | 5 + .../examples/execute_ai_service_locally.py | 43 ++++++ .../init.sh | 4 +- .../k8s/deployment.yaml | 22 +-- .../k8s/route.yaml | 6 +- .../k8s/service.yaml | 6 +- .../main.py | 17 ++- .../pyproject.toml | 8 +- .../requirements.txt | 2 +- .../openai_responses_agent_base/__init__.py | 18 +++ .../src/openai_responses_agent_base}/agent.py | 113 +++++++++----- .../src/openai_responses_agent_base}/tools.py | 0 .../src/openai_responses_agent_base}/utils.py | 0 25 files changed, 314 insertions(+), 290 deletions(-) delete mode 100644 agents/base/llamastack_agent/README.md delete mode 100644 agents/base/llamastack_agent/examples/_interactive_chat.py delete mode 100644 agents/base/llamastack_agent/examples/ai_service.py delete mode 100644 agents/base/llamastack_agent/examples/execute_ai_service_locally.py delete mode 100644 agents/base/llamastack_agent/src/llamastack_agent_base/__init__.py delete mode 100644 agents/base/llamastack_agent/tests/__init__.py rename agents/base/{llamastack_agent => openai_responses_agent}/Dockerfile (79%) rename agents/base/{llamastack_agent => openai_responses_agent}/LICENSE (100%) create mode 100644 agents/base/openai_responses_agent/README.md rename agents/base/{llamastack_agent => openai_responses_agent}/deploy.sh (62%) create mode 100644 agents/base/openai_responses_agent/examples/_interactive_chat.py create mode 100644 agents/base/openai_responses_agent/examples/ai_service.py create mode 100644 agents/base/openai_responses_agent/examples/execute_ai_service_locally.py rename agents/base/{llamastack_agent => openai_responses_agent}/init.sh (86%) rename agents/base/{llamastack_agent => openai_responses_agent}/k8s/deployment.yaml (73%) rename agents/base/{llamastack_agent => openai_responses_agent}/k8s/route.yaml (67%) rename agents/base/{llamastack_agent => openai_responses_agent}/k8s/service.yaml (63%) rename agents/base/{llamastack_agent => openai_responses_agent}/main.py (76%) rename agents/base/{llamastack_agent => openai_responses_agent}/pyproject.toml (59%) rename agents/base/{llamastack_agent => openai_responses_agent}/requirements.txt (71%) create mode 100644 agents/base/openai_responses_agent/src/openai_responses_agent_base/__init__.py rename agents/base/{llamastack_agent/src/llamastack_agent_base => openai_responses_agent/src/openai_responses_agent_base}/agent.py (70%) rename agents/base/{llamastack_agent/src/llamastack_agent_base => openai_responses_agent/src/openai_responses_agent_base}/tools.py (100%) rename agents/base/{llamastack_agent/src/llamastack_agent_base => openai_responses_agent/src/openai_responses_agent_base}/utils.py (100%) diff --git a/.gitignore b/.gitignore index f51bc305..a72ab0f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .env .vscode +.idea +.DS_Store +**/.DS_Store __pycache__ .pytest_cache .venv diff --git a/agents/base/llamastack_agent/README.md b/agents/base/llamastack_agent/README.md deleted file mode 100644 index 73ac8f19..00000000 --- a/agents/base/llamastack_agent/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# LlamaStack Agent - -Agent built **only on the LlamaStack API** ([llama-stack-client](https://pypi.org/project/llama-stack-client/)), without LlamaIndex. Uses `AIAgent` with chat, tools, and Action/Observation loop. Python 3.12+ required. - -# Use Agent Locally - -### Installation - -```bash -git clone -cd Agentic-Starter-Kits -``` -```bash -python -m venv .venv -source .venv/bin/activate # On Windows: .venv\Scripts\activate -``` - -If you want to install Ollama: [Ollama site](https://ollama.com/) or [Brew](https://formulae.brew.sh/formula/ollama#default). - -**Install Llama Stack**: - -```bash -pip install llama-stack llama-stack-client -``` - -### Setup Instructions - -**Step 1: Pull Required Models** - -```bash -ollama pull llama3.2:3b -``` - -**Step 2: Start Ollama Service** - -```bash -ollama serve -``` - -> **Keep this terminal open** – Ollama needs to keep running. - -**Step 3: Start Llama Stack Server** - -From the **repository root directory**: - -```bash -llama stack run run_llama_server.yaml -``` - -> **Keep this terminal open** – server runs at `http://localhost:8321`. - -**Step 4: Install Agent Dependencies** - -```bash -cd agents/base/llamastack_agent -pip install -r requirements.txt -``` - -**Step 5: Configure Environment Variables** - -Copy the template (from repo root: `template.env`) or create `.env` in the agent directory: - -```bash -cp ../../../template.env .env -``` - -Edit `.env`: - -```env -BASE_URL=http://localhost:8321 -MODEL_ID=ollama/llama3.2:3b -API_KEY=not-needed -``` - -**Step 6: Run the Interactive Chat** - -```bash -cd examples -python execute_ai_service_locally.py -``` - -**⚡ Or with [uv](https://docs.astral.sh/uv/)** (from repo root): - -1. Create venv and activate: -```bash -uv venv --python 3.12 -source .venv/bin/activate -``` - -2. Copy shared utils into the agent package: -```bash -cp utils.py agents/base/llamastack_agent/src/llamastack_agent_base/ -``` - -3. Install agent (editable) and its requirements: -```bash -uv pip install -e agents/base/llamastack_agent/. -r agents/base/llamastack_agent/requirements.txt -``` - -4. Run the example: -```bash -uv run agents/base/llamastack_agent/examples/execute_ai_service_locally.py -``` - -# Deployment on Red Hat OpenShift Cluster - -### Step 1: Initialize the Agent - -```bash -cd agents/base/llamastack_agent -chmod +x init.sh deploy.sh -./init.sh -``` - -This loads `.env`, validates variables, and copies `utils.py` into the agent package. - -### Step 2: Build Image and Deploy - -```bash -./deploy.sh -``` - -This creates the API key secret, builds and pushes the image, and deploys the agent (Deployment, Service, Route). - -### Step 3: Test the Agent - -Get the route host: - -```bash -oc get route llamastack-agent -o jsonpath='{.spec.host}' -``` - -Send a test request: - -```bash -curl -X POST https:///chat \ - -H "Content-Type: application/json" \ - -d '{"message": "What is 2+2? Answer briefly."}' -``` - -## References - -- [Llama Stack docs](https://llama-stack.readthedocs.io/) -- [llama-stack-client (PyPI)](https://pypi.org/project/llama-stack-client/) diff --git a/agents/base/llamastack_agent/examples/_interactive_chat.py b/agents/base/llamastack_agent/examples/_interactive_chat.py deleted file mode 100644 index 1a1a8276..00000000 --- a/agents/base/llamastack_agent/examples/_interactive_chat.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Placeholder for interactive chat UI using LlamaStack agent. - -Reuse or adapt from llamaindex_websearch_agent/examples once LlamaStack API is integrated. -""" diff --git a/agents/base/llamastack_agent/examples/ai_service.py b/agents/base/llamastack_agent/examples/ai_service.py deleted file mode 100644 index fa2239cf..00000000 --- a/agents/base/llamastack_agent/examples/ai_service.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Placeholder for LlamaStack-based AI service (streaming or sync). - -When integrating llama-stack-client, use LlamaStackClient to create chat completions -and optionally stream responses. See: - https://llama-stack.readthedocs.io/ - https://pypi.org/project/llama-stack-client/ -""" diff --git a/agents/base/llamastack_agent/examples/execute_ai_service_locally.py b/agents/base/llamastack_agent/examples/execute_ai_service_locally.py deleted file mode 100644 index c11fce28..00000000 --- a/agents/base/llamastack_agent/examples/execute_ai_service_locally.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Run LlamaStack agent locally (stub or real LlamaStack API). - -Usage: - From repo root with PYTHONPATH including agents/base/llamastack_agent: - python -m agents.base.llamastack_agent.examples.execute_ai_service_locally - - Or after cd agents/base/llamastack_agent and PYTHONPATH=/app:/app/src: - uvicorn main:app --reload - # Then POST /chat with {"message": "Hello"} -""" - -import asyncio -import os - -# Ensure we can import the agent and utils -if __name__ == "__main__": - import sys - - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) - -from llamastack_agent_base.agent import get_agent_closure -from llamastack_agent_base.utils import get_env_var - - -async def main(): - base_url = get_env_var("BASE_URL") - model_id = get_env_var("MODEL_ID") - get_agent = get_agent_closure(base_url=base_url, model_id=model_id) - agent = get_agent() - result = await agent.run(input=[{"role": "user", "content": "Hello, LlamaStack!"}]) - print("Response:", result) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/agents/base/llamastack_agent/src/llamastack_agent_base/__init__.py b/agents/base/llamastack_agent/src/llamastack_agent_base/__init__.py deleted file mode 100644 index 3844073d..00000000 --- a/agents/base/llamastack_agent/src/llamastack_agent_base/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -LlamaStack Agent Base – agent built only on LlamaStack API (no LlamaIndex). - -Use llama-stack-client to talk to Llama Stack (reasoning + tools). This package -provides the agent factory and placeholder tools until full LlamaStack integration. -""" - -from llamastack_agent_base.agent import AIAgent, get_agent_closure -from llamastack_agent_base.tools import search_reviews, search_price -from llamastack_agent_base.utils import get_env_var - -__all__ = [ - "get_agent_closure", - "AIAgent", - "search_reviews", - "search_price", - "get_env_var", -] diff --git a/agents/base/llamastack_agent/tests/__init__.py b/agents/base/llamastack_agent/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/agents/base/llamastack_agent/Dockerfile b/agents/base/openai_responses_agent/Dockerfile similarity index 79% rename from agents/base/llamastack_agent/Dockerfile rename to agents/base/openai_responses_agent/Dockerfile index 8e145e9a..1ed063d9 100644 --- a/agents/base/llamastack_agent/Dockerfile +++ b/agents/base/openai_responses_agent/Dockerfile @@ -1,4 +1,4 @@ -# LlamaStack Agent (LlamaStack API only, no LlamaIndex). Python 3.12+ required by llama-stack-client. +# OpenAI Responses Agent – OpenAI client + pure Python (Responses API, no agentic framework). Python 3.12+. FROM python:3.12-slim WORKDIR /app diff --git a/agents/base/llamastack_agent/LICENSE b/agents/base/openai_responses_agent/LICENSE similarity index 100% rename from agents/base/llamastack_agent/LICENSE rename to agents/base/openai_responses_agent/LICENSE diff --git a/agents/base/openai_responses_agent/README.md b/agents/base/openai_responses_agent/README.md new file mode 100644 index 00000000..34aa892f --- /dev/null +++ b/agents/base/openai_responses_agent/README.md @@ -0,0 +1,123 @@ +# OpenAI Responses Agent + +Agent **without any agentic framework**: uses only the **OpenAI Python client** and **pure Python** (Responses API). No LlamaStack, LangChain, LlamaIndex, etc. – to show it can be done without frameworks. Uses `AIAgent` with chat, tools, and Action/Observation loop. Compatible with OpenAI API or any OpenAI-compatible endpoint (e.g. `BASE_URL` override). Python 3.12+. + +# Use Agent Locally + +### Installation + +```bash +git clone +cd Agentic-Starter-Kits +``` +```bash +python -m venv .venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate +``` + +If you want to install Ollama: [Ollama site](https://ollama.com/) or [Brew](https://formulae.brew.sh/formula/ollama#default). + +**Install agent dependencies** (OpenAI client): + +```bash +cd agents/base/openai_responses_agent +uv pip install -e . +# or: poetry install +``` + +### Setup Instructions + +**Option A – OpenAI API** + +Create `.env` in the agent directory (or use repo `template.env`): + +```env +BASE_URL=https://api.openai.com/v1 +MODEL_ID=gpt-4o-mini +API_KEY=sk-... +``` + +**Option B – Local OpenAI-compatible endpoint (e.g. Ollama)** + +1. Pull model and run Ollama (or another OpenAI-compatible server). +2. In `.env` set e.g. `BASE_URL=http://localhost:11434/v1`, `MODEL_ID=llama3.2`, `API_KEY=not-needed` (if not required). + +**Configure Environment Variables** + +Copy the template (from repo root: `template.env`) or create `.env` in the agent directory: + +```bash +cp ../../../template.env .env +``` + +Edit `.env` with `BASE_URL`, `MODEL_ID`, and `API_KEY` as above. + +**Run the example** + +```bash +cd examples +python execute_ai_service_locally.py +``` + +**⚡ Or with [uv](https://docs.astral.sh/uv/)** (from repo root): + +1. Create venv and activate: +```bash +uv venv --python 3.12 +source .venv/bin/activate +``` + +2. Copy shared utils into the agent package: +```bash +cp utils.py agents/base/openai_responses_agent/src/openai_responses_agent_base/ +``` + +3. Install agent (editable) and its requirements: +```bash +uv pip install -e agents/base/openai_responses_agent/. -r agents/base/openai_responses_agent/requirements.txt +``` + +4. Run the example: +```bash +uv run agents/base/openai_responses_agent/examples/execute_ai_service_locally.py +``` + +# Deployment on Red Hat OpenShift Cluster + +### Step 1: Initialize the Agent + +```bash +cd agents/base/openai_responses_agent +chmod +x init.sh deploy.sh +./init.sh +``` + +This loads `.env`, validates variables, and copies `utils.py` into the agent package. + +### Step 2: Build Image and Deploy + +```bash +./deploy.sh +``` + +This creates the API key secret, builds and pushes the image, and deploys the agent (Deployment, Service, Route). + +### Step 3: Test the Agent + +Get the route host: + +```bash +oc get route openai-responses-agent -o jsonpath='{.spec.host}' +``` + +Send a test request: + +```bash +curl -X POST https:///chat \ + -H "Content-Type: application/json" \ + -d '{"message": "How much does a Lenovo Laptop costs and what are the reviews?"}' +``` + +## References + +- [OpenAI Python client](https://github.com/openai/openai-python) and [Responses API](https://platform.openai.com/docs/api-reference/responses/create) diff --git a/agents/base/llamastack_agent/deploy.sh b/agents/base/openai_responses_agent/deploy.sh similarity index 62% rename from agents/base/llamastack_agent/deploy.sh rename to agents/base/openai_responses_agent/deploy.sh index 7ea47eec..aac83779 100755 --- a/agents/base/llamastack_agent/deploy.sh +++ b/agents/base/openai_responses_agent/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Deploy LlamaStack Agent to OpenShift +# Deploy OpenAI Responses Agent to OpenShift # # Usage: # ./deploy.sh @@ -26,14 +26,14 @@ docker buildx build --platform linux/amd64 -t "${CONTAINER_IMAGE}" -f Dockerfile # OPENSHIFT CREATE SECRET ## ============================================ -oc delete secret llamastack-agent-secrets --ignore-not-found && echo "Secret deleted" -oc create secret generic llamastack-agent-secrets --from-literal=api-key="${API_KEY}" && echo "Secret created" +oc delete secret openai-responses-agent-secrets --ignore-not-found && echo "Secret deleted" +oc create secret generic openai-responses-agent-secrets --from-literal=api-key="${API_KEY}" && echo "Secret created" ## ============================================ # OPENSHIFT DELETE DEPLOYMENT, SERVICE, ROUTE ## ============================================ -oc delete deployment,service,route -l app=llamastack-agent --ignore-not-found && echo "Previous resources cleaned up" +oc delete deployment,service,route -l app=openai-responses-agent --ignore-not-found && echo "Previous resources cleaned up" ## ============================================ # OPENSHIFT APPLY DEPLOYMENT, SERVICE, ROUTE @@ -42,8 +42,8 @@ envsubst < k8s/deployment.yaml | oc apply -f - && echo "Deployment applied" oc apply -f k8s/service.yaml && echo "Service applied" oc apply -f k8s/route.yaml && echo "Route applied" -oc rollout status deployment/llamastack-agent --timeout=300s && echo "Deployment rolled out" +oc rollout status deployment/openai-responses-agent --timeout=300s && echo "Deployment rolled out" -oc get deployment llamastack-agent && echo "Deployment exists" -oc get service llamastack-agent && echo "Service exists" -oc get route llamastack-agent && echo "Route exists" +oc get deployment openai-responses-agent && echo "Deployment exists" +oc get service openai-responses-agent && echo "Service exists" +oc get route openai-responses-agent && echo "Route exists" diff --git a/agents/base/openai_responses_agent/examples/_interactive_chat.py b/agents/base/openai_responses_agent/examples/_interactive_chat.py new file mode 100644 index 00000000..5ff2fe4e --- /dev/null +++ b/agents/base/openai_responses_agent/examples/_interactive_chat.py @@ -0,0 +1,5 @@ +""" +Placeholder for interactive chat UI using OpenAI Responses agent. + +Reuse or adapt from llamaindex_websearch_agent/examples when needed. +""" diff --git a/agents/base/openai_responses_agent/examples/ai_service.py b/agents/base/openai_responses_agent/examples/ai_service.py new file mode 100644 index 00000000..0b04c952 --- /dev/null +++ b/agents/base/openai_responses_agent/examples/ai_service.py @@ -0,0 +1,5 @@ +""" +Placeholder for OpenAI Responses API-based AI service (streaming or sync). + +Uses OpenAI client and Responses API for chat completions and optionally streaming. +""" diff --git a/agents/base/openai_responses_agent/examples/execute_ai_service_locally.py b/agents/base/openai_responses_agent/examples/execute_ai_service_locally.py new file mode 100644 index 00000000..4c694bf4 --- /dev/null +++ b/agents/base/openai_responses_agent/examples/execute_ai_service_locally.py @@ -0,0 +1,43 @@ +""" +Run agent locally (OpenAI client + pure Python; use OpenAI or any OpenAI-compatible API via BASE_URL). + +Usage: + From repo root with PYTHONPATH including agents/base/openai_responses_agent: + python -m agents.base.openai_responses_agent.examples.execute_ai_service_locally + + Or after cd agents/base/openai_responses_agent and PYTHONPATH=/app:/app/src: + uvicorn main:app --reload + # Then POST /chat with {"message": "Hello"} +""" + +import asyncio +import os + +# Ensure we can import the agent and utils +if __name__ == "__main__": + import sys + + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) + +from openai_responses_agent_base.agent import get_agent_closure +from openai_responses_agent_base.utils import get_env_var + + +async def main(): + base_url = get_env_var("BASE_URL") + model_id = get_env_var("MODEL_ID") + get_agent = get_agent_closure(base_url=base_url, model_id=model_id) + agent = get_agent() + result = await agent.run( + input=[ + { + "role": "user", + "content": "How much does a Lenovo Laptop costs and what are the reviews?", + } + ] + ) + print("Response:", result) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/agents/base/llamastack_agent/init.sh b/agents/base/openai_responses_agent/init.sh similarity index 86% rename from agents/base/llamastack_agent/init.sh rename to agents/base/openai_responses_agent/init.sh index c11b6b39..4de5daa9 100755 --- a/agents/base/llamastack_agent/init.sh +++ b/agents/base/openai_responses_agent/init.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Initialize the LlamaStack Agent +# Initialize the OpenAI Responses Agent # # Usage: # ./init.sh @@ -42,7 +42,7 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" # Copy utils.py to the destination (if present at repo root) if [ -f "$ROOT_DIR/utils.py" ]; then - cp "$ROOT_DIR/utils.py" "$SCRIPT_DIR/src/llamastack_agent_base/" && echo "utils.py copied to destination" + cp "$ROOT_DIR/utils.py" "$SCRIPT_DIR/src/openai_responses_agent_base/" && echo "utils.py copied to destination" fi echo "Agent initialized successfully" diff --git a/agents/base/llamastack_agent/k8s/deployment.yaml b/agents/base/openai_responses_agent/k8s/deployment.yaml similarity index 73% rename from agents/base/llamastack_agent/k8s/deployment.yaml rename to agents/base/openai_responses_agent/k8s/deployment.yaml index fabbe3b0..f256a904 100644 --- a/agents/base/llamastack_agent/k8s/deployment.yaml +++ b/agents/base/openai_responses_agent/k8s/deployment.yaml @@ -1,30 +1,30 @@ -# Deployment for LlamaStack Agent +# Deployment for OpenAI Responses Agent # # Deploy using environment variables: -# 1. Secret for API_KEY (if required by LlamaStack): -# oc create secret generic llamastack-agent-secrets \ +# 1. Secret for API_KEY (if required): +# oc create secret generic openai-responses-agent-secrets \ # --from-literal=api-key='YOUR_API_KEY_HERE' # -# export BASE_URL="https://your-llamastack-endpoint.com" -# export MODEL_ID="Llama3.2-3B-Instruct" +# export BASE_URL="https://api.openai.com/v1" +# export MODEL_ID="gpt-4o-mini" apiVersion: apps/v1 kind: Deployment metadata: - name: llamastack-agent + name: openai-responses-agent labels: - app: llamastack-agent + app: openai-responses-agent spec: replicas: 1 selector: matchLabels: - app: llamastack-agent + app: openai-responses-agent template: metadata: labels: - app: llamastack-agent + app: openai-responses-agent spec: containers: - - name: llamastack-agent + - name: openai-responses-agent image: "${CONTAINER_IMAGE}" ports: - containerPort: 8080 @@ -35,7 +35,7 @@ spec: - name: API_KEY valueFrom: secretKeyRef: - name: llamastack-agent-secrets + name: openai-responses-agent-secrets key: api-key - name: BASE_URL value: "${BASE_URL}" diff --git a/agents/base/llamastack_agent/k8s/route.yaml b/agents/base/openai_responses_agent/k8s/route.yaml similarity index 67% rename from agents/base/llamastack_agent/k8s/route.yaml rename to agents/base/openai_responses_agent/k8s/route.yaml index 82669dcf..39f6a57e 100644 --- a/agents/base/llamastack_agent/k8s/route.yaml +++ b/agents/base/openai_responses_agent/k8s/route.yaml @@ -1,13 +1,13 @@ apiVersion: route.openshift.io/v1 kind: Route metadata: - name: llamastack-agent + name: openai-responses-agent labels: - app: llamastack-agent + app: openai-responses-agent spec: to: kind: Service - name: llamastack-agent + name: openai-responses-agent port: targetPort: http tls: diff --git a/agents/base/llamastack_agent/k8s/service.yaml b/agents/base/openai_responses_agent/k8s/service.yaml similarity index 63% rename from agents/base/llamastack_agent/k8s/service.yaml rename to agents/base/openai_responses_agent/k8s/service.yaml index 32c0b96c..62b73720 100644 --- a/agents/base/llamastack_agent/k8s/service.yaml +++ b/agents/base/openai_responses_agent/k8s/service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: llamastack-agent + name: openai-responses-agent labels: - app: llamastack-agent + app: openai-responses-agent spec: type: ClusterIP ports: @@ -12,4 +12,4 @@ spec: protocol: TCP name: http selector: - app: llamastack-agent + app: openai-responses-agent diff --git a/agents/base/llamastack_agent/main.py b/agents/base/openai_responses_agent/main.py similarity index 76% rename from agents/base/llamastack_agent/main.py rename to agents/base/openai_responses_agent/main.py index cd756770..9098c654 100644 --- a/agents/base/llamastack_agent/main.py +++ b/agents/base/openai_responses_agent/main.py @@ -2,8 +2,8 @@ from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException -from llamastack_agent_base.agent import get_agent_closure -from llamastack_agent_base.utils import get_env_var +from openai_responses_agent_base.agent import get_agent_closure +from openai_responses_agent_base.utils import get_env_var from pydantic import BaseModel @@ -27,17 +27,20 @@ class ChatResponse(BaseModel): @asynccontextmanager async def lifespan(app: FastAPI): - """Initialize the LlamaStack agent closure on startup and clear it on shutdown. + """Initialize the agent closure on startup and clear it on shutdown. Reads BASE_URL and MODEL_ID from the environment and sets the global get_agent - for the /chat endpoint. Uses only LlamaStack API (no LlamaIndex). + for the /chat endpoint. Uses OpenAI client and Responses API (no agentic framework). """ global get_agent base_url = get_env_var("BASE_URL") model_id = get_env_var("MODEL_ID") - # LlamaStack base_url is typically full (e.g. http://localhost:8321); no /v1 suffix required + # Ensure base_url ends with /v1 if provided + if base_url and not base_url.endswith("/v1"): + base_url = base_url.rstrip("/") + "/v1" + get_agent = get_agent_closure(base_url=base_url, model_id=model_id) yield @@ -46,8 +49,8 @@ async def lifespan(app: FastAPI): app = FastAPI( - title="LlamaStack Agent API", - description="FastAPI service for LlamaStack Agent (LlamaStack API only, no LlamaIndex)", + title="OpenAI Responses Agent API", + description="FastAPI service for agent (OpenAI client + pure Python, Responses API, no agentic framework)", lifespan=lifespan, ) diff --git a/agents/base/llamastack_agent/pyproject.toml b/agents/base/openai_responses_agent/pyproject.toml similarity index 59% rename from agents/base/llamastack_agent/pyproject.toml rename to agents/base/openai_responses_agent/pyproject.toml index 7452cb6b..98647b68 100644 --- a/agents/base/llamastack_agent/pyproject.toml +++ b/agents/base/openai_responses_agent/pyproject.toml @@ -1,17 +1,17 @@ [tool.poetry] -name = "llamastack_agent_base" +name = "openai_responses_agent_base" version = "0.1.0" -description = "Agent built on LlamaStack API only (no LlamaIndex). Placeholder for full llama-stack-client integration." +description = "Agent without any agentic framework: OpenAI client and pure Python (Responses API)." authors = ["Your Name "] license = "MIT" packages = [ - { include = "llamastack_agent_base", from = "src" } + { include = "openai_responses_agent_base", from = "src" } ] [tool.poetry.dependencies] python = ">=3.12" python-dotenv = "^1.0.0" -llama-stack-client = ">=0.4.0" +openai = ">=1.0.0" [tool.poetry.group.dev] optional = true diff --git a/agents/base/llamastack_agent/requirements.txt b/agents/base/openai_responses_agent/requirements.txt similarity index 71% rename from agents/base/llamastack_agent/requirements.txt rename to agents/base/openai_responses_agent/requirements.txt index 54100d97..55f650f1 100644 --- a/agents/base/llamastack_agent/requirements.txt +++ b/agents/base/openai_responses_agent/requirements.txt @@ -1,4 +1,4 @@ fastapi>=0.115.0 uvicorn[standard]>=0.32.0 python-dotenv>=1.0.0 -llama-stack-client>=0.4.0 +openai>=1.0.0 diff --git a/agents/base/openai_responses_agent/src/openai_responses_agent_base/__init__.py b/agents/base/openai_responses_agent/src/openai_responses_agent_base/__init__.py new file mode 100644 index 00000000..4d09ab1f --- /dev/null +++ b/agents/base/openai_responses_agent/src/openai_responses_agent_base/__init__.py @@ -0,0 +1,18 @@ +""" +OpenAI Responses Agent Base – agent without any agentic framework. + +Uses only the OpenAI Python client and pure Python (Responses API). +No LlamaStack, LangChain, LlamaIndex, etc. Compatible with OpenAI and OpenAI-compatible endpoints. +""" + +from openai_responses_agent_base.agent import AIAgent, get_agent_closure +from openai_responses_agent_base.tools import search_reviews, search_price +from openai_responses_agent_base.utils import get_env_var + +__all__ = [ + "get_agent_closure", + "AIAgent", + "search_reviews", + "search_price", + "get_env_var", +] diff --git a/agents/base/llamastack_agent/src/llamastack_agent_base/agent.py b/agents/base/openai_responses_agent/src/openai_responses_agent_base/agent.py similarity index 70% rename from agents/base/llamastack_agent/src/llamastack_agent_base/agent.py rename to agents/base/openai_responses_agent/src/openai_responses_agent_base/agent.py index fe8ff5a6..78c2a0fe 100644 --- a/agents/base/llamastack_agent/src/llamastack_agent_base/agent.py +++ b/agents/base/openai_responses_agent/src/openai_responses_agent_base/agent.py @@ -1,8 +1,9 @@ """ -LlamaStack agent – chat and tools agent using LlamaStack Client instead of OpenAI. +Agent without any agentic framework: OpenAI client and pure Python. -See: https://llama-stack.readthedocs.io/ and https://pypi.org/project/llama-stack-client/ -Based on the OpenAI example; all calls go through the LlamaStack API. +Uses only the official OpenAI Python client (openai package) and Responses API. +No LlamaStack, LangChain, LlamaIndex, etc. - to show it can be done without frameworks. +Compatible with OpenAI API and any OpenAI-compatible endpoint (e.g. base_url override). """ import asyncio @@ -13,10 +14,10 @@ from typing import Any, Callable, Dict, List, Optional from dotenv import load_dotenv -from llama_stack_client import LlamaStackClient +from openai import OpenAI -from llamastack_agent_base.utils import get_env_var -from llamastack_agent_base.tools import search_price, search_reviews +from openai_responses_agent_base.utils import get_env_var +from openai_responses_agent_base.tools import search_price, search_reviews def get_agent_closure( @@ -92,8 +93,44 @@ async def run(self, input: List[Dict[str, Any]]) -> Dict[str, Any]: return {"messages": response_messages, "finish_reason": "stop"} +def _messages_to_responses_input(messages: List[Dict]) -> tuple[str, List[Dict]]: + """ + Convert chat-style messages to Responses API format. + Returns (instructions, input_items) where instructions is the system content + and input_items is a list of {role, content} with content as [{type: 'input_text', text: '...'}]. + """ + instructions = "" + input_items: List[Dict] = [] + for m in messages: + role = m.get("role", "user") + content = m.get("content", "") or "" + text_content = [{"type": "input_text", "text": content}] + if role == "system": + instructions = content + continue + input_items.append({"role": role, "content": text_content}) + return instructions, input_items + + +def _get_output_text_from_response(response: Any) -> str: + """Extract assistant text from Responses API response (response.output[].content[]).""" + if not getattr(response, "output", None) or not response.output: + return "" + for item in response.output: + content = getattr(item, "content", None) or [] + for block in content: + if getattr(block, "type", None) == "output_text": + return getattr(block, "text", None) or "" + # some SDKs may expose .text directly + if hasattr(block, "text"): + return block.text or "" + return "" + + class AIAgent: - """Agent that uses tools and chat via the LlamaStack API instead of OpenAI.""" + """ + Agent using only OpenAI client and pure Python: Responses API, no agentic framework. + """ def __init__( self, @@ -103,13 +140,13 @@ def __init__( api_key: Optional[str] = None, ): """ - Initialize the agent with tools and LlamaStack configuration. + Initialize the agent with tools and OpenAI client configuration. Args: - model: Model identifier in LlamaStack (e.g. "Llama3.2-3B-Instruct") - temperature: Sampling temperature (0 = deterministic) - base_url: LlamaStack endpoint URL (e.g. http://localhost:8321) - api_key: Optional API key + model: Model identifier (e.g. "gpt-4o-mini" or provider-specific id). + temperature: Sampling temperature (0 = deterministic). + base_url: Optional API base URL (for OpenAI-compatible endpoints). + api_key: Optional API key (required for OpenAI; can be None for some local endpoints). """ load_dotenv() @@ -123,11 +160,14 @@ def __init__( except (EnvironmentError, ValueError): api_key = None - kwargs: Dict = {"base_url": base_url.rstrip("/")} + # OpenAI client: works with api.openai.com or any OpenAI-compatible API (base_url) + client_kwargs: Dict[str, Any] = {} + if base_url: + client_kwargs["base_url"] = base_url.rstrip("/") if api_key: - kwargs["api_key"] = api_key + client_kwargs["api_key"] = api_key - self.client = LlamaStackClient(**kwargs) + self.client = OpenAI(**client_kwargs) self.model = model self.temperature = temperature self.tools: Dict[str, Callable] = {} @@ -152,62 +192,57 @@ def _parse_arguments(self, args_str: str) -> List[str]: args = next(reader) return [arg.strip().strip("'\"") for arg in args] - def chat_completion( + def _responses_create( self, messages: Optional[List[Dict]] = None, temperature: Optional[float] = None, model: Optional[str] = None, ): """ - Single chat completion call via LlamaStack (equivalent to OpenAI chat.completions.create). + Single Responses API call via OpenAI client. Args: - messages: List of messages; if None, self.messages is used - temperature: Override temperature for this call - model: Override model for this call + messages: List of messages; if None, self.messages is used. + temperature: Override temperature for this call. + model: Override model for this call. Returns: - Response from client.chat.completions.create (object with .choices etc.) + Response from client.responses.create (object with .output etc.). """ msg_list = messages if messages is not None else self.messages temp = temperature if temperature is not None else self.temperature model_id = model if model is not None else self.model - kwargs: Dict = { + instructions, input_items = _messages_to_responses_input(msg_list) + kwargs: Dict[str, Any] = { "model": model_id, - "messages": msg_list, + "instructions": instructions, + "input": input_items, } if temp != 0: kwargs["temperature"] = temp - return self.client.chat.completions.create(**kwargs) - - def get_response_content(self, response: Any) -> str: - """Extract assistant response text from the completion response object.""" - if not response.choices or len(response.choices) == 0: - return "" - msg = response.choices[0].message - return getattr(msg, "content", None) or "" + return self.client.responses.create(**kwargs) def _execute(self) -> str: - """Execute a chat completion request.""" - completion = self.client.chat.completions.create( - model=self.model, - temperature=self.temperature, + """Execute a Responses API request.""" + response = self._responses_create( messages=self.messages, + temperature=self.temperature, + model=self.model, ) - return self.get_response_content(completion) + return _get_output_text_from_response(response) def query(self, question: str, max_turns: int = 10) -> Optional[str]: """ Process a question through multiple turns until getting final answer. Args: - question: The input question to process - max_turns: Maximum number of turns before timing out + question: The input question to process. + max_turns: Maximum number of turns before timing out. Returns: - The final answer or None if no answer found + The final answer or None if no answer found. """ self.setup_system_prompt() next_prompt = question diff --git a/agents/base/llamastack_agent/src/llamastack_agent_base/tools.py b/agents/base/openai_responses_agent/src/openai_responses_agent_base/tools.py similarity index 100% rename from agents/base/llamastack_agent/src/llamastack_agent_base/tools.py rename to agents/base/openai_responses_agent/src/openai_responses_agent_base/tools.py diff --git a/agents/base/llamastack_agent/src/llamastack_agent_base/utils.py b/agents/base/openai_responses_agent/src/openai_responses_agent_base/utils.py similarity index 100% rename from agents/base/llamastack_agent/src/llamastack_agent_base/utils.py rename to agents/base/openai_responses_agent/src/openai_responses_agent_base/utils.py From a7392ae10ed5fcbf9a3c70586dfdcda2a32f37b6 Mon Sep 17 00:00:00 2001 From: Wojciech Rebisz <147821486+Wojciech-Rebisz@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:29:54 +0100 Subject: [PATCH 2/3] Update main README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1d8559a..7d7cbc26 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Agentic-Starter-Kits/ │ ├── base/ │ │ ├── langgraph_react_agent/ # LangGraph ReAct agent │ │ └── llamaindex_websearch_agent/ # LlamaIndex web search agent -│ │ └── llamastack_agent/ # Llamastack bare API +│ │ └── openai_responses_agent/ # OpenAI Responses API (no framework) │ └── community/ │ └── langgraph_agentic_rag/ # RAG agent with Milvus vector store ├── run_llama_server.yaml # Llama Stack server configuration @@ -90,7 +90,7 @@ After completing the installation, choose an agent and follow its specific READM **Base Agents:** - **[LangGraph ReAct Agent](./agents/base/langgraph_react_agent/README.md)** - General-purpose agent with tool use - **[LlamaIndex WebSearch Agent](./agents/base/llamaindex_websearch_agent/README.md)** - Web search capabilities -- **[Llama Stack bare API](./agents/base/llamastack_agent/README.md)** - Web search capabilities +- **[OpenAI Responses Agent](./agents/base/openai_responses_agent/README.md)** - OpenAI client + pure Python (Responses API) **Community Agents:** - **[LangGraph Agentic RAG](./agents/community/langgraph_agentic_rag/README.md)** - RAG with Milvus vector store @@ -148,7 +148,7 @@ After completing the installation, choose an agent and follow its specific READM **Base Agents:** - **[LangGraph ReAct Agent](./agents/base/langgraph_react_agent/README.md)** - General-purpose agent with tool use - **[LlamaIndex WebSearch Agent](./agents/base/llamaindex_websearch_agent/README.md)** - Web search capabilities -- **[Llama Stack bare API](./agents/base/llamastack_agent/README.md)** - Web search capabilities +- **[OpenAI Responses Agent](./agents/base/openai_responses_agent/README.md)** - OpenAI client + pure Python (Responses API) **Community Agents:** - **[LangGraph Agentic RAG](./agents/community/langgraph_agentic_rag/README.md)** - RAG with Milvus vector store From acd9b43a1f74abaeee657e0373b5c66c540cf0ab Mon Sep 17 00:00:00 2001 From: Wojciech Rebisz <147821486+Wojciech-Rebisz@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:25:05 +0100 Subject: [PATCH 3/3] Update local execution --- .../examples/_interactive_chat.py | 124 +++++++++++++++++- .../examples/ai_service.py | 70 +++++++++- .../examples/execute_ai_service_locally.py | 70 +++++----- 3 files changed, 223 insertions(+), 41 deletions(-) diff --git a/agents/base/openai_responses_agent/examples/_interactive_chat.py b/agents/base/openai_responses_agent/examples/_interactive_chat.py index 5ff2fe4e..1ffd980f 100644 --- a/agents/base/openai_responses_agent/examples/_interactive_chat.py +++ b/agents/base/openai_responses_agent/examples/_interactive_chat.py @@ -1,5 +1,121 @@ -""" -Placeholder for interactive chat UI using OpenAI Responses agent. +import textwrap +import json +from collections.abc import Generator +from typing import Callable -Reuse or adapt from llamaindex_websearch_agent/examples when needed. -""" + +class InteractiveChat: + """Interactive REPL that prompts for user input and calls the AI service for each question.""" + + def __init__( + self, + ai_service_invoke: Callable, + questions: tuple[str] = None, + stream: bool = False, + verbose: bool = True, + ) -> None: + """Set up the chat: invoke callable, optional preset questions, and stream/verbose flags.""" + self.ai_service_invoke = ai_service_invoke + self._ordered_list = lambda seq_: "\n".join( + f"\t{i}) {k}" for i, k in enumerate(seq_, 1) + ) + self.verbose = verbose + self.stream = stream + self._last_role = None # Tracks role changes to split headers + + self.questions = ("Hi! How are you?",) if questions is None else questions + self._help_message = textwrap.dedent(""" + The following commands are supported: + --> help | h : prints this help message + --> quit | q : exits the prompt and ends the program + --> list_questions : prints a list of available questions + """) + + @property + def questions(self) -> tuple: + """Return the tuple of preset questions (e.g. for list_questions).""" + return self._questions + + @questions.setter + def questions(self, q: tuple) -> None: + """Set preset questions and refresh the list shown by list_questions.""" + self._questions = q + self._questions_prompt = ( + f"\tQuestions:\n{self._ordered_list(self._questions)}\n" + ) + + def _print_message(self, choice: dict) -> None: + """Print one choice from the AI response (streaming delta or full message) with role headers.""" + if delta := choice.get("delta"): + current_role = delta.get("role") + + # Check if we need to print a new header + if current_role != self._last_role: + display_names = { + "tool_answer": "Answer from tool", + "assistant_answer": "Assistant answer", + } + header_text = display_names.get(current_role, current_role.capitalize()) + print(f"\n{f' {header_text} '.center(80, '=')}") + self._last_role = current_role + + content = delta.get("content") + if content: + print(content, flush=True, end="") + else: + # Non-streaming fallback + msg = choice.get("message", {}) + header = f" {msg.get('role', 'Assistant').capitalize()} Message ".center( + 80, "=" + ) + print(f"\n{header}\n{msg.get('content', '')}") + + def _user_input_loop(self) -> Generator[tuple[str, str], None, None]: + """Yield (user input line, stage) forever; stage is 'question' for normal prompts.""" + print(self._help_message) + while True: + q = input("\nChoose a question or ask one of your own.\n --> ") + yield q, "question" + + def run(self) -> None: + """Run the REPL: read input, handle help/quit/list_questions, or send questions to the AI and print replies.""" + user_loop = self._user_input_loop() + while True: + try: + action, stage = next(user_loop) + + if action in ["h", "help"]: + print(self._help_message) + elif action in ["quit", "q"]: + break + elif action == "list_questions": + print(self._questions_prompt) + elif stage == "question": + # Reset role tracking for new question + self._last_role = None + + user_content = action.strip() + if action.isdigit(): + idx = int(action) - 1 + if 0 <= idx < len(self.questions): + user_content = self.questions[idx] + print(f"You chose: {user_content}\n") + + payload = {"messages": [{"role": "user", "content": user_content}]} + resp = self.ai_service_invoke(payload) + + if self.stream: + for r in resp: + if isinstance(r, str): + r = json.loads(r) + for c in r.get("choices", []): + self._print_message(c) + print() # Final newline + else: + # Standard invoke handling + choices = resp.get("body", {}).get("choices", []) + for c in choices: + self._print_message(c) + + except (EOFError, StopIteration): + break diff --git a/agents/base/openai_responses_agent/examples/ai_service.py b/agents/base/openai_responses_agent/examples/ai_service.py index 0b04c952..cddf8a8b 100644 --- a/agents/base/openai_responses_agent/examples/ai_service.py +++ b/agents/base/openai_responses_agent/examples/ai_service.py @@ -1,5 +1,71 @@ """ -Placeholder for OpenAI Responses API-based AI service (streaming or sync). +AI service for OpenAI Responses Agent: run the agent on request payload and return (generate, generate_stream). -Uses OpenAI client and Responses API for chat completions and optionally streaming. +Uses OpenAI client and Responses API. Both generate and generate_stream accept a context +whose get_json() returns the request payload (e.g. {"messages": [...]}). """ +import asyncio +from typing import Generator + +from openai_responses_agent_base.agent import get_agent_closure + + +def ai_stream_service(context, base_url=None, model_id=None): + """Create a deployable AI service that runs the OpenAI Responses agent and returns (generate, generate_stream). + + Builds the agent closure once, then returns two callables: one for a single + non-streaming response and one that yields one update (full answer) for compatibility + with the interactive chat. Both accept a context object whose get_json() returns the + request payload (e.g. {"messages": [...]}). + + Args: + context: Object with get_json() used to read the request payload (not used at setup). + base_url: LLM API base URL; uses BASE_URL env if omitted. + model_id: LLM model id; uses MODEL_ID env if omitted. + + Returns: + Tuple (generate, generate_stream). generate returns a dict with body/choices; + generate_stream yields choice dicts with delta (one chunk with full content). + """ + get_agent = get_agent_closure(base_url=base_url, model_id=model_id) + + def generate(context) -> dict: + """Run the agent once on the context payload and return a single response dict (headers + body with choices).""" + payload = context.get_json() + messages = payload.get("messages", []) + agent = get_agent() + result = asyncio.run(agent.run(input=messages)) + # result["messages"] includes history + last assistant message + last_msg = result["messages"][-1] if result["messages"] else {"role": "assistant", "content": ""} + content = last_msg.get("content", "") + return { + "headers": {"Content-Type": "application/json"}, + "body": { + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": content}, + } + ] + }, + } + + def generate_stream(context) -> Generator[dict, None, None]: + """Yield one choice delta with the full assistant answer (no true streaming; same format as LangGraph).""" + payload = context.get_json() + messages = payload.get("messages", []) + agent = get_agent() + result = asyncio.run(agent.run(input=messages)) + last_msg = result["messages"][-1] if result["messages"] else {"role": "assistant", "content": ""} + content = last_msg.get("content", "") + yield { + "choices": [ + { + "index": 0, + "delta": {"role": "assistant", "content": content}, + "finish_reason": None, + } + ] + } + + return generate, generate_stream diff --git a/agents/base/openai_responses_agent/examples/execute_ai_service_locally.py b/agents/base/openai_responses_agent/examples/execute_ai_service_locally.py index 4c694bf4..c5701936 100644 --- a/agents/base/openai_responses_agent/examples/execute_ai_service_locally.py +++ b/agents/base/openai_responses_agent/examples/execute_ai_service_locally.py @@ -1,43 +1,43 @@ -""" -Run agent locally (OpenAI client + pure Python; use OpenAI or any OpenAI-compatible API via BASE_URL). +from _interactive_chat import InteractiveChat +from ai_service import ai_stream_service +from openai_responses_agent_base.utils import get_env_var -Usage: - From repo root with PYTHONPATH including agents/base/openai_responses_agent: - python -m agents.base.openai_responses_agent.examples.execute_ai_service_locally - Or after cd agents/base/openai_responses_agent and PYTHONPATH=/app:/app/src: - uvicorn main:app --reload - # Then POST /chat with {"message": "Hello"} -""" +class SimpleContext: + """Simple context object for local execution that holds request payload and headers.""" -import asyncio -import os + def __init__(self, payload=None): + """Store the initial request payload (or an empty dict).""" + self.request_payload_json = payload or {} -# Ensure we can import the agent and utils -if __name__ == "__main__": - import sys + def get_json(self): + """Return the current request payload as a dict (e.g. messages for the agent).""" + return self.request_payload_json - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) + def get_headers(self): + """Return request headers; empty dict for local execution.""" + return {} -from openai_responses_agent_base.agent import get_agent_closure -from openai_responses_agent_base.utils import get_env_var + +base_url = get_env_var("BASE_URL") +model_id = get_env_var("MODEL_ID") + +# Ensure base_url ends with /v1 if provided +if base_url and not base_url.endswith("/v1"): + base_url = base_url.rstrip("/") + "/v1" + +stream = True +context = SimpleContext() +ai_service_resp_func = ai_stream_service( + context=context, base_url=base_url, model_id=model_id +)[stream] + + +def ai_service_invoke(payload): + """Run the AI service for one turn: set context from payload and return (stream or full) response.""" + context.request_payload_json = payload + return ai_service_resp_func(context) -async def main(): - base_url = get_env_var("BASE_URL") - model_id = get_env_var("MODEL_ID") - get_agent = get_agent_closure(base_url=base_url, model_id=model_id) - agent = get_agent() - result = await agent.run( - input=[ - { - "role": "user", - "content": "How much does a Lenovo Laptop costs and what are the reviews?", - } - ] - ) - print("Response:", result) - - -if __name__ == "__main__": - asyncio.run(main()) +chat = InteractiveChat(ai_service_invoke, stream=stream) +chat.run()