Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions ai/gen-ai-agents/agentsOCI-OpenAI-gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

# OCI OpenAI-Compatible Gateway

Simple FastAPI gateway exposing OCI LLMs and Agents via OpenAI-compatible API.
Big thanks to https://github.com/RETAJD/modelsOCI-toOpenAI/tree/main

## Quick Start

1. **Install dependencies**:
```bash
pip install fastapi uvicorn oci pyyaml openai
```

2. **Set API key (optional)**:
```bash
export GATEWAY_API_KEYS="ocigenerativeai" #default
```

3. **Prepare config files** (`agents.yaml`, `models.yaml`) next to `app.py`:

Example `agents.yaml`:
```yaml
agents:
- id: "sales-kb"
name: "Sales KB Agent"
description: "Grounded in sales docs"
region: "eu-frankfurt-1"
endpoint_ocid: "ocid1.genaiagentendpoint.oc1.xxx"
```

Example `models.yaml`:
```yaml
region: eu-frankfurt-1
compartment_id: "ocid1.compartment.oc1..xxx"
models:
ondemand:
- name: "cohere.command-r"
model_id: "cohere.command-r"
description: "Command R Model"
```

4. **Run the app**:
```bash
uvicorn app:app --host 0.0.0.0 --port 8088
```
or
```bash
python app.py
```

## Usage Example

```python
from openai import OpenAI

client = OpenAI(api_key="ocigenerativeai", base_url="http://localhost:8088/v1/")

r1 = client.chat.completions.create(
model="ignored",
messages=[{"role": "user", "content": "Reply with 'pong'."}],
extra_body={
"agent_endpoint_ocid": "ocid1.genaiagentendpoint.oc1.eu-frankfurt-1.", #your genai agent **endpoint** OCID
"region": "eu-frankfurt-1",
},
)
print(r1.choices[0].message.content)

```

## n8n/Open WebUI Integration

- URL: `http://localhost:8088/v1`
- Model: `agent:sales-kb` or model names from `models.yaml`
7 changes: 7 additions & 0 deletions ai/gen-ai-agents/agentsOCI-OpenAI-gateway/agents.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# agents.yaml
agents:
- id: "sales-kb" # your local handle
name: "Sales KB Agent"
description: "Grounded in sales docs"
region: "eu-frankfurt-1" # agent region
endpoint_ocid: "ocid1.genaiagentendpoint.oc1.eu-frankfurt-1." # ai agent **endpoint OCID**
Empty file.
28 changes: 28 additions & 0 deletions ai/gen-ai-agents/agentsOCI-OpenAI-gateway/api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
from typing import Annotated

# import boto3
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

from api.setting import DEFAULT_API_KEYS

api_key_param = os.environ.get("API_KEY_PARAM_NAME")
# if api_key_param:
# ssm = boto3.client("ssm")
# api_key = ssm.get_parameter(Name=api_key_param, WithDecryption=True)["Parameter"][
# "Value"
# ]
# else:
api_key = DEFAULT_API_KEYS

security = HTTPBearer()


def api_key_auth(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]
):
if credentials.credentials != api_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API Key"
)
Empty file.
67 changes: 67 additions & 0 deletions ai/gen-ai-agents/agentsOCI-OpenAI-gateway/api/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import time
import uuid
from abc import ABC, abstractmethod
from typing import AsyncIterable

from api.schema import (
# Chat
ChatResponse,
ChatRequest,
ChatStreamResponse,
# Embeddings
EmbeddingsRequest,
EmbeddingsResponse,
)


class BaseChatModel(ABC):
"""Represent a basic chat model

Currently, OCI GenAI model is supported.
"""

def list_models(self) -> list[str]:
"""Return a list of supported models"""
return []

def validate(self, chat_request: ChatRequest):
"""Validate chat completion requests."""
pass

@abstractmethod
def chat(self, chat_request: ChatRequest) -> ChatResponse:
"""Handle a basic chat completion requests."""
pass

@abstractmethod
def chat_stream(self, chat_request: ChatRequest) -> AsyncIterable[bytes]:
"""Handle a basic chat completion requests with stream response."""
pass

@staticmethod
def generate_message_id() -> str:
return "chatcmpl-" + str(uuid.uuid4())[:8]

@staticmethod
def stream_response_to_bytes(
response: ChatStreamResponse | None = None
) -> bytes:
if response:
# to populate other fields when using exclude_unset=True
response.system_fingerprint = "fp"
response.object = "chat.completion.chunk"
response.created = int(time.time())
return "data: {}\n\n".format(response.model_dump_json(exclude_unset=True)).encode("utf-8")
return "data: [DONE]\n\n".encode("utf-8")


class BaseEmbeddingsModel(ABC):
"""Represents a basic embeddings model.

Currently, OCI GenAI models are supported.
"""

@abstractmethod
def embed(self, embeddings_request: EmbeddingsRequest) -> EmbeddingsResponse:
"""Handle a basic embeddings request."""
pass
Loading