From 4d5dcd7d9fbed8414b0d5f020f2c9e815a842f82 Mon Sep 17 00:00:00 2001 From: rezaho Date: Wed, 9 Sep 2026 09:20:56 +0200 Subject: [PATCH] fix(models): forward caller prompt cache keys on OpenAI adapters --- src/marsys/models/adapters/openai.py | 3 ++ src/marsys/models/adapters/openai_oauth.py | 5 ++- tests/models/test_prompt_cache_key.py | 44 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/models/test_prompt_cache_key.py diff --git a/src/marsys/models/adapters/openai.py b/src/marsys/models/adapters/openai.py index 6c57a297..4c17b535 100644 --- a/src/marsys/models/adapters/openai.py +++ b/src/marsys/models/adapters/openai.py @@ -329,6 +329,9 @@ def convert_content_types(content): "effort": self._served_effort(reasoning_effort.lower(), model_lower) } + if kwargs.get("prompt_cache_key") is not None: + payload["prompt_cache_key"] = kwargs["prompt_cache_key"] + # Only accept known OpenAI Responses API parameters - warn about unknown ones # Based on: https://platform.openai.com/docs/api-reference/responses/create valid_openai_params = { diff --git a/src/marsys/models/adapters/openai_oauth.py b/src/marsys/models/adapters/openai_oauth.py index eefd94b4..d770f5ed 100644 --- a/src/marsys/models/adapters/openai_oauth.py +++ b/src/marsys/models/adapters/openai_oauth.py @@ -3,7 +3,6 @@ import logging import os import time -import uuid from pathlib import Path from typing import Any, Dict, List, Optional, Tuple @@ -361,9 +360,11 @@ def format_request_payload( "store": False, "stream": True, # REQUIRED for ChatGPT backend "include": ["reasoning.encrypted_content"], - "prompt_cache_key": str(uuid.uuid4()), } + if kwargs.get("prompt_cache_key") is not None: + payload["prompt_cache_key"] = kwargs["prompt_cache_key"] + # Handle structured output — text.format (Responses API). Strict mode here # demands BOTH additionalProperties:false AND required==every property; a # Pydantic schema satisfies neither out of the box, so compose both schema diff --git a/tests/models/test_prompt_cache_key.py b/tests/models/test_prompt_cache_key.py new file mode 100644 index 00000000..cef8c59b --- /dev/null +++ b/tests/models/test_prompt_cache_key.py @@ -0,0 +1,44 @@ +"""Caller-selected cache routing survives each OpenAI-family payload builder.""" + +import pytest + +from marsys.models.adapters.azure import AsyncAzureOpenAIAdapter, AzureOpenAIAdapter +from marsys.models.adapters.openai import AsyncOpenAIAdapter, OpenAIAdapter +from marsys.models.adapters.openai_oauth import AsyncOpenAIOAuthAdapter, OpenAIOAuthAdapter + + +@pytest.fixture(params=[ + OpenAIAdapter, AsyncOpenAIAdapter, AzureOpenAIAdapter, AsyncAzureOpenAIAdapter, + OpenAIOAuthAdapter, AsyncOpenAIOAuthAdapter, +]) +def adapter(request): + adapter_type = request.param + if issubclass(adapter_type, OpenAIOAuthAdapter): + # Payload construction needs no credential discovery or network client. + result = object.__new__(adapter_type) + result.model_name = "gpt-5" + return result + return adapter_type( + model_name="gpt-5", api_key="not-a-real-key", + base_url="https://example.invalid/openai/v1", max_tokens=1024, + ) + + +def test_caller_key_reaches_payload_and_stays_stable(adapter): + messages = [{"role": "user", "content": "Continue the work."}] + key = "installation:instance" + first = adapter.format_request_payload(messages, prompt_cache_key=key) + second = adapter.format_request_payload(messages, prompt_cache_key=key) + assert first["prompt_cache_key"] == second["prompt_cache_key"] == key + other = adapter.format_request_payload(messages, prompt_cache_key="installation:other") + assert other["prompt_cache_key"] == "installation:other" + + +@pytest.mark.parametrize("kwargs", [{}, {"prompt_cache_key": None}]) +def test_absent_key_is_omitted_without_minting_one(adapter, kwargs): + messages = [{"role": "user", "content": "Continue the work."}] + first = adapter.format_request_payload(messages, **kwargs) + second = adapter.format_request_payload(messages, **kwargs) + assert "prompt_cache_key" not in first + assert "prompt_cache_key" not in second + assert first == second