Skip to content
Merged

Dev #18

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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ OLI_OPENAI_BASE_URL="https://api.openai.com/v1"
OLI_OPENAI_API_KEY=""
OLI_OPENAI_MODEL=""
OLI_OPENAI_SMALL_MODEL=""
# Vision serialization: "openai" (default) or "bedrock" (for Kong/LiteLLM proxies fronting Bedrock)
OLI_OPENAI_VISION_STYLE="openai"
OLI_OPENAI_OPTIONAL_HEADERS='{}'

# HuggingFace configuration
OLI_HUGGINGFACE_BASE_URL="https://api-inference.huggingface.co"
Expand Down
20 changes: 16 additions & 4 deletions oli_bot/backends/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
UsageChunk,
)

from ..config import configs

from .base import (
MAX_TOKENS,
TEMPERATURE,
Expand All @@ -30,6 +32,14 @@

logger = logging.getLogger(__name__)

_raw_headers = configs.openai_optional_headers
if isinstance(_raw_headers, str):
_OPTIONAL_HEADERS = json.loads(_raw_headers)
elif isinstance(_raw_headers, dict):
_OPTIONAL_HEADERS = _raw_headers
else:
_OPTIONAL_HEADERS = {}


class OpenAIBackend(ModelBackend):
def __init__(
Expand All @@ -42,14 +52,16 @@ def __init__(
self.model = model
self.api_key = api_key
self.base_url = base_url
# "openai" keeps the standard image_url payload; "bedrock" emits
# Bedrock-native image blocks
self.vision_style = vision_style
self.client = AsyncOpenAI(api_key=api_key, base_url=base_url)
self.client = AsyncOpenAI(
api_key=api_key, base_url=base_url, default_headers=_OPTIONAL_HEADERS
)

def set_base_url(self, url: str) -> None:
self.base_url = url
self.client = AsyncOpenAI(api_key=self.api_key, base_url=url)
self.client = AsyncOpenAI(
api_key=self.api_key, base_url=url, default_headers=_OPTIONAL_HEADERS
)

async def generate(
self,
Expand Down
5 changes: 2 additions & 3 deletions oli_bot/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
oli — a Multi-Backend AI Chat Agent with a Textual-based TUI chat interface.
"""

import re
import time
import argparse
import asyncio
import logging
import random
import re
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
Expand Down
5 changes: 4 additions & 1 deletion oli_bot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class AppConfig(BaseSettings):
populate_by_name=True,
)

# Backend selection: "ollama", "openai", or "huggingface"
# Backend selection: "ollama", "openai", "huggingface", or "transformers"
backend: str = Field(default="ollama")

# OpenAI configuration
Expand All @@ -42,6 +42,9 @@ class AppConfig(BaseSettings):
# Bedrock-native {"image": {"format", "source": {"bytes"}}} blocks for
# OpenAI-compatible proxies that pass content through to Bedrock unchanged.
openai_vision_style: str = Field(default="openai")
# Optional headers to include in OpenAI API requests. This can be used to
# pass additional headers required by certain OpenAI-compatible endpoints.
openai_optional_headers: Optional[dict] = Field(default={})

# Ollama
ollama_base_url: str = Field(default="http://localhost:11434")
Expand Down
20 changes: 20 additions & 0 deletions oli_bot/screens/config_screen.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import json

from textual.app import ComposeResult
from textual.containers import Container, Horizontal, VerticalScroll
from textual.screen import ModalScreen
Expand Down Expand Up @@ -165,6 +167,12 @@ def compose(self) -> ComposeResult:
RadioButton("bedrock", id="vs-bedrock"),
id="cfg-openai-vision-style",
)
yield Input(
placeholder='Optional headers (JSON, e.g. {"X-Token": "..."})',
id="cfg-openai-optional-headers",
classes="config-input",
value=json.dumps(op.get("optional_headers", {})),
)

yield Label(
"Ollama", id="ollama-section-title", classes="section-title"
Expand Down Expand Up @@ -504,6 +512,9 @@ def _save(self) -> None:
"large_model": self._val("#cfg-openai-large-model"),
"small_model": self._val("#cfg-openai-small-model"),
"vision_style": vision_style,
"optional_headers": self._json(
"#cfg-openai-optional-headers"
),
},
"ollama": {
"base_url": self._val("#cfg-ollama-base-url"),
Expand Down Expand Up @@ -585,6 +596,15 @@ def _float(self, selector: str, default: float) -> float:
def _bool(self, selector: str) -> bool:
return self.query_one(selector, Checkbox).value

def _json(self, selector: str) -> dict:
try:
value = json.loads(self._val(selector) or "{}")
if isinstance(value, dict):
return value
return {}
except (json.JSONDecodeError, TypeError):
return {}

@staticmethod
def _radio_label(rs: RadioSet, default: str) -> str:
buttons = list(rs.query(RadioButton))
Expand Down
4 changes: 4 additions & 0 deletions oli_bot/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"large_model": "gpt-5",
"small_model": "gpt-5-mini",
"vision_style": "openai",
"optional_headers": {},
},
"ollama": {
"base_url": "http://localhost:11434",
Expand Down Expand Up @@ -86,6 +87,7 @@
"OLI_OPENAI_MODEL": "openai.large_model",
"OLI_OPENAI_SMALL_MODEL": "openai.small_model",
"OLI_OPENAI_VISION_STYLE": "openai.vision_style",
"OLI_OPENAI_OPTIONAL_HEADERS": "openai.optional_headers",
"OLI_OLLAMA_BASE_URL": "ollama.base_url",
"OLI_OLLAMA_MODEL": "ollama.large_model",
"OLI_OLLAMA_SMALL_MODEL": "ollama.small_model",
Expand Down Expand Up @@ -289,6 +291,7 @@ def to_appconfig(self, settings: dict) -> AppConfig:
openai_model=op.get("large_model", "gpt-5"),
openai_small_model=op.get("small_model", "gpt-5-mini"),
openai_vision_style=op.get("vision_style", "openai"),
openai_optional_headers=op.get("optional_headers", {}),
ollama_base_url=ol.get("base_url", "http://localhost:11434"),
ollama_model=ol.get("large_model", ""),
ollama_small_model=ol.get("small_model", ""),
Expand Down Expand Up @@ -338,6 +341,7 @@ def from_appconfig(self, config: AppConfig) -> dict:
settings["openai"]["large_model"] = config.openai_model
settings["openai"]["small_model"] = config.openai_small_model
settings["openai"]["vision_style"] = config.openai_vision_style
settings["openai"]["optional_headers"] = config.openai_optional_headers
settings["ollama"]["base_url"] = config.ollama_base_url
settings["ollama"]["large_model"] = config.ollama_model
settings["ollama"]["small_model"] = config.ollama_small_model
Expand Down