Add GPT-5-Codex support (#37)
This commit is contained in:
committed by
GitHub
parent
2f23cd5a89
commit
77d60fe321
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from flask import Flask, jsonify
|
||||
|
||||
from .config import BASE_INSTRUCTIONS
|
||||
from .config import BASE_INSTRUCTIONS, GPT5_CODEX_INSTRUCTIONS
|
||||
from .http import build_cors_headers
|
||||
from .routes_openai import openai_bp
|
||||
from .routes_ollama import ollama_bp
|
||||
@@ -26,6 +26,7 @@ def create_app(
|
||||
REASONING_COMPAT=reasoning_compat,
|
||||
DEBUG_MODEL=debug_model,
|
||||
BASE_INSTRUCTIONS=BASE_INSTRUCTIONS,
|
||||
GPT5_CODEX_INSTRUCTIONS=GPT5_CODEX_INSTRUCTIONS,
|
||||
EXPOSE_REASONING_MODELS=bool(expose_reasoning_models),
|
||||
DEFAULT_WEB_SEARCH=bool(default_web_search),
|
||||
)
|
||||
|
||||
@@ -10,26 +10,37 @@ CLIENT_ID_DEFAULT = os.getenv("CHATGPT_LOCAL_CLIENT_ID") or "app_EMoamEEZ73f0CkX
|
||||
CHATGPT_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses"
|
||||
|
||||
|
||||
def read_base_instructions() -> str:
|
||||
def _read_prompt_text(filename: str) -> str | None:
|
||||
candidates = [
|
||||
Path(__file__).parent.parent / "prompt.md",
|
||||
Path(__file__).parent / "prompt.md",
|
||||
Path(getattr(sys, "_MEIPASS", "")) / "prompt.md" if getattr(sys, "_MEIPASS", None) else None,
|
||||
Path.cwd() / "prompt.md",
|
||||
Path(__file__).parent.parent / filename,
|
||||
Path(__file__).parent / filename,
|
||||
Path(getattr(sys, "_MEIPASS", "")) / filename if getattr(sys, "_MEIPASS", None) else None,
|
||||
Path.cwd() / filename,
|
||||
]
|
||||
for p in candidates:
|
||||
if not p:
|
||||
for candidate in candidates:
|
||||
if not candidate:
|
||||
continue
|
||||
try:
|
||||
if p.exists():
|
||||
content = p.read_text(encoding="utf-8")
|
||||
if candidate.exists():
|
||||
content = candidate.read_text(encoding="utf-8")
|
||||
if isinstance(content, str) and content.strip():
|
||||
return content
|
||||
except Exception:
|
||||
continue
|
||||
raise FileNotFoundError(
|
||||
"Failed to read prompt.md; expected adjacent to package or CWD."
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def read_base_instructions() -> str:
|
||||
content = _read_prompt_text("prompt.md")
|
||||
if content is None:
|
||||
raise FileNotFoundError("Failed to read prompt.md; expected adjacent to package or CWD.")
|
||||
return content
|
||||
|
||||
|
||||
def read_gpt5_codex_instructions(fallback: str) -> str:
|
||||
content = _read_prompt_text("prompt_gpt5_codex.md")
|
||||
return content if isinstance(content, str) and content.strip() else fallback
|
||||
|
||||
|
||||
BASE_INSTRUCTIONS = read_base_instructions()
|
||||
GPT5_CODEX_INSTRUCTIONS = read_gpt5_codex_instructions(BASE_INSTRUCTIONS)
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Any, Dict, List
|
||||
|
||||
from flask import Blueprint, Response, current_app, jsonify, make_response, request, stream_with_context
|
||||
|
||||
from .config import BASE_INSTRUCTIONS
|
||||
from .config import BASE_INSTRUCTIONS, GPT5_CODEX_INSTRUCTIONS
|
||||
from .http import build_cors_headers
|
||||
from .reasoning import build_reasoning_param, extract_reasoning_from_model_name
|
||||
from .transform import convert_ollama_messages, normalize_ollama_tools
|
||||
@@ -18,6 +18,15 @@ from .utils import convert_chat_messages_to_responses_input, convert_tools_chat_
|
||||
ollama_bp = Blueprint("ollama", __name__)
|
||||
|
||||
|
||||
def _instructions_for_model(model: str) -> str:
|
||||
base = current_app.config.get("BASE_INSTRUCTIONS", BASE_INSTRUCTIONS)
|
||||
if model == "gpt-5-codex":
|
||||
codex = current_app.config.get("GPT5_CODEX_INSTRUCTIONS") or GPT5_CODEX_INSTRUCTIONS
|
||||
if isinstance(codex, str) and codex.strip():
|
||||
return codex
|
||||
return base
|
||||
|
||||
|
||||
_OLLAMA_FAKE_EVAL = {
|
||||
"total_duration": 8497226791,
|
||||
"load_duration": 1747193958,
|
||||
@@ -33,19 +42,19 @@ def ollama_tags() -> Response:
|
||||
if bool(current_app.config.get("VERBOSE")):
|
||||
print("IN GET /api/tags")
|
||||
expose_variants = bool(current_app.config.get("EXPOSE_REASONING_MODELS"))
|
||||
model_ids = [
|
||||
"gpt-5",
|
||||
*(
|
||||
model_ids = ["gpt-5", "gpt-5-codex"]
|
||||
if expose_variants:
|
||||
model_ids.extend(
|
||||
[
|
||||
"gpt-5-high",
|
||||
"gpt-5-medium",
|
||||
"gpt-5-low",
|
||||
"gpt-5-minimal",
|
||||
"gpt-5-codex-high",
|
||||
"gpt-5-codex-medium",
|
||||
"gpt-5-codex-low",
|
||||
]
|
||||
if expose_variants
|
||||
else []
|
||||
),
|
||||
]
|
||||
)
|
||||
models = []
|
||||
for model_id in model_ids:
|
||||
models.append(
|
||||
@@ -184,10 +193,11 @@ def ollama_chat() -> Response:
|
||||
input_items = convert_chat_messages_to_responses_input(messages)
|
||||
|
||||
model_reasoning = extract_reasoning_from_model_name(model)
|
||||
normalized_model = normalize_model_name(model)
|
||||
upstream, error_resp = start_upstream_request(
|
||||
normalize_model_name(model),
|
||||
normalized_model,
|
||||
input_items,
|
||||
instructions=BASE_INSTRUCTIONS,
|
||||
instructions=_instructions_for_model(normalized_model),
|
||||
tools=tools_responses,
|
||||
tool_choice=tool_choice,
|
||||
parallel_tool_calls=parallel_tool_calls,
|
||||
@@ -231,7 +241,7 @@ def ollama_chat() -> Response:
|
||||
)
|
||||
|
||||
created_at = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
model_out = model if isinstance(model, str) and model.strip() else normalize_model_name(model)
|
||||
model_out = model if isinstance(model, str) and model.strip() else normalized_model
|
||||
|
||||
if stream_req:
|
||||
def _gen():
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any, Dict, List
|
||||
|
||||
from flask import Blueprint, Response, current_app, jsonify, make_response, request
|
||||
|
||||
from .config import BASE_INSTRUCTIONS
|
||||
from .config import BASE_INSTRUCTIONS, GPT5_CODEX_INSTRUCTIONS
|
||||
from .http import build_cors_headers
|
||||
from .reasoning import apply_reasoning_to_message, build_reasoning_param, extract_reasoning_from_model_name
|
||||
from .upstream import normalize_model_name, start_upstream_request
|
||||
@@ -21,6 +21,15 @@ from .utils import (
|
||||
openai_bp = Blueprint("openai", __name__)
|
||||
|
||||
|
||||
def _instructions_for_model(model: str) -> str:
|
||||
base = current_app.config.get("BASE_INSTRUCTIONS", BASE_INSTRUCTIONS)
|
||||
if model == "gpt-5-codex":
|
||||
codex = current_app.config.get("GPT5_CODEX_INSTRUCTIONS") or GPT5_CODEX_INSTRUCTIONS
|
||||
if isinstance(codex, str) and codex.strip():
|
||||
return codex
|
||||
return base
|
||||
|
||||
|
||||
@openai_bp.route("/v1/chat/completions", methods=["POST"])
|
||||
def chat_completions() -> Response:
|
||||
verbose = bool(current_app.config.get("VERBOSE"))
|
||||
@@ -125,7 +134,7 @@ def chat_completions() -> Response:
|
||||
upstream, error_resp = start_upstream_request(
|
||||
model,
|
||||
input_items,
|
||||
instructions=BASE_INSTRUCTIONS,
|
||||
instructions=_instructions_for_model(model),
|
||||
tools=tools_responses,
|
||||
tool_choice=tool_choice,
|
||||
parallel_tool_calls=parallel_tool_calls,
|
||||
@@ -327,7 +336,7 @@ def completions() -> Response:
|
||||
upstream, error_resp = start_upstream_request(
|
||||
model,
|
||||
input_items,
|
||||
instructions=BASE_INSTRUCTIONS,
|
||||
instructions=_instructions_for_model(model),
|
||||
reasoning_param=reasoning_param,
|
||||
)
|
||||
if error_resp is not None:
|
||||
@@ -424,18 +433,16 @@ def completions() -> Response:
|
||||
@openai_bp.route("/v1/models", methods=["GET"])
|
||||
def list_models() -> Response:
|
||||
expose_variants = bool(current_app.config.get("EXPOSE_REASONING_MODELS"))
|
||||
data = []
|
||||
if expose_variants:
|
||||
variant_ids = [
|
||||
"gpt-5",
|
||||
"gpt-5-high",
|
||||
"gpt-5-medium",
|
||||
"gpt-5-low",
|
||||
"gpt-5-minimal",
|
||||
]
|
||||
data = [{"id": mid, "object": "model", "owned_by": "owner"} for mid in variant_ids]
|
||||
else:
|
||||
data = [{"id": "gpt-5", "object": "model", "owned_by": "owner"}]
|
||||
model_groups = [
|
||||
("gpt-5", ["high", "medium", "low", "minimal"]),
|
||||
("gpt-5-codex", ["high", "medium", "low"]),
|
||||
]
|
||||
model_ids: List[str] = []
|
||||
for base, efforts in model_groups:
|
||||
model_ids.append(base)
|
||||
if expose_variants:
|
||||
model_ids.extend([f"{base}-{effort}" for effort in efforts])
|
||||
data = [{"id": mid, "object": "model", "owned_by": "owner"} for mid in model_ids]
|
||||
models = {"object": "list", "data": data}
|
||||
resp = make_response(jsonify(models), 200)
|
||||
for k, v in build_cors_headers().items():
|
||||
|
||||
@@ -31,6 +31,9 @@ def normalize_model_name(name: str | None, debug_model: str | None = None) -> st
|
||||
"gpt5": "gpt-5",
|
||||
"gpt-5-latest": "gpt-5",
|
||||
"gpt-5": "gpt-5",
|
||||
"gpt5-codex": "gpt-5-codex",
|
||||
"gpt-5-codex": "gpt-5-codex",
|
||||
"gpt-5-codex-latest": "gpt-5-codex",
|
||||
"codex": "codex-mini-latest",
|
||||
"codex-mini": "codex-mini-latest",
|
||||
"codex-mini-latest": "codex-mini-latest",
|
||||
|
||||
Reference in New Issue
Block a user