diff --git a/.github/workflows/e2e_tests_providers.yaml b/.github/workflows/e2e_tests_providers.yaml index 284ccb87f..eeb754bbc 100644 --- a/.github/workflows/e2e_tests_providers.yaml +++ b/.github/workflows/e2e_tests_providers.yaml @@ -23,13 +23,13 @@ jobs: # Expected default LLM for Behave (matches tests/e2e/configs/run-.yaml). # | environment | model_id | provider_id | # |-------------|--------------------------------------------| --------------| - # | azure | gpt-4o-mini | azure | + # | azure | gpt-5.6-luna | azure | # | vertexai | publishers/google/models/gemini-2.5-flash | google-vertex | # | watsonx | meta-llama/llama-3-3-70b-instruct | watsonx | # | bedrock | deepseek.v3-v1:0 | aws-bedrock | include: - environment: azure - e2e_default_model: gpt-4o-mini + e2e_default_model: gpt-5.6-luna e2e_default_provider: azure - environment: vertexai e2e_default_model: publishers/google/models/gemini-2.5-flash diff --git a/Makefile b/Makefile index ed8c1ebf9..324582192 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ start-ogx-container: build-ogx-image ## Start OGX container -e OPENAI_API_KEY \ -e BRAVE_SEARCH_API_KEY \ -e TAVILY_SEARCH_API_KEY \ - -e E2E_OPENAI_MODEL=$${E2E_OPENAI_MODEL:-gpt-4o-mini} \ + -e E2E_OPENAI_MODEL=$${E2E_OPENAI_MODEL:-gpt-5.6-luna} \ -e TENANT_ID=$${TENANT_ID:-} \ -e CLIENT_ID=$${CLIENT_ID:-} \ -e CLIENT_SECRET \ diff --git a/README.md b/README.md index c66072ce3..f57a07c53 100644 --- a/README.md +++ b/README.md @@ -241,11 +241,11 @@ __Note__: Support for individual models is dependent on the specific inference p | Provider | Model | Tool Calling | provider_type | Example | |----------------|------------------------------------------------------------------------------|---------------|------------------|----------------------------------------------------------------------------| -| OpenAI | gpt-5, gpt-4o, gpt-4-turbo, gpt-4.1, o1, o3, o4 | Yes | remote::openai | [1](examples/openai-faiss-run.yaml) [2](examples/openai-pgvector-run.yaml) | +| OpenAI | gpt-5, gpt-5.6-luna, gpt-4-turbo, gpt-4.1, o1, o3, o4 | Yes | remote::openai | [1](examples/openai-faiss-run.yaml) [2](examples/openai-pgvector-run.yaml) | | OpenAI | gpt-3.5-turbo, gpt-4 | No | remote::openai | | | RHOAI (vLLM) | meta-llama/Llama-3.2-1B-Instruct | Yes | remote::vllm | [1](tests/e2e-prow/rhoai/configs/run.yaml) | | RHEL AI (RHAIIS/vLLM) | meta-llama/Llama-3.1-8B-Instruct | Yes | remote::vllm | [1](tests/e2e/configs/run-rhelai.yaml) [2](tests/e2e/configs/run-rhaiis.yaml) | -| Azure | gpt-5, gpt-5-mini, gpt-5-nano, gpt-4o-mini, o3-mini, o4-mini, o1 | Yes | remote::azure | [1](examples/azure-run.yaml) | +| Azure | gpt-5, gpt-5-mini, gpt-5-nano, gpt-5.6-luna, o3-mini, o4-mini, o1 | Yes | remote::azure | [1](examples/azure-run.yaml) | | Azure | gpt-5-chat, gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, o1-mini | No or limited | remote::azure | | | VertexAI | google/gemini-2.0-flash, google/gemini-2.5-flash, google/gemini-2.5-pro [^1] | Yes | remote::vertexai | [1](examples/vertexai-run.yaml) | | WatsonX | meta-llama/llama-3-3-70b-instruct | Yes | remote::watsonx | [1](examples/watsonx-run.yaml) | @@ -1254,12 +1254,12 @@ will be returned. "model_type": "embedding" }, { - "identifier": "openai/gpt-4o-mini", + "identifier": "openai/gpt-5.6-luna", "metadata": {}, "api_model_type": "llm", "provider_id": "openai", "type": "model", - "provider_resource_id": "gpt-4o-mini", + "provider_resource_id": "gpt-5.6-luna", "model_type": "llm" }, { diff --git a/docker-compose-library.yaml b/docker-compose-library.yaml index 07e13e24e..b5b2a2d22 100755 --- a/docker-compose-library.yaml +++ b/docker-compose-library.yaml @@ -36,7 +36,7 @@ services: - TAVILY_SEARCH_API_KEY=${TAVILY_SEARCH_API_KEY:-} # OpenAI - OPENAI_API_KEY=${OPENAI_API_KEY} - - E2E_OPENAI_MODEL=${E2E_OPENAI_MODEL:-gpt-4o-mini} + - E2E_OPENAI_MODEL=${E2E_OPENAI_MODEL:-gpt-5.6-luna} # Azure Entra ID credentials (AZURE_API_KEY is obtained dynamically in Python) - TENANT_ID=${TENANT_ID:-} - CLIENT_ID=${CLIENT_ID:-} diff --git a/docker-compose.yaml b/docker-compose.yaml index 7ae0e60b3..707d6af4d 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -34,7 +34,7 @@ services: - TAVILY_SEARCH_API_KEY=${TAVILY_SEARCH_API_KEY:-} # OpenAI - OPENAI_API_KEY=${OPENAI_API_KEY} - - E2E_OPENAI_MODEL=${E2E_OPENAI_MODEL:-gpt-4o-mini} + - E2E_OPENAI_MODEL=${E2E_OPENAI_MODEL:-gpt-5.6-luna} # Azure Entra ID credentials (AZURE_API_KEY is passed via provider_data at request time) - TENANT_ID=${TENANT_ID:-} - CLIENT_ID=${CLIENT_ID:-} diff --git a/docs/basic_info/getting_started.md b/docs/basic_info/getting_started.md index 4377501c7..d49f00712 100644 --- a/docs/basic_info/getting_started.md +++ b/docs/basic_info/getting_started.md @@ -184,10 +184,10 @@ authentication: DEBUG Set provider/model configuration for openai/openai/gpt-3.5-turbo-instruct to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4 to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4-turbo to 0 utils.py:45 - DEBUG Set provider/model configuration for openai/openai/gpt-4o to 0 utils.py:45 + DEBUG Set provider/model configuration for openai/openai/gpt-5.6-luna to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4o-2024-08-06 to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4o-audio-preview to 0 utils.py:45 - DEBUG Set provider/model configuration for openai/openai/gpt-4o-mini to 0 utils.py:45 + DEBUG Set provider/model configuration for openai/openai/gpt-5.6-luna to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/o1 to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/o1-mini to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/o3-mini to 0 utils.py:45 diff --git a/docs/design/conversation-compaction/conversation-compaction-spike.md b/docs/design/conversation-compaction/conversation-compaction-spike.md index 306ca165b..7a97e91d5 100644 --- a/docs/design/conversation-compaction/conversation-compaction-spike.md +++ b/docs/design/conversation-compaction/conversation-compaction-spike.md @@ -501,7 +501,7 @@ To verify: check that the docs site renders correctly and OpenAPI spec validates # PoC results -A proof-of-concept was built in lightspeed-stack and tested against a real OGX + OpenAI (gpt-4o-mini) setup. +A proof-of-concept was built in lightspeed-stack and tested against a real OGX + OpenAI (gpt-5.6-luna) setup. ## What the PoC does diff --git a/docs/design/conversation-compaction/conversation-compaction.md b/docs/design/conversation-compaction/conversation-compaction.md index 6c70380be..6399e359d 100644 --- a/docs/design/conversation-compaction/conversation-compaction.md +++ b/docs/design/conversation-compaction/conversation-compaction.md @@ -136,10 +136,10 @@ Context window sizes are configured per model in YAML: ``` yaml inference: - default_model: openai/gpt-4o-mini + default_model: openai/gpt-5.6-luna context_windows: - openai/gpt-4o-mini: 128000 - openai/gpt-4o: 128000 + openai/gpt-5.6-luna: 128000 + openai/gpt-5.6-luna: 128000 ``` Token estimation runs on every request. Cost: ~1-5ms for a 10K token conversation, negligible compared to the LLM call (seconds). @@ -383,7 +383,7 @@ Example config files go in `examples/`. | Trigger turn | 1 LLM call (or 413) | 2 LLM calls (summarize + respond) | | Post-trigger turn | 1 LLM call | 1 LLM call (no change) | -Compaction adds latency only on the trigger turn. In PoC testing, compaction turns took 14-40 seconds vs 9-20 seconds for normal turns (gpt-4o-mini). +Compaction adds latency only on the trigger turn. In PoC testing, compaction turns took 14-40 seconds vs 9-20 seconds for normal turns (gpt-5.6-luna). # Open Questions for Future Work diff --git a/docs/design/ogx-config-merge/ogx-config-merge-spike.md b/docs/design/ogx-config-merge/ogx-config-merge-spike.md index cea25facb..d495540c7 100644 --- a/docs/design/ogx-config-merge/ogx-config-merge-spike.md +++ b/docs/design/ogx-config-merge/ogx-config-merge-spike.md @@ -200,7 +200,7 @@ also stay under `ogx.config` whenever they ship as high-level keys Literal values (`openai`, `azure`, `sentence_transformers`, `vertexai`, `watsonx`, `vllm_rhaiis`, `vllm_rhel_ai`). They are vendor identifiers that both OGX (`provider_type: remote::openai`) and Pydantic AI -(model-string prefixes such as `openai:gpt-4o-mini`) recognise. Each +(model-string prefixes such as `openai:gpt-5.6-luna`) recognise. Each backend-specific synthesizer translates the canonical LCORE vocabulary to its target shape; we do not adopt either backend's surface verbatim. @@ -941,7 +941,7 @@ Summary of validation: from production design"). - **Vacuous safety-shield validation in the library-mode PoC**. The `native_override` used during PoC validation registered `llama-guard` - with `provider_shield_id: openai/gpt-4o-mini` — an OpenAI chat model, + with `provider_shield_id: openai/gpt-5.6-luna` — an OpenAI chat model, not a Llama Guard checkpoint. The "`native_override` took effect" evidence row above only shows that the key landed in the synthesized output; it does **not** show that a real safety shield gated any @@ -1184,7 +1184,7 @@ curl -sSL -o /tmp/lcore-836-poc/lightspeed-stack-unified-library.yaml \ # 1. Start LCORE in library mode with the unified config export OPENAI_API_KEY= -export E2E_OPENAI_MODEL=gpt-4o-mini +export E2E_OPENAI_MODEL=gpt-5.6-luna uv run lightspeed-stack \ -c /tmp/lcore-836-poc/lightspeed-stack-unified-library.yaml diff --git a/docs/design/ogx-config-merge/ogx-config-merge.md b/docs/design/ogx-config-merge/ogx-config-merge.md index b0e7b2d8c..832733613 100644 --- a/docs/design/ogx-config-merge/ogx-config-merge.md +++ b/docs/design/ogx-config-merge/ogx-config-merge.md @@ -256,12 +256,12 @@ Top-level high-level sections plus a sub-section under the existing # with a `providers:` list (Decision S5). Backend-agnostic: it stays at the # root, not under `ogx`, so it survives a future backend change. inference: - default_model: gpt-4o-mini # existing — query-time default routing + default_model: gpt-5.6-luna # existing — query-time default routing default_provider: openai # existing — query-time default routing providers: # NEW — high-level provider setup (synthesis input) - type: openai # mapped to remote::openai api_key_env: OPENAI_API_KEY - allowed_models: [gpt-4o-mini] + allowed_models: [gpt-5.6-luna] - type: sentence_transformers ogx: @@ -588,7 +588,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] # ... more ... ``` @@ -629,7 +629,7 @@ ogx: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] # ... rest of run.yaml content under native_override ... # ... rest of lightspeed-stack.yaml content ... ``` @@ -652,7 +652,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.OPENAI_MODEL:=gpt-5.6-luna}"] - provider_id: sentence-transformers provider_type: inline::sentence-transformers # ... the rest is the same shape as a working run.yaml ... diff --git a/docs/devel_doc/container_orchestration.md b/docs/devel_doc/container_orchestration.md index 30f2a09c6..30bb77be0 100644 --- a/docs/devel_doc/container_orchestration.md +++ b/docs/devel_doc/container_orchestration.md @@ -314,7 +314,7 @@ See [OKP Guide](okp_guide.md) for detailed setup instructions. #### Other Configuration -- `E2E_OPENAI_MODEL`: OpenAI model for E2E tests (default: `gpt-4o-mini`) +- `E2E_OPENAI_MODEL`: OpenAI model for E2E tests (default: `gpt-5.6-luna`) - `OGX_LOGGING`: Enable debug logging in OGX - `FAISS_VECTOR_STORE_ID`: FAISS vector store identifier - `LITELLM_DROP_PARAMS`: Drop unsupported params in LiteLLM (default: `true`) diff --git a/docs/devel_doc/openapi.json b/docs/devel_doc/openapi.json index ef035a8b9..809811c90 100644 --- a/docs/devel_doc/openapi.json +++ b/docs/devel_doc/openapi.json @@ -14153,6 +14153,12 @@ "type": "null" } ] + }, + "responses_request_max_size": { + "type": "integer", + "title": "Maximum character length for the serialized responses request body", + "description": "Maximum character length for the serialized /v1/response responses request body", + "default": 131072 } }, "additionalProperties": false, diff --git a/docs/devel_doc/openapi.md b/docs/devel_doc/openapi.md index ff872ff8c..0e14d5963 100644 --- a/docs/devel_doc/openapi.md +++ b/docs/devel_doc/openapi.md @@ -6591,7 +6591,7 @@ Inference configuration. |-------|------|-------------| | default_model | | Identification of default model used when no other model is specified. | | default_provider | | Identification of default provider used when no other model is specified. | -| context_windows | object | Map of fully-qualified model identifier (e.g., "openai/gpt-4o-mini") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger. | +| context_windows | object | Map of fully-qualified model identifier (e.g., "openai/gpt-5.6-luna") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger. | ## InfoResponse diff --git a/docs/devel_doc/responses.md b/docs/devel_doc/responses.md index 4320a4b55..afed57998 100644 --- a/docs/devel_doc/responses.md +++ b/docs/devel_doc/responses.md @@ -595,12 +595,46 @@ When server-tool merging is active, the response stream and output include items In streaming mode, server-deployed MCP events (e.g. `mcp_call`, `mcp_list_tools`) are filtered from the SSE stream so clients only see standard output types (`message`, `function_call`, etc.) for their own tools. +### Shield Moderation After Last Refusal + +By default, shields moderate the **entire** extracted `input` (plus attachments). Clients that own their own context (`store: false`, no conversation id) resend full history on every call. Once a blocked turn is in that history, every later request is refused — even when the new user turn is clean. + +Set the `X-LCS-Shield-After-Last-Refusal: true` request header to window moderation: + +```bash +curl -X POST http://localhost:8090/v1/responses \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -H "X-LCS-Shield-After-Last-Refusal: true" \ + -d '{ + "input": [ + {"type": "message", "role": "user", "content": ""}, + {"type": "message", "role": "assistant", "id": "modr-abc", "content": "I cannot help with that."}, + {"type": "message", "role": "user", "content": "What is Kubernetes?"} + ], + "store": false + }' +``` + +When this header is set to `true`: + +1. LCORE finds the **last** input item whose `id` starts with `modr` (a prior shield refusal). +2. Shields run only on text and attachments extracted from items **after** that refusal. +3. If no `modr-` item is present, behavior matches today (moderate the full input). + +When the header is absent or `false`, full-input moderation applies (no breaking change for conversation / `store: true` clients). + +**Client guidance:** Keep refusal items (with their `modr-…` ids) in resent history so the window can advance. Shield refusals use response and output-item ids of the form `modr-…` (not `resp_…`). Deployments that require blocked content never enter the model should omit this header. + +See also the [Safety Shields Guide](../user_doc/shields_guide.md). + ### LCORE-Specific Extensions The API introduces extensions that are not part of the OpenResponses specification: - `generate_topic_summary` (request) — When set to `true` and a new conversation is created, a topic summary is automatically generated and stored in conversation metadata. - `shield_ids` (request) — Optional list of LCORE shield `name` values to apply. If omitted, all shields from `lightspeed-stack.yaml` are used. See the [Safety Shields Guide](../user_doc/shields_guide.md). +- `X-LCS-Shield-After-Last-Refusal` (request header) — When `true`, shields moderate only input items after the last prior refusal (`modr-…`). See [Shield Moderation After Last Refusal](#shield-moderation-after-last-refusal). - `solr` (request) — Object with optional `mode` (`semantic`, `hybrid`, or `lexical`) and `filters` (Solr vector_io provider payload). Legacy filter-only objects (no `mode`/`filters` wrapper) still work. - `available_quotas` (response) — Provides real-time quota information from all configured quota limiters. diff --git a/docs/models/common.json b/docs/models/common.json index df1bbcd4e..fec4c9f5e 100644 --- a/docs/models/common.json +++ b/docs/models/common.json @@ -389,7 +389,7 @@ "model": { "description": "Model identifier used for this turn", "examples": [ - "gpt-4o-mini" + "gpt-5.6-luna" ], "title": "Model", "type": "string" diff --git a/docs/models/conversation_summary.json b/docs/models/conversation_summary.json index 79ddaf433..bf22405eb 100644 --- a/docs/models/conversation_summary.json +++ b/docs/models/conversation_summary.json @@ -7,7 +7,7 @@ "components": { "schemas": { "ConversationSummary": { - "description": "A single compaction-produced summary chunk.\n\nAttributes:\n summary_text: The natural-language summary produced by the\n summarization LLM call. Used directly as context for\n subsequent requests (alongside any later summary chunks\n and the buffer of recent turns kept verbatim).\n summarized_through_turn: Running total of conversation items\n consumed by this and all preceding summaries. Used by the\n caller to advance the partition boundary on the next\n compaction so the new summary only covers items that\n have not yet been summarized.\n token_count: Number of tokens in ``summary_text``. Tracked so\n the recursive-resummarize fallback can decide when the\n cumulative summary size itself approaches the context\n limit without re-tokenizing.\n created_at: ISO 8601 timestamp recording when this summary was\n produced. Kept as a string (not datetime) to match the\n cache schema convention used elsewhere in the codebase.\n model_used: Fully-qualified model identifier used for the\n summarization LLM call (e.g., ``\"openai/gpt-4o-mini\"``).\n Preserved for audit and for diagnostics when summary\n quality varies between models.", + "description": "A single compaction-produced summary chunk.\n\nAttributes:\n summary_text: The natural-language summary produced by the\n summarization LLM call. Used directly as context for\n subsequent requests (alongside any later summary chunks\n and the buffer of recent turns kept verbatim).\n summarized_through_turn: Running total of conversation items\n consumed by this and all preceding summaries. Used by the\n caller to advance the partition boundary on the next\n compaction so the new summary only covers items that\n have not yet been summarized.\n token_count: Number of tokens in ``summary_text``. Tracked so\n the recursive-resummarize fallback can decide when the\n cumulative summary size itself approaches the context\n limit without re-tokenizing.\n created_at: ISO 8601 timestamp recording when this summary was\n produced. Kept as a string (not datetime) to match the\n cache schema convention used elsewhere in the codebase.\n model_used: Fully-qualified model identifier used for the\n summarization LLM call (e.g., ``\"openai/gpt-5.6-luna\"``).\n Preserved for audit and for diagnostics when summary\n quality varies between models.", "properties": { "summary_text": { "description": "Natural-language summary produced by the summarization LLM call.", diff --git a/docs/models/conversation_summary.md b/docs/models/conversation_summary.md index e5b093647..e7d43a633 100644 --- a/docs/models/conversation_summary.md +++ b/docs/models/conversation_summary.md @@ -40,7 +40,7 @@ Attributes: produced. Kept as a string (not datetime) to match the cache schema convention used elsewhere in the codebase. model_used: Fully-qualified model identifier used for the - summarization LLM call (e.g., ``"openai/gpt-4o-mini"``). + summarization LLM call (e.g., ``"openai/gpt-5.6-luna"``). Preserved for audit and for diagnostics when summary quality varies between models. diff --git a/docs/models/error_responses.json b/docs/models/error_responses.json index 247cdca3d..80675365c 100644 --- a/docs/models/error_responses.json +++ b/docs/models/error_responses.json @@ -372,7 +372,7 @@ }, { "detail": { - "cause": "Model with ID gpt-4o-mini does not exist", + "cause": "Model with ID gpt-5.6-luna does not exist", "response": "Model not found" }, "label": "model" @@ -443,7 +443,7 @@ "examples": [ { "detail": { - "cause": "The input exceeds the context window size of model 'gpt-4o-mini'.", + "cause": "The input exceeds the context window size of model 'gpt-5.6-luna'.", "response": "Context window exceeded" }, "label": "context window exceeded" diff --git a/docs/models/requests.json b/docs/models/requests.json index 4a56726b3..4eb267999 100644 --- a/docs/models/requests.json +++ b/docs/models/requests.json @@ -2210,7 +2210,7 @@ "generate_topic_summary": true, "input": "Hello World!", "instructions": "You are a helpful assistant", - "model": "openai/gpt-4o-mini", + "model": "openai/gpt-5.6-luna", "store": true, "stream": false } diff --git a/docs/models/successful_responses.json b/docs/models/successful_responses.json index 70c737f2d..53bd69d43 100644 --- a/docs/models/successful_responses.json +++ b/docs/models/successful_responses.json @@ -1257,7 +1257,7 @@ "type": "assistant" } ], - "model": "gpt-4o-mini", + "model": "gpt-5.6-luna", "provider": "openai", "started_at": "2024-01-01T00:01:00Z", "tool_calls": [], @@ -1291,7 +1291,7 @@ "type": "assistant" } ], - "model": "gpt-4o-mini", + "model": "gpt-5.6-luna", "provider": "openai", "started_at": "2024-01-01T00:01:00Z", "tool_calls": [], @@ -1350,7 +1350,7 @@ "model": { "description": "Model identifier used for this turn", "examples": [ - "gpt-4o-mini" + "gpt-5.6-luna" ], "title": "Model", "type": "string" @@ -1570,6 +1570,12 @@ } ], "default": null + }, + "responses_request_max_size": { + "default": 65536, + "description": "Maximum character length for the serialized /v1/response responses request body", + "title": "Maximum character length for the serialized responses request body", + "type": "integer" } }, "title": "Customization", @@ -1946,7 +1952,7 @@ "minimum": 0, "type": "integer" }, - "description": "Map of fully-qualified model identifier (e.g., \"openai/gpt-4o-mini\") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window \u2014 callers fall back to their own default or skip the token-based trigger.", + "description": "Map of fully-qualified model identifier (e.g., \"openai/gpt-5.6-luna\") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window \u2014 callers fall back to their own default or skip the token-based trigger.", "title": "Per-model context window sizes (tokens)", "type": "object" }, @@ -5689,7 +5695,7 @@ "available_quotas", "output_text" ], - "sse_example": "event: response.created\ndata: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"in_progress\",\"model\":\"openai/gpt-4o-mini\",\"output\":[],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{},\"output_text\":\"\"}}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"sequence_number\":1,\"response_id\":\"resp_abc\",\"output_index\":0,\"item\":{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"in_progress\",\"role\":\"assistant\",\"content\":[]}}\n\n...\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"sequence_number\":30,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"completed\",\"model\":\"openai/gpt-4o-mini\",\"output\":[{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello! How can I help?\",\"annotations\":[]}]}],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"usage\":{\"input_tokens\":10,\"output_tokens\":6,\"total_tokens\":16,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens_details\":{\"reasoning_tokens\":0}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{\"daily\":1000,\"monthly\":50000},\"output_text\":\"Hello! How can I help?\"}}\n\ndata: [DONE]\n\n", + "sse_example": "event: response.created\ndata: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"in_progress\",\"model\":\"openai/gpt-5.6-luna\",\"output\":[],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{},\"output_text\":\"\"}}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"sequence_number\":1,\"response_id\":\"resp_abc\",\"output_index\":0,\"item\":{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"in_progress\",\"role\":\"assistant\",\"content\":[]}}\n\n...\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"sequence_number\":30,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"completed\",\"model\":\"openai/gpt-5.6-luna\",\"output\":[{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello! How can I help?\",\"annotations\":[]}]}],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"usage\":{\"input_tokens\":10,\"output_tokens\":6,\"total_tokens\":16,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens_details\":{\"reasoning_tokens\":0}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{\"daily\":1000,\"monthly\":50000},\"output_text\":\"Hello! How can I help?\"}}\n\ndata: [DONE]\n\n", "title": "ResponsesResponse", "type": "object" }, @@ -6308,7 +6314,7 @@ { "config": { "invalid_question_response": "I can only answer questions about the product.", - "model_id": "openai/gpt-4o-mini", + "model_id": "openai/gpt-5.6-luna", "model_prompt": "Is this question valid?" }, "name": "question-validity", diff --git a/docs/models/successful_responses.md b/docs/models/successful_responses.md index cca5a265e..f9bf76341 100644 --- a/docs/models/successful_responses.md +++ b/docs/models/successful_responses.md @@ -604,6 +604,7 @@ Service customization. | agent_card_path | string | | | agent_card_config | object | | | custom_profile | | | +| responses_request_max_size | integer | Maximum character length for the serialized /v1/response responses request body | ## DatabaseConfiguration @@ -778,7 +779,7 @@ Inference configuration. |-------|------|-------------| | default_model | string | Identification of default model used when no other model is specified. | | default_provider | string | Identification of default provider used when no other model is specified. | -| context_windows | object | Map of fully-qualified model identifier (e.g., "openai/gpt-4o-mini") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger. | +| context_windows | object | Map of fully-qualified model identifier (e.g., "openai/gpt-5.6-luna") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger. | | providers | array | Unified-mode synthesis input (Decision S5): a high-level, backend-agnostic list of inference providers the synthesizer expands into OGX provider entries. Lives at the configuration root so it survives a future backend change. A non-empty list signals unified mode. Empty (the default) leaves legacy/remote modes unaffected. The sibling default_model / default_provider keep their query-time routing meaning and are independent of this list. | | max_infer_iters | integer | Server-side default for the maximum number of inference iterations a model can perform in a single request. Prevents small models from looping indefinitely on tool calls. Per-request values take precedence over this default. Set to None to disable the limit. | | max_tool_calls | integer | Server-side default for the maximum number of tool calls allowed in a single response. Prevents small models from exhausting the context window with repeated tool calls. Per-request values take precedence over this default. Set to None to disable the limit. | diff --git a/docs/testing/e2e_testing.md b/docs/testing/e2e_testing.md index f3bee36f4..ac5dec1aa 100644 --- a/docs/testing/e2e_testing.md +++ b/docs/testing/e2e_testing.md @@ -294,7 +294,7 @@ For another YAML (`lightspeed-stack-okp-online.yaml`, tool RAG, and so on), repe | `E2E_OGX_PORT` | `8321` | Port of the OGX service. | | `E2E_OGX_STACK_URL` | — | Full base URL for OGX (overrides host/port if set). Used by shield helpers. | | `E2E_OGX_STACK_API_KEY` | `xyzzy` | API key for OGX client (e.g. shield API). | -| `E2E_DEFAULT_MODEL_OVERRIDE` | — | Override default LLM model id (e.g. `gpt-4o-mini`). | +| `E2E_DEFAULT_MODEL_OVERRIDE` | — | Override default LLM model id (e.g. `gpt-5.6-luna`). | | `E2E_DEFAULT_PROVIDER_OVERRIDE` | — | Override default provider id (e.g. `openai`). | | `FAISS_VECTOR_STORE_ID` | — | Vector store id for FAISS-related scenarios. | | `RUNNING_PROW` | — | Set in Prow/OpenShift; enables Prow config paths and pod/container ops. | diff --git a/docs/testing/integration_vs_e2e.md b/docs/testing/integration_vs_e2e.md new file mode 100644 index 000000000..2b46368d8 --- /dev/null +++ b/docs/testing/integration_vs_e2e.md @@ -0,0 +1,76 @@ +# Integration tests and end-to-end tests + +This repository has three test layers. A test belongs in the lowest layer that can still fail when the behavior breaks. + +| | Unit | Integration | End to end | +|---|---|---|---| +| Where | `tests/unit/` | `tests/integration/` | `tests/e2e/` | +| How | pytest, one function or class | pytest, several real components in-process | behave, a separate process from the service | +| Real | the code under test | config loading, SQLite, auth, pipelines, repo CLIs | a deployed stack (Docker Compose locally, OpenShift in Prow) | +| Fake | every collaborator | external services only (OGX, LLM providers) | nothing inside the service; the test sees public surfaces | +| May import `src/` | yes | yes | no | +| Run | `make test-unit` (60% coverage) | `make test-integration` (10% coverage) | `make test-e2e` (no coverage gate) | +| Cost | seconds | seconds | minutes, needs containers | + +Integration calls Python. It imports a handler or runs `src/lightspeed_stack.py` as a subprocess. The database and YAML config are real. The OGX client is a mock. `TestClient` against the FastAPI app is still integration: same process, no container. + +End to end calls the deployed service. A scenario sends HTTP, restarts a container, or reads `docker logs`. Step definitions under `tests/e2e/features/steps/` must not import, call, or shell out to anything under `src/`. If a scenario needs a generated file, commit the file and guard the generator with an integration test. + +The check: could this scenario run unchanged against a container image, with no source checkout on the machine? If yes, it is end to end. If it needs `src/lightspeed_stack.py`, `src/ogx_configuration.py`, or any import of the service, it is integration. + +| Looks like | It is | +|---|---| +| `TestClient` GET `/openapi.json` inside pytest | integration | +| `subprocess` of `src/lightspeed_stack.py --migrate-config` | integration | +| `requests` against a container the test just started | end to end | +| `docker logs` / `docker restart` | end to end | +| Status code and response schema | either; the subject decides the layer | +| A word the model happened to write | a flaky end-to-end test | + +## Good integration tests + +These keep several real components and stub only the external service. + +| File | What it proves | +|---|---| +| `tests/integration/endpoints/test_responses_integration.py` | `responses_endpoint_handler` plus the real SQLite session. The check that matters is the conversation row (owner, model, provider, turn), not the response object. | +| `tests/integration/test_unified_synthesis.py` | Real YAML through `AppConfig.load_configuration` and `synthesize_to_file`: baseline selection, enrichment, parity with the legacy path. No port, no Docker. | +| `tests/integration/test_unified_mode_cli.py` | `src/lightspeed_stack.py --dump-configuration` and `--migrate-config` as a subprocess: exit code, message, file mode. This is how operators and the container entrypoint invoke the script. | +| `tests/integration/test_otel_trace_propagation.py` | A W3C `traceparent` through `query_endpoint_handler`. Spans from more than one component share a trace id. An in-memory exporter is enough. | + +## Good end-to-end tests + +These fail only when the running stack is wrong, and they look at surfaces a caller outside the repo can see. + +| File | What it proves | +|---|---| +| `tests/e2e/features/health.feature`, `smoketests.feature` | The local service is up. Status, schema, and — for health — an exact JSON body the service produces, not a model. | +| `tests/e2e/features/unified-mode-boot.feature` | A unified-mode YAML, a container restart, then `readiness` and `query` return 200. It does not inspect the synthesized file. | +| `tests/e2e/features/unified-mode-synthesis.feature` | After restart, container logs contain the synthesized path (`docker inspect` / `docker logs` in `tests/e2e/features/steps/unified_mode.py`). Synthesis rules stay in `test_unified_synthesis.py`. | +| `tests/e2e/features/ogx_disrupted.feature`, `degraded_mode_startup.feature` | OGX is actually stopped. HTTP then shows 503, readiness `unhealthy`, or `ls_started_in_degraded_mode`. Mocking `ApiException` in `test_info_integration.py` covers the handler branch; it does not cover restart order. | +| `tests/e2e/features/unified-mode-migration.feature` | The stack boots from the committed `lightspeed-stack-unified-migrated.yaml`. The scenario does not run `--migrate-config`. `test_unified_mode_cli.py` guards that fixture against drift. | + +## Poor fits + +These tests check something true. They sit in the wrong layer, or they assert something the layer cannot keep stable. + +| File | What is wrong | +|---|---| +| `tests/integration/endpoints/test_authorized_endpoint.py` | Loads `test_config`, discards it, calls one handler, compares the result to noop-auth constants. No second component. That is a unit test. | +| `test_health_liveness` in `tests/integration/endpoints/test_health_integration.py` | Same shape: unused config fixture, assertion is `response.alive is True`. Liveness does not consult OGX. The other tests in that file, which map stubbed provider health into the readiness payload, are real integration tests. | +| `tests/integration/test_version.py` | Compares `version.__version__` with `pdm show --version`. No config, database, or handler. The repo runs tests with `uv`, not `pdm`. | +| `tests/integration/container_lifecycle/test_container_lifecycle.py` | Builds an image, polls `http://localhost:8321/v1/health`, and `exec`s into the container. That is a deployed stack, collected by `make test-integration`, and one function does build, start, probe, mounts, and cleanup. The behave suite never runs it. | +| `tests/unit/app/endpoints/test_info.py` and `tests/integration/endpoints/test_info_integration.py` | Both call `info_endpoint_handler` with a mocked OGX version. The integration test earns its place: it reads `name` (`"foo bar baz"`) from `tests/configuration/lightspeed-stack.yaml` and uses the real noop auth fixture. The unit happy path only asserts `response.name is not None`. | +| `@flaky` scenarios in `tests/e2e/features/query.feature` (also `streaming_query.feature`, `mcp.feature`, parts of `responses.feature`) | The reply must contain `"ask"`, `"checkout"`, or `"image"`. The contract is status 200 and token counters. The extra word depends on the model. | + +`tests/e2e/features/conversation-compaction.feature` also matches response fragments, and that use holds. The scenario plants names (`north-quarry`, `blue-lagoon`) and later asks for them. The fragment is a value the test wrote. Stable end-to-end assertions are status, schema, `context_status`, metrics, and planted facts. + +## Where a new test goes + +1. One function's branch: unit test, collaborators mocked. +2. A wrong database row, merged config, span parent, or CLI exit code: integration test. Use `test_config`, `test_db_session`, `mock_ogx_client`, and `mock_conversation_store` from `tests/integration/conftest.py`. +3. A container that does not become ready, a missing log line after restart, or a 503 only when the OGX process is gone: behave scenario. Reuse steps. Do not import `src/`. +4. A scenario that needs a generated file: commit the file, and add an integration test that fails when regeneration would change it. +5. Assert the contract. Tag model-wording checks so the default run does not depend on them. + +How to run each layer is in [testing.md](testing.md). Scenario tags, deployment modes, and hooks are in [e2e_testing.md](e2e_testing.md). Integration fixtures are in [tests/integration/README.md](../../tests/integration/README.md). diff --git a/docs/user_doc/config.html b/docs/user_doc/config.html index cb8890e9f..4a2365eaf 100644 --- a/docs/user_doc/config.html +++ b/docs/user_doc/config.html @@ -1137,7 +1137,7 @@

InferenceConfiguration

context_windows object - Map of fully-qualified model identifier (e.g., “openai/gpt-4o-mini”) + Map of fully-qualified model identifier (e.g., “openai/gpt-5.6-luna”) to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered diff --git a/docs/user_doc/config.json b/docs/user_doc/config.json index c873028e9..eecd3653a 100644 --- a/docs/user_doc/config.json +++ b/docs/user_doc/config.json @@ -1018,7 +1018,7 @@ "minimum": 0, "type": "integer" }, - "description": "Map of fully-qualified model identifier (e.g., \"openai/gpt-4o-mini\") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger.", + "description": "Map of fully-qualified model identifier (e.g., \"openai/gpt-5.6-luna\") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger.", "title": "Per-model context window sizes (tokens)", "type": "object" }, diff --git a/docs/user_doc/config.md b/docs/user_doc/config.md index b73aeaa85..e9bf51509 100644 --- a/docs/user_doc/config.md +++ b/docs/user_doc/config.md @@ -398,7 +398,7 @@ Inference configuration. |------------------|---------|-------------- | | default_model | string | Identification of default model used when no other model is specified. | | default_provider | string | Identification of default provider used when no other model is specified. | -| context_windows | object | Map of fully-qualified model identifier (e.g., "openai/gpt-4o-mini") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger. | +| context_windows | object | Map of fully-qualified model identifier (e.g., "openai/gpt-5.6-luna") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window — callers fall back to their own default or skip the token-based trigger. | | providers | array | Unified-mode synthesis input (Decision S5): a high-level, backend-agnostic list of inference providers the synthesizer expands into OGX provider entries. Lives at the configuration root so it survives a future backend change. A non-empty list signals unified mode. Empty (the default) leaves legacy/remote modes unaffected. The sibling default_model / default_provider keep their query-time routing meaning and are independent of this list. | | max_infer_iters | integer | Server-side default for the maximum number of inference iterations a model can perform in a single request. Prevents small models from looping indefinitely on tool calls. Per-request values take precedence over this default. Set to None to disable the limit. | | max_tool_calls | integer | Server-side default for the maximum number of tool calls allowed in a single response. Prevents small models from exhausting the context window with repeated tool calls. Per-request values take precedence over this default. Set to None to disable the limit. | diff --git a/docs/user_doc/conversation_compaction.md b/docs/user_doc/conversation_compaction.md index f29887a60..6611cbdc3 100644 --- a/docs/user_doc/conversation_compaction.md +++ b/docs/user_doc/conversation_compaction.md @@ -76,9 +76,9 @@ Compaction is disabled by default. To enable it, add a `compaction` section to y ```yaml inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna context_windows: - openai/gpt-4o-mini: 128000 + openai/gpt-5.6-luna: 128000 compaction: enabled: true @@ -89,10 +89,10 @@ compaction: ```yaml inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna context_windows: - openai/gpt-4o-mini: 128000 - openai/gpt-4o: 128000 + openai/gpt-5.6-luna: 128000 + openai/gpt-5.6-luna: 128000 compaction: enabled: true @@ -114,7 +114,7 @@ compaction: ### Prerequisites -- **Context windows must be registered.** Add entries to `inference.context_windows` mapping each fully-qualified model identifier (e.g., `"openai/gpt-4o-mini"`) to its context window size in tokens. Models absent from this map have no registered window and compaction will not trigger for them. +- **Context windows must be registered.** Add entries to `inference.context_windows` mapping each fully-qualified model identifier (e.g., `"openai/gpt-5.6-luna"`) to its context window size in tokens. Models absent from this map have no registered window and compaction will not trigger for them. - **A conversation cache is recommended.** While compaction works without a cache (using marker-only mode), enabling a conversation cache (PostgreSQL, SQLite, or in-memory) improves performance by caching summaries across requests and enabling recursive fold-up of accumulated summaries. ## Behavior details diff --git a/docs/user_doc/deployment_guide.md b/docs/user_doc/deployment_guide.md index e6f3db3c0..9c5ed0dec 100644 --- a/docs/user_doc/deployment_guide.md +++ b/docs/user_doc/deployment_guide.md @@ -695,10 +695,10 @@ is consumed as the synthesis [profile](#profiles) via DEBUG Set provider/model configuration for openai/openai/gpt-3.5-turbo-instruct to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4 to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4-turbo to 0 utils.py:45 - DEBUG Set provider/model configuration for openai/openai/gpt-4o to 0 utils.py:45 + DEBUG Set provider/model configuration for openai/openai/gpt-5.6-luna to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4o-2024-08-06 to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/gpt-4o-audio-preview to 0 utils.py:45 - DEBUG Set provider/model configuration for openai/openai/gpt-4o-mini to 0 utils.py:45 + DEBUG Set provider/model configuration for openai/openai/gpt-5.6-luna to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/o1 to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/o1-mini to 0 utils.py:45 DEBUG Set provider/model configuration for openai/openai/o3-mini to 0 utils.py:45 @@ -1309,12 +1309,12 @@ Please note that actual response from the service is larger. It was stripped dow "model_type": "llm" }, { - "identifier": "openai/gpt-4o-mini", + "identifier": "openai/gpt-5.6-luna", "metadata": {}, "api_model_type": "llm", "provider_id": "openai", "type": "model", - "provider_resource_id": "gpt-4o-mini", + "provider_resource_id": "gpt-5.6-luna", "model_type": "llm" }, { diff --git a/docs/user_doc/rag_guide.md b/docs/user_doc/rag_guide.md index 49a8cb964..faee5dcd0 100644 --- a/docs/user_doc/rag_guide.md +++ b/docs/user_doc/rag_guide.md @@ -287,7 +287,7 @@ models: - model_id: my-model provider_id: openai model_type: llm - provider_model_id: # e.g. gpt-4o-mini + provider_model_id: # e.g. gpt-5.6-luna providers: [...] diff --git a/docs/user_doc/shields_guide.md b/docs/user_doc/shields_guide.md index 93f095c9a..71bd84194 100644 --- a/docs/user_doc/shields_guide.md +++ b/docs/user_doc/shields_guide.md @@ -53,7 +53,7 @@ shields: - name: topic-guard provider_id: question_validity config: - model_id: openai/gpt-4o-mini + model_id: openai/gpt-5.6-luna # optional: # model_prompt: "..." # invalid_question_response: "..." @@ -82,7 +82,7 @@ for a complete example. | Config field | Required | Description | |--------------|----------|-------------| -| `model_id` | Yes | Model used for the validity check (for example `openai/gpt-4o-mini`) | +| `model_id` | Yes | Model used for the validity check (for example `openai/gpt-5.6-luna`) | | `model_prompt` | No | Classifier prompt (has a built-in default) | | `invalid_question_response` | No | Reply returned when the question is rejected | @@ -179,6 +179,12 @@ functionality directly** through a custom API (`run_shield_moderation`) before each request. When moderation blocks the input, the endpoint returns a refusal (and may persist the blocked turn) without calling the model. +On `/v1/responses`, clients that resend full history (`store: false`) can opt +into windowed moderation with the +`X-LCS-Shield-After-Last-Refusal: true` header. Shields then judge only input +items after the last prior refusal (`modr-…`). See +[Shield Moderation After Last Refusal](../devel_doc/responses.md#shield-moderation-after-last-refusal). + ## Per-endpoint behavior | Endpoint | How shields run | `shield_ids` | diff --git a/examples/azure-run.yaml b/examples/azure-run.yaml index 17195a8d0..e924eca72 100644 --- a/examples/azure-run.yaml +++ b/examples/azure-run.yaml @@ -97,10 +97,10 @@ storage: backend: sql_default registered_resources: models: - - model_id: gpt-4o-mini + - model_id: gpt-5.6-luna provider_id: azure model_type: llm - provider_model_id: gpt-4o-mini + provider_model_id: gpt-5.6-luna - model_id: all-mpnet-base-v2 model_type: embedding provider_id: sentence-transformers diff --git a/examples/lightspeed-stack-shields.yaml b/examples/lightspeed-stack-shields.yaml index 5aa84f0a9..24ebf827b 100644 --- a/examples/lightspeed-stack-shields.yaml +++ b/examples/lightspeed-stack-shields.yaml @@ -22,7 +22,7 @@ shields: - name: topic-guard provider_id: question_validity config: - model_id: openai/gpt-4o-mini + model_id: openai/gpt-5.6-luna # Optional; omit to use built-in defaults. Must reference ${message} # (string.Template substitution) or the classifier never sees the # question it's meant to classify. diff --git a/examples/profiles/openai-remote.yaml b/examples/profiles/openai-remote.yaml index 268fc599d..ca0a43ed5 100644 --- a/examples/profiles/openai-remote.yaml +++ b/examples/profiles/openai-remote.yaml @@ -34,7 +34,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.OPENAI_MODEL:=gpt-5.6-luna}"] - provider_id: sentence-transformers provider_type: inline::sentence-transformers files: diff --git a/examples/run.yaml b/examples/run.yaml index 5150cd9b4..c2684a1ef 100644 --- a/examples/run.yaml +++ b/examples/run.yaml @@ -24,7 +24,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers @@ -88,10 +88,10 @@ storage: backend: sql_default registered_resources: models: - - model_id: gpt-4o-mini + - model_id: gpt-5.6-luna provider_id: openai model_type: llm - provider_model_id: gpt-4o-mini + provider_model_id: gpt-5.6-luna vector_stores: annotation_prompt_params: # Override the default OGX annotation that adds <| file-xyz |> to responses enable_annotations: true diff --git a/examples/vllm-rhelai.yaml b/examples/vllm-rhelai.yaml index c2b33ead1..465546339 100644 --- a/examples/vllm-rhelai.yaml +++ b/examples/vllm-rhelai.yaml @@ -24,7 +24,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["gpt-4o-mini"] + allowed_models: ["gpt-5.6-luna"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers diff --git a/run.yaml b/run.yaml index 71b043399..05f89d7b0 100644 --- a/run.yaml +++ b/run.yaml @@ -17,7 +17,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - provider_id: sentence-transformers provider_type: inline::sentence-transformers files: diff --git a/src/app/endpoints/responses.py b/src/app/endpoints/responses.py index df12cbfe4..eac32bd58 100644 --- a/src/app/endpoints/responses.py +++ b/src/app/endpoints/responses.py @@ -33,7 +33,11 @@ from authorization.middleware import authorize from client.ogx import AsyncOgxClientHolder from configuration import configuration -from constants import ENDPOINT_PATH_RESPONSES, SUBSTITUTED_INSTRUCTIONS_PLACEHOLDER +from constants import ( + ENDPOINT_PATH_RESPONSES, + RESPONSES_REQUEST_MAX_SIZE, + SUBSTITUTED_INSTRUCTIONS_PLACEHOLDER, +) from log import get_logger from metrics import recording from models.api.requests import ResponsesRequest @@ -100,7 +104,7 @@ build_turn_summary, check_model_configured, deduplicate_referenced_documents, - extract_attachments_text, + extract_shield_moderation_text, extract_text_from_response_items, extract_token_usage, extract_vector_store_ids_from_tools, @@ -631,13 +635,18 @@ async def handle_responses_with_tracing( # pylint: disable=too-many-locals ): client = await AsyncOgxClientHolder().update_azure_token() - attachments_text = extract_attachments_text(original_request.input) add_span_event(root_span, SpanEvents.VALIDATION_COMPLETED) endpoint_path = ENDPOINT_PATH_RESPONSES + shield_after_last_refusal = ( + request.headers.get("X-LCS-Shield-After-Last-Refusal", "").lower() == "true" + ) moderation_result = await run_shield_moderation_v2( - input_text + "\n\n" + attachments_text, + extract_shield_moderation_text( + original_request.input, + shield_after_last_refusal, + ), configuration.configuration.shields, responses_request.shield_ids, ) @@ -660,6 +669,7 @@ async def handle_responses_with_tracing( # pylint: disable=too-many-locals original_request.reasoning, updated_request.tools, updated_request.model, + configuration.skills, ) # Extract vector store IDs for Inline RAG context from the original request @@ -1476,6 +1486,13 @@ async def handle_non_streaming_response( tools, configuration.rag_id_mapping, ) + + # retrieve max size of response request + responses_request_max_size = RESPONSES_REQUEST_MAX_SIZE + customization = configuration.configuration.customization + if customization is not None: + responses_request_max_size = customization.responses_request_max_size + return ResponsesResponse.model_validate( { **response_dict, @@ -1483,5 +1500,6 @@ async def handle_non_streaming_response( "conversation": normalize_conversation_id(api_params.conversation), "completed_at": int(completed_at.timestamp()), "output_text": output_text, - } + }, + context={"responses_request_max_size": responses_request_max_size}, ) diff --git a/src/constants.py b/src/constants.py index 3c6c6355d..4ae9347c8 100644 --- a/src/constants.py +++ b/src/constants.py @@ -348,8 +348,8 @@ # Input size limits for API request validation # Maximum character length for the question field in /v1/infer requests (32 KiB) RLSAPI_V1_QUESTION_MAX_LENGTH: Final[int] = 32_768 -# Maximum character length for the serialized /v1/responses request body (64 KiB) -RESPONSES_REQUEST_MAX_SIZE: Final[int] = 65_536 +# Maximum character length for the serialized /v1/responses request body (128 KiB) +RESPONSES_REQUEST_MAX_SIZE: Final[int] = 131_072 # Sentry configuration constants # Environment variable name for the Sentry DSN (Data Source Name) diff --git a/src/data/default_run.yaml b/src/data/default_run.yaml index 8c87112f4..60d059022 100644 --- a/src/data/default_run.yaml +++ b/src/data/default_run.yaml @@ -39,7 +39,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY:=} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - provider_id: sentence-transformers provider_type: inline::sentence-transformers files: diff --git a/src/models/api/requests/responses_openai.py b/src/models/api/requests/responses_openai.py index 809b6963f..c40d4b798 100644 --- a/src/models/api/requests/responses_openai.py +++ b/src/models/api/requests/responses_openai.py @@ -15,7 +15,7 @@ from ogx_api.openai_responses import ( OpenAIResponseText as Text, ) -from pydantic import BaseModel, field_validator, model_validator +from pydantic import BaseModel, ValidationInfo, field_validator, model_validator from constants import RESPONSES_REQUEST_MAX_SIZE from models.common.query import SolrVectorSearchRequest @@ -105,7 +105,7 @@ class ResponsesRequest(BaseModel): @model_validator(mode="before") @classmethod - def validate_body_size(cls, values: Any) -> Any: + def validate_body_size(cls, values: Any, info: ValidationInfo) -> Any: """Validate that the request body does not exceed the maximum allowed size. Serializes the raw request payload to JSON and checks the total character @@ -130,10 +130,15 @@ def validate_body_size(cls, values: Any) -> Any: # model instances). The size guard only applies to wire-format HTTP # requests which FastAPI always parses into JSON-compatible dicts. return values - if len(serialized) > RESPONSES_REQUEST_MAX_SIZE: + + max_size = (info.context or {}).get( + "responses_request_max_size", RESPONSES_REQUEST_MAX_SIZE + ) + + if len(serialized) > max_size: raise ValueError( f"Request body size ({len(serialized)} characters) exceeds maximum " - f"allowed size of {RESPONSES_REQUEST_MAX_SIZE} characters" + f"allowed size of {max_size} characters" ) return values diff --git a/src/models/common/moderation.py b/src/models/common/moderation.py index 575d6e535..00e906760 100644 --- a/src/models/common/moderation.py +++ b/src/models/common/moderation.py @@ -23,10 +23,11 @@ class ShieldModerationBlocked(BaseModel): @property def refusal_response(self) -> ResponseMessage: - """Build a ResponseMessage carrying the shield's refusal text.""" + """Build a refusal message; ``id`` is the moderation id (``modr-…``).""" return ResponseMessage( role="assistant", content=self.message, + id=self.moderation_id, ) diff --git a/src/models/config.py b/src/models/config.py index cbd78a148..c0ac0de92 100644 --- a/src/models/config.py +++ b/src/models/config.py @@ -1716,6 +1716,12 @@ class Customization(ConfigurationBase): agent_card_path: Optional[FilePath] = None agent_card_config: Optional[dict[str, Any]] = None custom_profile: Optional[CustomProfile] = Field(default=None, init=False) + responses_request_max_size: int = Field( + default=constants.RESPONSES_REQUEST_MAX_SIZE, + title="Maximum character length for the serialized responses request body", + description="Maximum character length for the serialized /v1/response responses " + "request body", + ) @model_validator(mode="after") def check_customization_model(self) -> Self: diff --git a/src/utils/responses.py b/src/utils/responses.py index 852d10af4..8775364fc 100644 --- a/src/utils/responses.py +++ b/src/utils/responses.py @@ -113,7 +113,7 @@ ToolResultSummary, TurnSummary, ) -from models.config import RagStore +from models.config import RagStore, SkillsConfiguration from models.database.conversations import UserConversation from utils.mcp.mcp_headers import ( McpHeaders, @@ -135,7 +135,7 @@ normalize_vertex_ai_model_id, prepare_input, ) -from utils.suid import to_ogx_conversation_id +from utils.suid import is_moderation_id, to_ogx_conversation_id from utils.token_counter import TokenCounter logger = get_logger(__name__) @@ -439,7 +439,12 @@ async def prepare_responses_params( # pylint: disable=too-many-arguments,too-ma normalized_model = normalize_vertex_ai_model_id(model) # Known LLS bug: https://redhat.atlassian.net/browse/LCORE-1583 - reasoning = apply_reasoning_for_resolved_tools(None, tools, normalized_model) + reasoning = apply_reasoning_for_resolved_tools( + None, + tools, + normalized_model, + configuration.skills, + ) return ResponsesApiParams( input=input_text, @@ -1737,6 +1742,64 @@ def extract_attachments_text(response_input: ResponseInput) -> str: return "\n\n".join(file_data_parts) +def get_shield_moderation_input( + response_input: ResponseInput, + after_last_refusal: bool = False, +) -> ResponseInput: + """Return input for shield moderation, optionally after the last refusal. + + Args: + response_input: Responses API ``input`` (string or item list). + after_last_refusal: When True, window after the last ``modr-…`` item. + + Returns: + The full input, or the suffix list after the last moderated refusal. + + """ + if not after_last_refusal or isinstance(response_input, str): + return response_input + + item_count = len(response_input) + for offset, item in enumerate(reversed(response_input)): + if ( + isinstance(item, ResponseMessage) + and item.role == "assistant" + and item.id is not None + and is_moderation_id(item.id) + ): + return list(response_input[item_count - offset :]) + + return response_input + + +def extract_shield_moderation_text( + response_input: ResponseInput, + after_last_refusal: bool = False, +) -> str: + """Extract text and attachments for shield moderation. + + Optionally windows to items after the last ``modr-…`` refusal first. + + Args: + response_input: Responses API ``input`` (string or item list). + after_last_refusal: When True, window after the last ``modr-…`` item. + + Returns: + Combined message text and attachment ``file_data`` for shields. + + """ + shield_input = get_shield_moderation_input( + response_input, + after_last_refusal=after_last_refusal, + ) + text = ( + shield_input + if isinstance(shield_input, str) + else extract_text_from_response_items(shield_input) + ) + return text + "\n\n" + extract_attachments_text(shield_input) + + def _merge_tools( client_tools: list[InputTool], server_tools: list[InputTool], @@ -1864,6 +1927,7 @@ def apply_reasoning_for_resolved_tools( reasoning: Optional[OpenAIResponseReasoning], tools: Optional[list[InputTool]], model_id: str, + skills_configuration: Optional[SkillsConfiguration], ) -> Optional[OpenAIResponseReasoning]: """Set reasoning effort to ``none`` when resolved tools are present. @@ -1875,13 +1939,15 @@ def apply_reasoning_for_resolved_tools( reasoning: Optional reasoning configuration to modify. tools: Optional list of tools that have been resolved. model_id: Resolved model identifier in ``provider/model`` form. - + skills_configuration: Optional skills configuration. Returns: Modified reasoning configuration with effort set to "none" if tools are present on a default-on reasoning model, or the original reasoning otherwise. """ - if not tools or not model_reasoning_enabled_by_default(model_id): + if ( + not tools and not skills_configuration + ) or not model_reasoning_enabled_by_default(model_id): return reasoning base = reasoning or OpenAIResponseReasoning() diff --git a/tests/benchmarks/data/python_1000_lines.py b/tests/benchmarks/data/python_1000_lines.py index 3fd21b4cb..f94a2fd51 100644 --- a/tests/benchmarks/data/python_1000_lines.py +++ b/tests/benchmarks/data/python_1000_lines.py @@ -922,7 +922,7 @@ def print_rag_response(response): # - tisk odpovědi -llm = ChatOpenAI(model_name="gpt-4o-mini") +llm = ChatOpenAI(model_name="gpt-5.6-luna") response = llm.invoke("Say Hi") print(response) @@ -933,7 +933,7 @@ def print_rag_response(response): # - tisk odpovědi v naformátované podobě -llm = ChatOpenAI(model_name="gpt-4o-mini") +llm = ChatOpenAI(model_name="gpt-5.6-luna") response = llm.invoke("Say Hi") print(response) @@ -944,7 +944,7 @@ def print_rag_response(response): # - tisk odpovědi -llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0.7) +llm = ChatOpenAI(model_name="gpt-5.6-luna", temperature=0.7) response = llm.invoke("Say Hi") print(response) @@ -957,7 +957,7 @@ def print_rag_response(response): prompt = PromptTemplate.from_template("How to say 'hi' in {language} language?") -llm = ChatOpenAI(model_name="gpt-4o-mini") +llm = ChatOpenAI(model_name="gpt-5.6-luna") chain = LLMChain(prompt=prompt, llm=llm) response = chain.invoke("Czech") @@ -976,7 +976,7 @@ def print_rag_response(response): ) -llm = ChatOpenAI(model_name="gpt-4o-mini") +llm = ChatOpenAI(model_name="gpt-5.6-luna") chain = LLMChain(prompt=prompt, llm=llm) response = chain.invoke("Czech") @@ -990,7 +990,7 @@ def print_rag_response(response): prompt = PromptTemplate.from_template("How to say 'hi' in {language} language?") -llm = ChatOpenAI(model_name="gpt-4o-mini") +llm = ChatOpenAI(model_name="gpt-5.6-luna") chain = prompt | llm | StrOutputParser() diff --git a/tests/benchmarks/data_generators.py b/tests/benchmarks/data_generators.py index 6923612ad..9a02a793f 100644 --- a/tests/benchmarks/data_generators.py +++ b/tests/benchmarks/data_generators.py @@ -43,7 +43,7 @@ def generate_model_for_provider(provider: str) -> str: "gpt-5.2 pro", "gpt-5 mini", "gpt-4.1", - "gpt-4o", + "gpt-5.6-luna", "gpt-4-turbo", "gpt-4.1 mini", "gpt-4.1 nano", diff --git a/tests/configuration/unified-mode/lightspeed-stack-invalid-config-and-legacy.yaml b/tests/configuration/unified-mode/lightspeed-stack-invalid-config-and-legacy.yaml index a33693698..98b789af7 100644 --- a/tests/configuration/unified-mode/lightspeed-stack-invalid-config-and-legacy.yaml +++ b/tests/configuration/unified-mode/lightspeed-stack-invalid-config-and-legacy.yaml @@ -22,4 +22,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/configuration/unified-mode/lightspeed-stack-invalid-providers-and-legacy.yaml b/tests/configuration/unified-mode/lightspeed-stack-invalid-providers-and-legacy.yaml index 19c299ab9..aac2209b5 100644 --- a/tests/configuration/unified-mode/lightspeed-stack-invalid-providers-and-legacy.yaml +++ b/tests/configuration/unified-mode/lightspeed-stack-invalid-providers-and-legacy.yaml @@ -20,7 +20,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Unified synthesis input (Decision S5): the high-level provider entry # replaces the default baseline's openai provider by id at synthesis time. providers: @@ -28,4 +28,4 @@ inference: id: openai api_key_env: OPENAI_API_KEY allowed_models: - - ${env.E2E_OPENAI_MODEL:=gpt-4o-mini} + - ${env.E2E_OPENAI_MODEL:=gpt-5.6-luna} diff --git a/tests/configuration/unified-mode/lightspeed-stack-invalid-version-legacy-unified-body.yaml b/tests/configuration/unified-mode/lightspeed-stack-invalid-version-legacy-unified-body.yaml index 9ae7b389d..acca3a227 100644 --- a/tests/configuration/unified-mode/lightspeed-stack-invalid-version-legacy-unified-body.yaml +++ b/tests/configuration/unified-mode/lightspeed-stack-invalid-version-legacy-unified-body.yaml @@ -18,7 +18,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Unified synthesis input (Decision S5): the high-level provider entry # replaces the default baseline's openai provider by id at synthesis time. providers: @@ -26,6 +26,6 @@ inference: id: openai api_key_env: OPENAI_API_KEY allowed_models: - - ${env.E2E_OPENAI_MODEL:=gpt-4o-mini} + - ${env.E2E_OPENAI_MODEL:=gpt-5.6-luna} # INVALID: explicit legacy marker on a unified-shaped body (R11, LCORE-2872) config_format_version: legacy diff --git a/tests/configuration/unified-mode/lightspeed-stack-legacy-for-migration.yaml b/tests/configuration/unified-mode/lightspeed-stack-legacy-for-migration.yaml index 396b24fcb..eba50a042 100644 --- a/tests/configuration/unified-mode/lightspeed-stack-legacy-for-migration.yaml +++ b/tests/configuration/unified-mode/lightspeed-stack-legacy-for-migration.yaml @@ -20,4 +20,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-openai.yaml b/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-openai.yaml index 87f600001..ea199c6c0 100644 --- a/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-openai.yaml +++ b/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-openai.yaml @@ -178,7 +178,7 @@ spec: key: key optional: true - name: E2E_OPENAI_MODEL - value: "gpt-4o-mini" + value: "gpt-5.6-luna" - name: VLLM_URL valueFrom: secretKeyRef: diff --git a/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-prow.yaml b/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-prow.yaml index 22fa0caf2..d800664ed 100644 --- a/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-prow.yaml +++ b/tests/e2e-prow/rhoai/manifests/lightspeed/ogx-prow.yaml @@ -118,7 +118,7 @@ spec: key: key optional: true - name: E2E_OPENAI_MODEL - value: "gpt-4o-mini" + value: "gpt-5.6-luna" - name: LLAMA_STACK_CONFIG value: "/opt/app-root/src/.llama/storage/run.yaml" - name: FAISS_VECTOR_STORE_ID diff --git a/tests/e2e-prow/rhoai/pipeline-konflux.sh b/tests/e2e-prow/rhoai/pipeline-konflux.sh index 72bf84370..70cc11b0a 100755 --- a/tests/e2e-prow/rhoai/pipeline-konflux.sh +++ b/tests/e2e-prow/rhoai/pipeline-konflux.sh @@ -480,7 +480,7 @@ if [[ -n "${VLLM_URL:-}" ]]; then : "${E2E_DEFAULT_MODEL_OVERRIDE:=${VLLM_MODEL:-meta-llama/Llama-3.2-1B-Instruct}}" else : "${E2E_DEFAULT_PROVIDER_OVERRIDE:=openai}" - : "${E2E_DEFAULT_MODEL_OVERRIDE:=${E2E_OPENAI_MODEL:-gpt-4o-mini}}" + : "${E2E_DEFAULT_MODEL_OVERRIDE:=${E2E_OPENAI_MODEL:-gpt-5.6-luna}}" fi export E2E_DEFAULT_PROVIDER_OVERRIDE E2E_DEFAULT_MODEL_OVERRIDE log "LCS accessible at: http://$E2E_LSC_HOSTNAME:8080" diff --git a/tests/e2e/configs/run-azure.yaml b/tests/e2e/configs/run-azure.yaml index 7880b4f41..c5c87ff01 100644 --- a/tests/e2e/configs/run-azure.yaml +++ b/tests/e2e/configs/run-azure.yaml @@ -19,13 +19,13 @@ providers: config: base_url: https://ols-test.openai.azure.com/openai/v1 api_version: 2024-02-15-preview - allowed_models: ["gpt-4o-mini"] + allowed_models: ["gpt-5.6-luna"] model_validation: false - provider_id: openai provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers @@ -99,10 +99,10 @@ storage: backend: sql_default registered_resources: models: - - model_id: gpt-4o-mini + - model_id: gpt-5.6-luna provider_id: azure model_type: llm - provider_model_id: gpt-4o-mini + provider_model_id: gpt-5.6-luna - model_id: all-mpnet-base-v2 model_type: embedding provider_id: sentence-transformers diff --git a/tests/e2e/configs/run-bedrock.yaml b/tests/e2e/configs/run-bedrock.yaml index 3448351e4..a556c9cf5 100644 --- a/tests/e2e/configs/run-bedrock.yaml +++ b/tests/e2e/configs/run-bedrock.yaml @@ -23,7 +23,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers diff --git a/tests/e2e/configs/run-ci.yaml b/tests/e2e/configs/run-ci.yaml index eeae1ec81..b9a85478b 100644 --- a/tests/e2e/configs/run-ci.yaml +++ b/tests/e2e/configs/run-ci.yaml @@ -18,7 +18,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers diff --git a/tests/e2e/configs/run-rhaiis.yaml b/tests/e2e/configs/run-rhaiis.yaml index 542e7cc4c..81bf88188 100644 --- a/tests/e2e/configs/run-rhaiis.yaml +++ b/tests/e2e/configs/run-rhaiis.yaml @@ -25,7 +25,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers diff --git a/tests/e2e/configs/run-rhelai.yaml b/tests/e2e/configs/run-rhelai.yaml index 29e377e82..592071d10 100644 --- a/tests/e2e/configs/run-rhelai.yaml +++ b/tests/e2e/configs/run-rhelai.yaml @@ -25,7 +25,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["gpt-4o-mini"] + allowed_models: ["gpt-5.6-luna"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers diff --git a/tests/e2e/configs/run-vertexai.yaml b/tests/e2e/configs/run-vertexai.yaml index ba361be6d..4aa4b4562 100644 --- a/tests/e2e/configs/run-vertexai.yaml +++ b/tests/e2e/configs/run-vertexai.yaml @@ -24,7 +24,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers diff --git a/tests/e2e/configs/run-watsonx.yaml b/tests/e2e/configs/run-watsonx.yaml index f410eb44a..f62cc1b3e 100644 --- a/tests/e2e/configs/run-watsonx.yaml +++ b/tests/e2e/configs/run-watsonx.yaml @@ -25,7 +25,7 @@ providers: provider_type: remote::openai config: api_key: ${env.OPENAI_API_KEY} - allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"] + allowed_models: ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"] - config: {} provider_id: sentence-transformers provider_type: inline::sentence-transformers diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-authorized.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-authorized.yaml index 8702c2b5b..8820e796d 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-authorized.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-authorized.yaml @@ -36,7 +36,7 @@ authentication: module: "noop-with-token" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: byok: diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-byok-pdf.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-byok-pdf.yaml index 2ee300367..1d27379af 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-byok-pdf.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-byok-pdf.yaml @@ -30,7 +30,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # BYOK store built from a PDF by rag-content's `pdf` module (LCORE-2091). # The vector_db_id is hardcoded to the id baked into the committed fixture diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-compaction-disabled.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-compaction-disabled.yaml index 227792a2f..f6ee927fa 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-compaction-disabled.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-compaction-disabled.yaml @@ -22,7 +22,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Compaction e2e (LCORE-1673): a deliberately small window for every # model the e2e workflows run against, so the third query of the # compaction scenarios crosses the trigger threshold. The real windows @@ -31,8 +31,8 @@ inference: # var, and context_windows keys are not env-substituted, so they are # not listed and skip the token-based trigger. context_windows: - openai/gpt-4o-mini: 2000 - azure/gpt-4o-mini: 2000 + openai/gpt-5.6-luna: 2000 + azure/gpt-5.6-luna: 2000 google-vertex/publishers/google/models/gemini-2.5-flash: 2000 watsonx/meta-llama/llama-3-3-70b-instruct: 2000 aws-bedrock/deepseek.v3-v1:0: 2000 diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-compaction.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-compaction.yaml index 94675e16c..b11a3f5be 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-compaction.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-compaction.yaml @@ -22,7 +22,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Compaction e2e (LCORE-1673): a deliberately small window for every # model the e2e workflows run against, so the third query of the # compaction scenarios crosses the trigger threshold. The real windows @@ -31,8 +31,8 @@ inference: # var, and context_windows keys are not env-substituted, so they are # not listed and skip the token-based trigger. context_windows: - openai/gpt-4o-mini: 2000 - azure/gpt-4o-mini: 2000 + openai/gpt-5.6-luna: 2000 + azure/gpt-5.6-luna: 2000 google-vertex/publishers/google/models/gemini-2.5-flash: 2000 watsonx/meta-llama/llama-3-3-70b-instruct: 2000 aws-bedrock/deepseek.v3-v1:0: 2000 diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-default.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-default.yaml index 778c04d94..8d01eb489 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-default.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-default.yaml @@ -32,7 +32,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: byok: diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-legacy.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-legacy.yaml index 6b4ea3c09..8530ef7c7 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-legacy.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-legacy.yaml @@ -22,7 +22,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: byok: stores: diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-shields-empty.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-shields-empty.yaml index 5a5021bf0..2fc4225ae 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-shields-empty.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-shields-empty.yaml @@ -25,4 +25,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-shields-override-disabled.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-shields-override-disabled.yaml index f93d32297..f9fdd3fa7 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-shields-override-disabled.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-shields-override-disabled.yaml @@ -27,14 +27,14 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna customization: disable_shield_ids_override: true shields: - name: topic-guard provider_id: question_validity config: - model_id: openai/gpt-4o-mini + model_id: openai/gpt-5.6-luna model_prompt: "Classify whether the following question is about OpenShift or Kubernetes. Reply with exactly one word: ${allowed} if it is about OpenShift or Kubernetes, or ${rejected} if it is not. Do not explain your answer or add any other text.\n\nQuestion: ${message}\nAnswer:" invalid_question_response: "I can only answer questions about OpenShift." - name: pii-redaction diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-shields.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-shields.yaml index 14342a3eb..7c282a46a 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-shields.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-shields.yaml @@ -27,12 +27,12 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna shields: - name: topic-guard provider_id: question_validity config: - model_id: openai/gpt-4o-mini + model_id: openai/gpt-5.6-luna # Must reference ${message}; string.Template silently drops the question # otherwise (the classifier would never see what it's classifying). model_prompt: "Classify whether the following question is about OpenShift or Kubernetes. Reply with exactly one word: ${allowed} if it is about OpenShift or Kubernetes, or ${rejected} if it is not. Do not explain your answer or add any other text.\n\nQuestion: ${message}\nAnswer:" diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-skills-directory.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-skills-directory.yaml index e7cd3668e..c5ccd6347 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-skills-directory.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-skills-directory.yaml @@ -24,7 +24,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna skills: paths: - skills diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack-skills.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack-skills.yaml index cf6dd64b0..d377bd71c 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack-skills.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack-skills.yaml @@ -25,7 +25,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna skills: paths: - skills/echo diff --git a/tests/e2e/configuration/library-mode/lightspeed-stack.yaml b/tests/e2e/configuration/library-mode/lightspeed-stack.yaml index a6e135306..f5be1b7a2 100644 --- a/tests/e2e/configuration/library-mode/lightspeed-stack.yaml +++ b/tests/e2e/configuration/library-mode/lightspeed-stack.yaml @@ -22,7 +22,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: byok: stores: diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-authorized.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-authorized.yaml index 941f5657b..7d6bbc4a4 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-authorized.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-authorized.yaml @@ -35,7 +35,7 @@ authentication: module: "noop-with-token" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: byok: diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-compaction-disabled.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-compaction-disabled.yaml index ff1e3510c..880c9ea70 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-compaction-disabled.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-compaction-disabled.yaml @@ -20,7 +20,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Compaction e2e (LCORE-1673): a deliberately small window for every # model the e2e workflows run against, so the third query of the # compaction scenarios crosses the trigger threshold. The real windows @@ -29,8 +29,8 @@ inference: # var, and context_windows keys are not env-substituted, so they are # not listed and skip the token-based trigger. context_windows: - openai/gpt-4o-mini: 2000 - azure/gpt-4o-mini: 2000 + openai/gpt-5.6-luna: 2000 + azure/gpt-5.6-luna: 2000 google-vertex/publishers/google/models/gemini-2.5-flash: 2000 watsonx/meta-llama/llama-3-3-70b-instruct: 2000 aws-bedrock/deepseek.v3-v1:0: 2000 diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-compaction.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-compaction.yaml index 82dd0c32b..de82b73fa 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-compaction.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-compaction.yaml @@ -20,7 +20,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Compaction e2e (LCORE-1673): a deliberately small window for every # model the e2e workflows run against, so the third query of the # compaction scenarios crosses the trigger threshold. The real windows @@ -29,8 +29,8 @@ inference: # var, and context_windows keys are not env-substituted, so they are # not listed and skip the token-based trigger. context_windows: - openai/gpt-4o-mini: 2000 - azure/gpt-4o-mini: 2000 + openai/gpt-5.6-luna: 2000 + azure/gpt-5.6-luna: 2000 google-vertex/publishers/google/models/gemini-2.5-flash: 2000 watsonx/meta-llama/llama-3-3-70b-instruct: 2000 aws-bedrock/deepseek.v3-v1:0: 2000 diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-default.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-default.yaml index 294603b16..aca1c6f95 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-default.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-default.yaml @@ -30,7 +30,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: byok: diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-degraded.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-degraded.yaml index 798d407c1..c7f209728 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-degraded.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-degraded.yaml @@ -24,4 +24,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-offline.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-offline.yaml index 1b8720634..a259df0cf 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-offline.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-offline.yaml @@ -26,7 +26,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: retrieval: inline: diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-online.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-online.yaml index f598622ae..6d0094187 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-online.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-online.yaml @@ -27,7 +27,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: retrieval: inline: diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-offline.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-offline.yaml index 31f93aa5d..04044f7c4 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-offline.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-offline.yaml @@ -26,7 +26,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: retrieval: tool: diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-online.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-online.yaml index c72db0572..c0dbe8f98 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-online.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-okp-tool-online.yaml @@ -27,7 +27,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: retrieval: tool: diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-shields-empty.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-shields-empty.yaml index 1a5233af3..1afecb4d8 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-shields-empty.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-shields-empty.yaml @@ -23,4 +23,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-shields-override-disabled.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-shields-override-disabled.yaml index 40220313b..d3a456f9f 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-shields-override-disabled.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-shields-override-disabled.yaml @@ -25,14 +25,14 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna customization: disable_shield_ids_override: true shields: - name: topic-guard provider_id: question_validity config: - model_id: openai/gpt-4o-mini + model_id: openai/gpt-5.6-luna model_prompt: "Classify whether the following question is about OpenShift or Kubernetes. Reply with exactly one word: ${allowed} if it is about OpenShift or Kubernetes, or ${rejected} if it is not. Do not explain your answer or add any other text.\n\nQuestion: ${message}\nAnswer:" invalid_question_response: "I can only answer questions about OpenShift." - name: pii-redaction diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-shields.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-shields.yaml index e9966dbf6..f0a4d9e8c 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-shields.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-shields.yaml @@ -25,12 +25,12 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna shields: - name: topic-guard provider_id: question_validity config: - model_id: openai/gpt-4o-mini + model_id: openai/gpt-5.6-luna # Must reference ${message}; string.Template silently drops the question # otherwise (the classifier would never see what it's classifying). model_prompt: "Classify whether the following question is about OpenShift or Kubernetes. Reply with exactly one word: ${allowed} if it is about OpenShift or Kubernetes, or ${rejected} if it is not. Do not explain your answer or add any other text.\n\nQuestion: ${message}\nAnswer:" diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-skills-directory.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-skills-directory.yaml index 6418577ce..d43b7c138 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-skills-directory.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-skills-directory.yaml @@ -21,7 +21,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna skills: paths: - skills diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack-skills.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack-skills.yaml index 3c7a1b7c4..d203e7af6 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack-skills.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack-skills.yaml @@ -21,7 +21,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna skills: paths: - skills/echo diff --git a/tests/e2e/configuration/server-mode/lightspeed-stack.yaml b/tests/e2e/configuration/server-mode/lightspeed-stack.yaml index ece1c9c66..0e64e6422 100644 --- a/tests/e2e/configuration/server-mode/lightspeed-stack.yaml +++ b/tests/e2e/configuration/server-mode/lightspeed-stack.yaml @@ -20,7 +20,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna rag: byok: stores: diff --git a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-legacy-for-migration.yaml b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-legacy-for-migration.yaml index 6393142b5..71488a02d 100644 --- a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-legacy-for-migration.yaml +++ b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-legacy-for-migration.yaml @@ -20,4 +20,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-absolute-profile.yaml b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-absolute-profile.yaml index 089fb0afb..46719c545 100644 --- a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-absolute-profile.yaml +++ b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-absolute-profile.yaml @@ -21,4 +21,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-config-only.yaml b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-config-only.yaml index b3df26828..8dd94d04d 100644 --- a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-config-only.yaml +++ b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-config-only.yaml @@ -21,4 +21,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-migrated.yaml b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-migrated.yaml index 576b1950a..db8ea6009 100644 --- a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-migrated.yaml +++ b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-migrated.yaml @@ -1,7 +1,7 @@ authentication: module: noop inference: - default_model: gpt-4o-mini + default_model: gpt-5.6-luna default_provider: openai name: Lightspeed Core Service (LCS) ogx: @@ -36,7 +36,7 @@ ogx: inference: - config: allowed_models: - - ${env.E2E_OPENAI_MODEL:=gpt-4o-mini} + - ${env.E2E_OPENAI_MODEL:=gpt-5.6-luna} api_key: ${env.OPENAI_API_KEY} provider_id: openai provider_type: remote::openai diff --git a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-providers.yaml b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-providers.yaml index 731c39b5d..a465c99dc 100644 --- a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-providers.yaml +++ b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-providers.yaml @@ -18,7 +18,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Unified synthesis input (Decision S5): the high-level provider entry # replaces the default baseline's openai provider by id at synthesis time. providers: @@ -26,4 +26,4 @@ inference: id: openai api_key_env: OPENAI_API_KEY allowed_models: - - ${env.E2E_OPENAI_MODEL:=gpt-4o-mini} + - ${env.E2E_OPENAI_MODEL:=gpt-5.6-luna} diff --git a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-relative-profile.yaml b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-relative-profile.yaml index 228b2d40b..ac6a12982 100644 --- a/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-relative-profile.yaml +++ b/tests/e2e/configuration/unified-mode/library-mode/lightspeed-stack-unified-relative-profile.yaml @@ -21,4 +21,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-legacy-for-migration.yaml b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-legacy-for-migration.yaml index 18890c9fc..777756f5a 100644 --- a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-legacy-for-migration.yaml +++ b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-legacy-for-migration.yaml @@ -20,4 +20,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-absolute-profile.yaml b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-absolute-profile.yaml index 7f9b31dd5..e9d93b371 100644 --- a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-absolute-profile.yaml +++ b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-absolute-profile.yaml @@ -23,4 +23,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-config-only.yaml b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-config-only.yaml index 34ace93fa..1c182a0ff 100644 --- a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-config-only.yaml +++ b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-config-only.yaml @@ -23,4 +23,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-migrated.yaml b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-migrated.yaml index 7a31fc7af..068985a40 100644 --- a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-migrated.yaml +++ b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-migrated.yaml @@ -1,7 +1,7 @@ authentication: module: noop inference: - default_model: gpt-4o-mini + default_model: gpt-5.6-luna default_provider: openai name: Lightspeed Core Service (LCS) ogx: @@ -37,7 +37,7 @@ ogx: inference: - config: allowed_models: - - ${env.E2E_OPENAI_MODEL:=gpt-4o-mini} + - ${env.E2E_OPENAI_MODEL:=gpt-5.6-luna} api_key: ${env.OPENAI_API_KEY} provider_id: openai provider_type: remote::openai diff --git a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-providers.yaml b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-providers.yaml index a40465cca..4226e2c82 100644 --- a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-providers.yaml +++ b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-providers.yaml @@ -20,7 +20,7 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna # Unified synthesis input (Decision S5): the high-level provider entry # replaces the default baseline's openai provider by id at synthesis time. providers: @@ -28,4 +28,4 @@ inference: id: openai api_key_env: OPENAI_API_KEY allowed_models: - - ${env.E2E_OPENAI_MODEL:=gpt-4o-mini} + - ${env.E2E_OPENAI_MODEL:=gpt-5.6-luna} diff --git a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-relative-profile.yaml b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-relative-profile.yaml index 0647f4c87..82aefde14 100644 --- a/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-relative-profile.yaml +++ b/tests/e2e/configuration/unified-mode/server-mode/lightspeed-stack-unified-relative-profile.yaml @@ -23,4 +23,4 @@ authentication: module: "noop" inference: default_provider: openai - default_model: gpt-4o-mini + default_model: gpt-5.6-luna diff --git a/tests/e2e/features/environment.py b/tests/e2e/features/environment.py index 2080fee9c..612b43e0d 100644 --- a/tests/e2e/features/environment.py +++ b/tests/e2e/features/environment.py @@ -46,7 +46,7 @@ switch_config, ) -FALLBACK_MODEL = "gpt-4o-mini" +FALLBACK_MODEL = "gpt-5.6-luna" FALLBACK_PROVIDER = "openai" # Wall-clock start for each feature (on ``Feature``; survives Behave context resets). diff --git a/tests/e2e/features/query.feature b/tests/e2e/features/query.feature index c8e98efb5..796ac6d88 100644 --- a/tests/e2e/features/query.feature +++ b/tests/e2e/features/query.feature @@ -20,7 +20,7 @@ Feature: Query endpoint API tests Then The status code of the response is 200 And The response contains following fragments | Fragments in LLM response | - | ask | + | questions | And The token metrics have increased @flaky diff --git a/tests/e2e/features/shields.feature b/tests/e2e/features/shields.feature index 4a942ac05..a717782ff 100644 --- a/tests/e2e/features/shields.feature +++ b/tests/e2e/features/shields.feature @@ -27,7 +27,7 @@ Feature: Shields endpoint tests "provider_id": "question_validity", "type": "shield", "config": { - "model_id": "openai/gpt-4o-mini", + "model_id": "openai/gpt-5.6-luna", "model_prompt": "Classify whether the following question is about OpenShift or Kubernetes. Reply with exactly one word: ${allowed} if it is about OpenShift or Kubernetes, or ${rejected} if it is not. Do not explain your answer or add any other text.\n\nQuestion: ${message}\nAnswer:", "invalid_question_response": "I can only answer questions about OpenShift." } diff --git a/tests/e2e/features/shields_granite_guardian.feature b/tests/e2e/features/shields_granite_guardian.feature index 9427f5447..f817e8ac8 100644 --- a/tests/e2e/features/shields_granite_guardian.feature +++ b/tests/e2e/features/shields_granite_guardian.feature @@ -24,7 +24,7 @@ Feature: granite_guardian shield functional tests } """ Then The status code of the response is 200 - And The body of the response contains cluster + And The body of the response contains kubectl And The body of the response does not contain That phrasing is not something I can act on. And The body of the response does not contain I cannot return that response. diff --git a/tests/integration/test_unified_synthesis.py b/tests/integration/test_unified_synthesis.py index 0d5f9adba..cc0fbe3cc 100644 --- a/tests/integration/test_unified_synthesis.py +++ b/tests/integration/test_unified_synthesis.py @@ -97,7 +97,7 @@ "registered_resources": { "models": [ { - "model_id": "gpt-4o-mini", + "model_id": "gpt-5.6-luna", "provider_id": "azure", "model_type": "llm", } diff --git a/tests/unit/app/endpoints/test_responses.py b/tests/unit/app/endpoints/test_responses.py index 27da0c1a1..28f3aab5a 100644 --- a/tests/unit/app/endpoints/test_responses.py +++ b/tests/unit/app/endpoints/test_responses.py @@ -669,6 +669,133 @@ async def test_responses_blocked_with_conversation_appends_refusal( assert "model" in payload, "Handler must set model on the response payload" ResponsesResponse.model_validate(payload) + @pytest.mark.asyncio + async def test_shield_after_last_refusal_header_windows_moderation( + self, + minimal_config: AppConfig, + mocker: MockerFixture, + ) -> None: + """X-LCS-Shield-After-Last-Refusal moderates only items after last modr-.""" + structured_input: list[Any] = [ + OpenAIResponseMessage(role="user", content="blocked earlier"), + OpenAIResponseMessage( + role="assistant", + content="I cannot help with that.", + id="modr-abc", + ), + OpenAIResponseMessage(role="user", content="What is Kubernetes?"), + ] + responses_request = ResponsesRequest( + input=cast("Any", structured_input), + store=False, + ) + req = Request( + scope={ + "type": "http", + "headers": [(b"x-lcs-shield-after-last-refusal", b"true")], + } + ) + req.state.authorized_actions = { + Action.RESPONSES, + Action.READ_OTHERS_CONVERSATIONS, + } + + _patch_base(mocker, minimal_config) + _patch_client(mocker) + _patch_resolve_response_context(mocker) + mocker.patch( + f"{MODULE}.select_model_for_responses", + new=mocker.AsyncMock(return_value="provider/model1"), + ) + mocker.patch( + f"{MODULE}.check_model_configured", + new=mocker.AsyncMock(return_value=True), + ) + _patch_rag(mocker) + mock_moderation = mocker.patch( + f"{MODULE}.run_shield_moderation_v2", + new=mocker.AsyncMock(return_value=ShieldModerationPassed()), + ) + mocker.patch( + f"{MODULE}.handle_non_streaming_response", + new=mocker.AsyncMock( + return_value=_make_responses_response( + output_text="K8s is Kubernetes.", + conversation="conv_new", + ) + ), + ) + + await responses_endpoint_handler( + request=req, + responses_request=responses_request, + auth=MOCK_AUTH, + mcp_headers={}, + ) + + moderated_text = mock_moderation.await_args.args[0] + assert moderated_text.startswith("What is Kubernetes?") + assert "blocked earlier" not in moderated_text + assert "I cannot help with that." not in moderated_text + + @pytest.mark.asyncio + async def test_without_shield_after_last_refusal_header_moderates_full_input( + self, + dummy_request: Request, + minimal_config: AppConfig, + mocker: MockerFixture, + ) -> None: + """Without the header, shields still see the full resent history.""" + structured_input: list[Any] = [ + OpenAIResponseMessage(role="user", content="blocked earlier"), + OpenAIResponseMessage( + role="assistant", + content="I cannot help with that.", + id="modr-abc", + ), + OpenAIResponseMessage(role="user", content="What is Kubernetes?"), + ] + responses_request = ResponsesRequest( + input=cast("Any", structured_input), + store=False, + ) + _patch_base(mocker, minimal_config) + _patch_client(mocker) + _patch_resolve_response_context(mocker) + mocker.patch( + f"{MODULE}.select_model_for_responses", + new=mocker.AsyncMock(return_value="provider/model1"), + ) + mocker.patch( + f"{MODULE}.check_model_configured", + new=mocker.AsyncMock(return_value=True), + ) + _patch_rag(mocker) + mock_moderation = mocker.patch( + f"{MODULE}.run_shield_moderation_v2", + new=mocker.AsyncMock(return_value=ShieldModerationPassed()), + ) + mocker.patch( + f"{MODULE}.handle_non_streaming_response", + new=mocker.AsyncMock( + return_value=_make_responses_response( + output_text="K8s is Kubernetes.", + conversation="conv_new", + ) + ), + ) + + await responses_endpoint_handler( + request=dummy_request, + responses_request=responses_request, + auth=MOCK_AUTH, + mcp_headers={}, + ) + + moderated_text = mock_moderation.await_args.args[0] + assert "blocked earlier" in moderated_text + assert "What is Kubernetes?" in moderated_text + @pytest.mark.asyncio async def test_tool_choice_none_without_tools_does_not_load_server_tools( self, diff --git a/tests/unit/app/endpoints/test_rlsapi_v1.py b/tests/unit/app/endpoints/test_rlsapi_v1.py index f223aaec5..1e07b0959 100644 --- a/tests/unit/app/endpoints/test_rlsapi_v1.py +++ b/tests/unit/app/endpoints/test_rlsapi_v1.py @@ -451,7 +451,7 @@ async def test_get_default_model_id_auto_discovery_success( mocker.patch("app.endpoints.rlsapi_v1.configuration", minimal_config) mock_llm_model = make_openai_model( - model_id="openai/gpt-4o-mini", + model_id="openai/gpt-5.6-luna", provider_id="openai", model_type="llm", ) @@ -479,7 +479,7 @@ async def test_get_default_model_id_auto_discovery_success( model_id = await _get_default_model_id() - assert model_id == "openai/gpt-4o-mini" + assert model_id == "openai/gpt-5.6-luna" # --- Test get_rh_identity_context --- diff --git a/tests/unit/app/endpoints/test_shields.py b/tests/unit/app/endpoints/test_shields.py index d3869d188..bbd4c472f 100644 --- a/tests/unit/app/endpoints/test_shields.py +++ b/tests/unit/app/endpoints/test_shields.py @@ -106,7 +106,7 @@ async def test_shields_endpoint_handler_configured_shields( "name": "question-validity", "provider_id": "question_validity", "config": { - "model_id": "openai/gpt-4o-mini", + "model_id": "openai/gpt-5.6-luna", "model_prompt": "Is this question valid?", "invalid_question_response": "I can only answer product questions.", }, @@ -138,7 +138,7 @@ async def test_shields_endpoint_handler_configured_shields( assert response.shields[0].name == "question-validity" assert response.shields[0].provider_id == "question_validity" assert response.shields[0].type == "shield" - assert response.shields[0].config["model_id"] == "openai/gpt-4o-mini" + assert response.shields[0].config["model_id"] == "openai/gpt-5.6-luna" assert response.shields[1].name == "pii-redaction" assert response.shields[1].provider_id == "redaction" assert response.shields[1].type == "shield" diff --git a/tests/unit/cache/test_in_memory_cache.py b/tests/unit/cache/test_in_memory_cache.py index 2b11aace5..ea24cc436 100644 --- a/tests/unit/cache/test_in_memory_cache.py +++ b/tests/unit/cache/test_in_memory_cache.py @@ -21,7 +21,7 @@ summarized_through_turn=8, token_count=9, created_at="2025-10-03T09:31:29Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) diff --git a/tests/unit/cache/test_noop_cache.py b/tests/unit/cache/test_noop_cache.py index 36fb82da2..79f7a2aed 100644 --- a/tests/unit/cache/test_noop_cache.py +++ b/tests/unit/cache/test_noop_cache.py @@ -260,7 +260,7 @@ def test_get_improper_conversation_id(cache_fixture: NoopCache) -> None: summarized_through_turn=8, token_count=9, created_at="2025-10-03T09:31:29Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) diff --git a/tests/unit/cache/test_postgres_cache.py b/tests/unit/cache/test_postgres_cache.py index 0ce2015c6..604ebba8f 100644 --- a/tests/unit/cache/test_postgres_cache.py +++ b/tests/unit/cache/test_postgres_cache.py @@ -926,14 +926,14 @@ def test_insert_and_get_with_tool_calls_and_results( summarized_through_turn=8, token_count=14, created_at="2025-10-03T09:31:29Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) summary_2 = ConversationSummary( summary_text="User then moved on to Helm charts and Istio routing.", summarized_through_turn=18, token_count=12, created_at="2025-10-03T10:05:01Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) # A fold collapsing the accumulated chunks (R3, LCORE-1572). folded_summary = ConversationSummary( @@ -941,7 +941,7 @@ def test_insert_and_get_with_tool_calls_and_results( summarized_through_turn=18, token_count=11, created_at="2025-10-03T11:00:00Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) diff --git a/tests/unit/cache/test_sqlite_cache.py b/tests/unit/cache/test_sqlite_cache.py index 2fdb9ba1a..3a3e1eab9 100644 --- a/tests/unit/cache/test_sqlite_cache.py +++ b/tests/unit/cache/test_sqlite_cache.py @@ -626,14 +626,14 @@ def test_insert_and_get_with_all_fields(tmpdir: Path) -> None: summarized_through_turn=8, token_count=14, created_at="2025-10-03T09:31:29Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) summary_2 = ConversationSummary( summary_text="User then moved on to Helm charts and Istio routing.", summarized_through_turn=18, token_count=12, created_at="2025-10-03T10:05:01Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) # A fold collapsing summary_1 + summary_2; created after both (R3, LCORE-1572). folded_summary = ConversationSummary( @@ -641,7 +641,7 @@ def test_insert_and_get_with_all_fields(tmpdir: Path) -> None: summarized_through_turn=18, token_count=11, created_at="2025-10-03T11:00:00Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) # A chunk produced after a fold; created later still, so it sorts after the fold. summary_after_fold = ConversationSummary( @@ -649,7 +649,7 @@ def test_insert_and_get_with_all_fields(tmpdir: Path) -> None: summarized_through_turn=26, token_count=10, created_at="2025-10-03T11:42:00Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) diff --git a/tests/unit/models/config/test_customization.py b/tests/unit/models/config/test_customization.py index 71fc9cf1d..f01c9e2e9 100644 --- a/tests/unit/models/config/test_customization.py +++ b/tests/unit/models/config/test_customization.py @@ -6,6 +6,7 @@ from pydantic import ValidationError from pytest_subtests import SubTests +from constants import RESPONSES_REQUEST_MAX_SIZE from models.config import Customization @@ -57,6 +58,21 @@ def test_service_customization(subtests: SubTests) -> None: assert c.disable_query_system_prompt is True +def test_service_customization_max_response_request_size(subtests: SubTests) -> None: + """Check the service customization class.""" + with subtests.test(msg="Default response request size should be used"): + c = Customization() + assert c is not None + # default value should not be used now + assert c.responses_request_max_size == RESPONSES_REQUEST_MAX_SIZE + + with subtests.test(msg="Max response request size is specified"): + c = Customization(responses_request_max_size=42) + assert c is not None + # default value should not be used now + assert c.responses_request_max_size == 42 + + def test_service_customization_wrong_system_prompt_path() -> None: """Check the service customization class. diff --git a/tests/unit/models/config/test_inference_configuration.py b/tests/unit/models/config/test_inference_configuration.py index 0fcabed96..66907f883 100644 --- a/tests/unit/models/config/test_inference_configuration.py +++ b/tests/unit/models/config/test_inference_configuration.py @@ -69,19 +69,19 @@ def test_context_windows_accepts_model_to_size_map() -> None: """Test context_windows accepts a populated model-to-window map.""" inference_config = InferenceConfiguration( context_windows={ - "openai/gpt-4o-mini": 128000, - "openai/gpt-4o": 128000, + "openai/gpt-5.6-luna": 128000, + "openai/gpt-5.6-terra": 128000, }, ) # pyright: ignore[reportCallIssue] - assert inference_config.context_windows["openai/gpt-4o-mini"] == 128000 - assert inference_config.context_windows["openai/gpt-4o"] == 128000 + assert inference_config.context_windows["openai/gpt-5.6-luna"] == 128000 + assert inference_config.context_windows["openai/gpt-5.6-terra"] == 128000 def test_context_windows_rejects_non_positive_size() -> None: """Test that a non-positive window size is rejected by Pydantic.""" with pytest.raises(ValueError): InferenceConfiguration( - context_windows={"openai/gpt-4o-mini": 0}, + context_windows={"openai/gpt-5.6-luna": 0}, ) # pyright: ignore[reportCallIssue] @@ -89,7 +89,7 @@ def test_context_windows_rejects_negative_size() -> None: """Test that a negative window size is rejected by Pydantic.""" with pytest.raises(ValueError): InferenceConfiguration( - context_windows={"openai/gpt-4o-mini": -1}, + context_windows={"openai/gpt-5.6-luna": -1}, ) # pyright: ignore[reportCallIssue] diff --git a/tests/unit/models/requests/test_responses_request.py b/tests/unit/models/requests/test_responses_request.py index 4d8e1e69a..ec5d7ac1c 100644 --- a/tests/unit/models/requests/test_responses_request.py +++ b/tests/unit/models/requests/test_responses_request.py @@ -39,12 +39,12 @@ def test_one_over_limit_rejected(self) -> None: ResponsesRequest(input=payload) # pyright: ignore[reportCallIssue] error_text = str(exc_info.value) - assert "65536" in error_text + assert "131073" in error_text assert str(_LIMIT + 1) in error_text def test_large_list_input_rejected(self) -> None: """A request with a list input that exceeds 64 KB is rejected.""" - long_item_text = "a" * 1000 + long_item_text = "a" * 2000 large_list = [{"role": "user", "content": long_item_text}] * 100 raw = {"input": large_list} @@ -54,4 +54,71 @@ def test_large_list_input_rejected(self) -> None: ResponsesRequest(input=large_list) # pyright: ignore[reportCallIssue] error_text = str(exc_info.value) - assert "65536" in error_text + assert "203311" in error_text + + def test_model_validation_normal_request_accepted(self) -> None: + """A small, normal request must be accepted without raising.""" + req = ResponsesRequest.model_validate( + { + "input": "hello", + } + ) + assert req.input == "hello" + + def test_model_validation_normal_request_accepted_configurable_limit(self) -> None: + """A small, normal request must be accepted without raising.""" + req = ResponsesRequest.model_validate( + { + "input": "hello", + }, + context={"responses_request_max_size": _LIMIT * 2}, + ) + assert req.input == "hello" + + def test_model_validation_over_limit(self) -> None: + """A request whose JSON serialization is 65,537 chars raises ValidationError.""" + payload = "x" * (_AT_LIMIT_PADDING + 1) + serialized = json.dumps({"input": payload}) + assert len(serialized) == _LIMIT + 1 + + with pytest.raises(ValidationError) as exc_info: + ResponsesRequest.model_validate( + { + "input": payload, + } + ) + + error_text = str(exc_info.value) + assert "131073" in error_text + assert str(_LIMIT + 1) in error_text + + def test_model_validation_over_standard_limit_not_reached_threshold(self) -> None: + """Validation for custom limit.""" + payload = "x" * (_AT_LIMIT_PADDING + 1) + serialized = json.dumps({"input": payload}) + assert len(serialized) == _LIMIT + 1 + + req = ResponsesRequest.model_validate( + { + "input": payload, + }, + context={"responses_request_max_size": _LIMIT * 2}, + ) + assert req.input is not None + assert "xxxx" in req.input + + def test_model_validation_over_limit_reached_threshold(self) -> None: + """A request whose JSON serialization is longer than max size raises ValidationError.""" + payload = "x" * (110_000 - _OVERHEAD) + + with pytest.raises(ValidationError) as exc_info: + ResponsesRequest.model_validate( + { + "input": payload, + }, + context={"responses_request_max_size": 100_000}, + ) + + error_text = str(exc_info.value) + assert "110000" in error_text + assert "100000" in error_text diff --git a/tests/unit/models/responses/test_successful_responses.py b/tests/unit/models/responses/test_successful_responses.py index 8f323e8ef..d409bd8b2 100644 --- a/tests/unit/models/responses/test_successful_responses.py +++ b/tests/unit/models/responses/test_successful_responses.py @@ -192,7 +192,7 @@ def test_constructor(self) -> None: "provider_id": "question_validity", "type": "shield", "config": { - "model_id": "openai/gpt-4o-mini", + "model_id": "openai/gpt-5.6-luna", "model_prompt": "Is this question valid?", "invalid_question_response": ( "I can only answer questions about the product." diff --git a/tests/unit/models/test_compaction.py b/tests/unit/models/test_compaction.py index f20ba5b55..7379e1499 100644 --- a/tests/unit/models/test_compaction.py +++ b/tests/unit/models/test_compaction.py @@ -12,7 +12,7 @@ def _valid_kwargs() -> dict: "summarized_through_turn": 8, "token_count": 42, "created_at": "2026-05-11T00:00:00Z", - "model_used": "openai/gpt-4o-mini", + "model_used": "openai/gpt-5.6-luna", } @@ -23,7 +23,7 @@ def test_constructor_with_valid_values() -> None: assert summary.summarized_through_turn == 8 assert summary.token_count == 42 assert summary.created_at == "2026-05-11T00:00:00Z" - assert summary.model_used == "openai/gpt-4o-mini" + assert summary.model_used == "openai/gpt-5.6-luna" def test_summarized_through_turn_accepts_zero() -> None: diff --git a/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py b/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py index f35a0a551..c8cc3a8c3 100644 --- a/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py +++ b/tests/unit/pydantic_ai_lightspeed/capabilities/question_validity/test_capability.py @@ -742,6 +742,7 @@ async def test_rejected_returns_blocked(self, mocker: MockerFixture) -> None: assert result.moderation_id.startswith("modr-") assert result.refusal_response.role == "assistant" assert result.refusal_response.content == DEFAULT_INVALID_QUESTION_RESPONSE + assert result.refusal_response.id == result.moderation_id @pytest.mark.asyncio async def test_unexpected_response_returns_blocked( diff --git a/tests/unit/pydantic_ai_lightspeed/ogx/test_provider.py b/tests/unit/pydantic_ai_lightspeed/ogx/test_provider.py index 2b4425964..d8b394cd7 100644 --- a/tests/unit/pydantic_ai_lightspeed/ogx/test_provider.py +++ b/tests/unit/pydantic_ai_lightspeed/ogx/test_provider.py @@ -44,7 +44,7 @@ def test_repr(self) -> None: def test_model_profile_known_model(self) -> None: """Test model_profile returns a profile for a known OpenAI model.""" - profile = OgxProvider.model_profile("gpt-4o") + profile = OgxProvider.model_profile("gpt-5.6-luna") assert profile is not None def test_model_profile_unknown_model(self) -> None: diff --git a/tests/unit/telemetry/conftest.py b/tests/unit/telemetry/conftest.py index a13f43647..f1ef7d39c 100644 --- a/tests/unit/telemetry/conftest.py +++ b/tests/unit/telemetry/conftest.py @@ -251,9 +251,9 @@ "registered_resources": { "models": [ { - "model_id": "gpt-4o-mini", + "model_id": "gpt-5.6-luna", "provider_id": "openai", - "provider_model_id": "gpt-4o-mini", + "provider_model_id": "gpt-5.6-luna", "model_type": "llm", }, { @@ -352,9 +352,9 @@ def build_fully_populated_config() -> Configuration: ), ), inference=InferenceConfiguration.model_construct( - default_model="gpt-4o-mini", + default_model="gpt-5.6-luna", default_provider="openai", - context_windows={"openai/gpt-4o-mini": 128000}, + context_windows={"openai/gpt-5.6-luna": 128000}, max_infer_iters=10, max_tool_calls=30, providers=[ @@ -362,7 +362,7 @@ def build_fully_populated_config() -> Configuration: type="openai", id="openai-provider", api_key_env=PII_PROVIDER_API_KEY_ENV, - allowed_models=["gpt-4o-mini", "gpt-4o"], + allowed_models=["gpt-5.6-luna", "gpt-5.6-luna"], extra={}, ), ], diff --git a/tests/unit/telemetry/test_configuration_snapshot.py b/tests/unit/telemetry/test_configuration_snapshot.py index 0912a34c3..2f68f5f76 100644 --- a/tests/unit/telemetry/test_configuration_snapshot.py +++ b/tests/unit/telemetry/test_configuration_snapshot.py @@ -465,7 +465,7 @@ def test_passthrough_fields_preserved(self) -> None: assert snapshot["service"]["cors"]["allow_credentials"] is True assert snapshot["service"]["cors"]["allow_methods"] == ["GET", "POST"] assert snapshot["ogx"]["use_as_library_client"] is False - assert snapshot["inference"]["default_model"] == "gpt-4o-mini" + assert snapshot["inference"]["default_model"] == "gpt-5.6-luna" assert snapshot["inference"]["default_provider"] == "openai" assert snapshot["authentication"]["module"] == "jwk_token" assert snapshot["authentication"]["skip_tls_verification"] is False @@ -599,7 +599,7 @@ def test_inference_context_windows_passthrough(self) -> None: """Test inference context_windows passes through.""" snapshot = build_lightspeed_stack_snapshot(build_fully_populated_config()) assert snapshot["inference"]["context_windows"] == { - "openai/gpt-4o-mini": 128000 + "openai/gpt-5.6-luna": 128000 } def test_inference_max_infer_iters_passthrough(self) -> None: @@ -621,7 +621,7 @@ def test_inference_providers_extraction(self) -> None: assert providers[0]["type"] == "openai" assert providers[0]["id"] == "openai-provider" assert providers[0]["api_key_env"] == CONFIGURED - assert providers[0]["allowed_models"] == ["gpt-4o-mini", "gpt-4o"] + assert providers[0]["allowed_models"] == ["gpt-5.6-luna", "gpt-5.6-luna"] def test_inference_providers_empty(self) -> None: """Test inference providers when empty.""" @@ -1097,7 +1097,7 @@ async def test_models_extraction(self, ogx_config_file: str) -> None: result = await build_ogx_snapshot(ogx_config_file) models = result["registered_resources"]["models"] assert len(models) == 2 - assert models[0]["model_id"] == "gpt-4o-mini" + assert models[0]["model_id"] == "gpt-5.6-luna" assert models[0]["model_type"] == "llm" @pytest.mark.asyncio diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index 6df3a10f4..2f711a3aa 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -4180,7 +4180,7 @@ def test_native_override_env_refs_not_resolved( OGX's own raw schema. """ monkeypatch.setenv("LCORE_TEST_SECRET", "supersecret") - monkeypatch.setenv("LCORE_TEST_MODEL", "gpt-4o-mini") + monkeypatch.setenv("LCORE_TEST_MODEL", "gpt-5.6-luna") config_dict = { "inference": {"default_model": "${env.LCORE_TEST_MODEL}"}, "ogx": { @@ -4206,7 +4206,7 @@ def test_native_override_env_refs_not_resolved( assert override_api_key == "${env.LCORE_TEST_SECRET}" assert "supersecret" not in str(resolved["ogx"]) # everything outside native_override still resolves - assert resolved["inference"]["default_model"] == "gpt-4o-mini" + assert resolved["inference"]["default_model"] == "gpt-5.6-luna" def test_replace_env_vars_preserves_native_override_under_deprecated_llama_stack_key( @@ -4241,7 +4241,7 @@ def test_replace_env_vars_without_native_override_resolves_all( monkeypatch: pytest.MonkeyPatch, ) -> None: """With no native_override, the helper resolves env refs like the plain call.""" - monkeypatch.setenv("LCORE_TEST_MODEL", "gpt-4o-mini") + monkeypatch.setenv("LCORE_TEST_MODEL", "gpt-5.6-luna") config_dict = {"inference": {"default_model": "${env.LCORE_TEST_MODEL}"}} resolved = replace_env_vars_preserving_native_override(config_dict) - assert resolved["inference"]["default_model"] == "gpt-4o-mini" + assert resolved["inference"]["default_model"] == "gpt-5.6-luna" diff --git a/tests/unit/test_ogx_synthesize.py b/tests/unit/test_ogx_synthesize.py index 2cefb8468..4f3e2ae3f 100644 --- a/tests/unit/test_ogx_synthesize.py +++ b/tests/unit/test_ogx_synthesize.py @@ -173,7 +173,7 @@ def test_load_default_baseline_openai_is_conditional_on_api_key() -> None: "provider_type": "remote::openai", "config": { "api_key": OPENAI_CONDITIONAL_API_KEY, - "allowed_models": ["${env.E2E_OPENAI_MODEL:=gpt-4o-mini}"], + "allowed_models": ["${env.E2E_OPENAI_MODEL:=gpt-5.6-luna}"], }, } ] @@ -273,7 +273,7 @@ def test_apply_high_level_inference_maps_type_and_emits_env_ref() -> None: { "type": "openai", "api_key_env": "OPENAI_API_KEY", - "allowed_models": ["gpt-4o-mini"], + "allowed_models": ["gpt-5.6-luna"], "extra": {}, } ] @@ -283,7 +283,7 @@ def test_apply_high_level_inference_maps_type_and_emits_env_ref() -> None: assert entry["provider_id"] == "openai" assert entry["provider_type"] == "remote::openai" assert entry["config"]["api_key"] == "${env.OPENAI_API_KEY}" - assert entry["config"]["allowed_models"] == ["gpt-4o-mini"] + assert entry["config"]["allowed_models"] == ["gpt-5.6-luna"] def test_apply_high_level_inference_hyphenates_provider_id() -> None: @@ -993,7 +993,7 @@ def test_synthesize_to_file_tightens_perms_on_overwrite(tmp_path: Path) -> None: "provider_type": "remote::openai", "config": { "api_key": "${env.OPENAI_API_KEY}", - "allowed_models": ["gpt-4o-mini"], + "allowed_models": ["gpt-5.6-luna"], }, }, { diff --git a/tests/unit/utils/dumpers/test_models_dumper.py b/tests/unit/utils/dumpers/test_models_dumper.py index 2dce7605a..8226bf35c 100644 --- a/tests/unit/utils/dumpers/test_models_dumper.py +++ b/tests/unit/utils/dumpers/test_models_dumper.py @@ -1451,7 +1451,7 @@ def test_dump_models(tmpdir: Path) -> None: "type": "assistant" } ], - "model": "gpt-4o-mini", + "model": "gpt-5.6-luna", "provider": "openai", "started_at": "2024-01-01T00:01:00Z", "tool_calls": [], @@ -1485,7 +1485,7 @@ def test_dump_models(tmpdir: Path) -> None: "type": "assistant" } ], - "model": "gpt-4o-mini", + "model": "gpt-5.6-luna", "provider": "openai", "started_at": "2024-01-01T00:01:00Z", "tool_calls": [], @@ -1507,7 +1507,7 @@ def test_dump_models(tmpdir: Path) -> None: "type": "object" }, "ConversationSummary": { - "description": "A single compaction-produced summary chunk.\n\nAttributes:\n summary_text: The natural-language summary produced by the\n summarization LLM call. Used directly as context for\n subsequent requests (alongside any later summary chunks\n and the buffer of recent turns kept verbatim).\n summarized_through_turn: Running total of conversation items\n consumed by this and all preceding summaries. Used by the\n caller to advance the partition boundary on the next\n compaction so the new summary only covers items that\n have not yet been summarized.\n token_count: Number of tokens in ``summary_text``. Tracked so\n the recursive-resummarize fallback can decide when the\n cumulative summary size itself approaches the context\n limit without re-tokenizing.\n created_at: ISO 8601 timestamp recording when this summary was\n produced. Kept as a string (not datetime) to match the\n cache schema convention used elsewhere in the codebase.\n model_used: Fully-qualified model identifier used for the\n summarization LLM call (e.g., ``\"openai/gpt-4o-mini\"``).\n Preserved for audit and for diagnostics when summary\n quality varies between models.", + "description": "A single compaction-produced summary chunk.\n\nAttributes:\n summary_text: The natural-language summary produced by the\n summarization LLM call. Used directly as context for\n subsequent requests (alongside any later summary chunks\n and the buffer of recent turns kept verbatim).\n summarized_through_turn: Running total of conversation items\n consumed by this and all preceding summaries. Used by the\n caller to advance the partition boundary on the next\n compaction so the new summary only covers items that\n have not yet been summarized.\n token_count: Number of tokens in ``summary_text``. Tracked so\n the recursive-resummarize fallback can decide when the\n cumulative summary size itself approaches the context\n limit without re-tokenizing.\n created_at: ISO 8601 timestamp recording when this summary was\n produced. Kept as a string (not datetime) to match the\n cache schema convention used elsewhere in the codebase.\n model_used: Fully-qualified model identifier used for the\n summarization LLM call (e.g., ``\"openai/gpt-5.6-luna\"``).\n Preserved for audit and for diagnostics when summary\n quality varies between models.", "properties": { "summary_text": { "description": "Natural-language summary produced by the summarization LLM call.", @@ -1585,7 +1585,7 @@ def test_dump_models(tmpdir: Path) -> None: "model": { "description": "Model identifier used for this turn", "examples": [ - "gpt-4o-mini" + "gpt-5.6-luna" ], "title": "Model", "type": "string" @@ -2473,7 +2473,7 @@ def test_dump_models(tmpdir: Path) -> None: "minimum": 0, "type": "integer" }, - "description": "Map of fully-qualified model identifier (e.g., \"openai/gpt-4o-mini\") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window \u2014 callers fall back to their own default or skip the token-based trigger.", + "description": "Map of fully-qualified model identifier (e.g., \"openai/gpt-5.6-luna\") to context window size in tokens. Used by the conversation compaction trigger to decide when older turns must be summarized before the input exceeds the window. Models absent from this map have no registered window \u2014 callers fall back to their own default or skip the token-based trigger.", "title": "Per-model context window sizes (tokens)", "type": "object" }, @@ -3519,7 +3519,7 @@ def test_dump_models(tmpdir: Path) -> None: }, { "detail": { - "cause": "Model with ID gpt-4o-mini does not exist", + "cause": "Model with ID gpt-5.6-luna does not exist", "response": "Model not found" }, "label": "model" @@ -5372,7 +5372,7 @@ def test_dump_models(tmpdir: Path) -> None: "examples": [ { "detail": { - "cause": "The input exceeds the context window size of model 'gpt-4o-mini'.", + "cause": "The input exceeds the context window size of model 'gpt-5.6-luna'.", "response": "Context window exceeded" }, "label": "context window exceeded" @@ -6964,7 +6964,7 @@ def test_dump_models(tmpdir: Path) -> None: "generate_topic_summary": true, "input": "Hello World!", "instructions": "You are a helpful assistant", - "model": "openai/gpt-4o-mini", + "model": "openai/gpt-5.6-luna", "store": true, "stream": false } @@ -7497,7 +7497,7 @@ def test_dump_models(tmpdir: Path) -> None: "available_quotas", "output_text" ], - "sse_example": "event: response.created\ndata: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"in_progress\",\"model\":\"openai/gpt-4o-mini\",\"output\":[],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{},\"output_text\":\"\"}}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"sequence_number\":1,\"response_id\":\"resp_abc\",\"output_index\":0,\"item\":{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"in_progress\",\"role\":\"assistant\",\"content\":[]}}\n\n...\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"sequence_number\":30,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"completed\",\"model\":\"openai/gpt-4o-mini\",\"output\":[{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello! How can I help?\",\"annotations\":[]}]}],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"usage\":{\"input_tokens\":10,\"output_tokens\":6,\"total_tokens\":16,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens_details\":{\"reasoning_tokens\":0}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{\"daily\":1000,\"monthly\":50000},\"output_text\":\"Hello! How can I help?\"}}\n\ndata: [DONE]\n\n", + "sse_example": "event: response.created\ndata: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"in_progress\",\"model\":\"openai/gpt-5.6-luna\",\"output\":[],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{},\"output_text\":\"\"}}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"sequence_number\":1,\"response_id\":\"resp_abc\",\"output_index\":0,\"item\":{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"in_progress\",\"role\":\"assistant\",\"content\":[]}}\n\n...\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"sequence_number\":30,\"response\":{\"id\":\"resp_abc\",\"object\":\"response\",\"created_at\":1704067200,\"status\":\"completed\",\"model\":\"openai/gpt-5.6-luna\",\"output\":[{\"id\":\"msg_abc\",\"type\":\"message\",\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello! How can I help?\",\"annotations\":[]}]}],\"store\":true,\"text\":{\"format\":{\"type\":\"text\"}},\"usage\":{\"input_tokens\":10,\"output_tokens\":6,\"total_tokens\":16,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens_details\":{\"reasoning_tokens\":0}},\"conversation\":\"0d21ba731f21f798dc9680125d5d6f49\",\"available_quotas\":{\"daily\":1000,\"monthly\":50000},\"output_text\":\"Hello! How can I help?\"}}\n\ndata: [DONE]\n\n", "title": "ResponsesResponse", "type": "object" }, @@ -8248,7 +8248,7 @@ def test_dump_models(tmpdir: Path) -> None: { "config": { "invalid_question_response": "I can only answer questions about the product.", - "model_id": "openai/gpt-4o-mini", + "model_id": "openai/gpt-5.6-luna", "model_prompt": "Is this question valid?" }, "name": "question-validity", diff --git a/tests/unit/utils/test_compaction.py b/tests/unit/utils/test_compaction.py index 0eda1dcce..300879bad 100644 --- a/tests/unit/utils/test_compaction.py +++ b/tests/unit/utils/test_compaction.py @@ -362,7 +362,7 @@ async def test_returns_conversation_summary_with_populated_fields( ] summary = await summarize_chunk( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", old_items=items, summarized_through_turn=2, encoding_name=DEFAULT_ENCODING_NAME, @@ -373,7 +373,7 @@ async def test_returns_conversation_summary_with_populated_fields( ) assert summary.summarized_through_turn == 2 assert summary.token_count > 0 - assert summary.model_used == "openai/gpt-4o-mini" + assert summary.model_used == "openai/gpt-5.6-luna" assert summary.created_at # non-empty ISO 8601 @pytest.mark.asyncio @@ -388,7 +388,7 @@ async def test_invokes_responses_create_with_store_false( ) await summarize_chunk( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", old_items=[_MessageItem("user", "hi")], summarized_through_turn=1, encoding_name=DEFAULT_ENCODING_NAME, @@ -396,7 +396,7 @@ async def test_invokes_responses_create_with_store_false( kwargs = client.responses.create.call_args.kwargs assert kwargs["store"] is False assert kwargs["stream"] is False - assert kwargs["model"] == "openai/gpt-4o-mini" + assert kwargs["model"] == "openai/gpt-5.6-luna" # System directives travel as `instructions`, transcript as `input`. # This keeps the directives in their own channel (resilient to # prompt-injection from user content) and matches the convention used @@ -413,7 +413,7 @@ async def test_raises_when_llm_returns_empty(self, mocker: MockerFixture) -> Non with pytest.raises(ValueError, match="no extractable text"): await summarize_chunk( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", old_items=[_MessageItem("user", "hi")], summarized_through_turn=1, encoding_name=DEFAULT_ENCODING_NAME, @@ -439,14 +439,14 @@ async def test_additive_second_call_does_not_resummarize_first( first = await summarize_chunk( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", old_items=first_chunk, summarized_through_turn=1, encoding_name=DEFAULT_ENCODING_NAME, ) second = await summarize_chunk( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", old_items=second_chunk, summarized_through_turn=2, encoding_name=DEFAULT_ENCODING_NAME, @@ -478,7 +478,7 @@ def _make_summary(text: str, through: int, tokens: int = 5) -> ConversationSumma summarized_through_turn=through, token_count=tokens, created_at="2026-05-11T00:00:00Z", - model_used="openai/gpt-4o-mini", + model_used="openai/gpt-5.6-luna", ) @@ -499,7 +499,7 @@ async def test_collapses_n_summaries_into_one(self, mocker: MockerFixture) -> No ] folded = await recursively_resummarize( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", summaries=summaries, encoding_name=DEFAULT_ENCODING_NAME, ) @@ -508,7 +508,7 @@ async def test_collapses_n_summaries_into_one(self, mocker: MockerFixture) -> No # new turns were summarized by this call. assert folded.summarized_through_turn == 15 assert folded.token_count > 0 - assert folded.model_used == "openai/gpt-4o-mini" + assert folded.model_used == "openai/gpt-5.6-luna" @pytest.mark.asyncio async def test_prompt_lists_each_summary(self, mocker: MockerFixture) -> None: @@ -524,7 +524,7 @@ async def test_prompt_lists_each_summary(self, mocker: MockerFixture) -> None: ] await recursively_resummarize( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", summaries=summaries, encoding_name=DEFAULT_ENCODING_NAME, ) @@ -547,7 +547,7 @@ async def test_uses_store_false(self, mocker: MockerFixture) -> None: ] await recursively_resummarize( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", summaries=summaries, encoding_name=DEFAULT_ENCODING_NAME, ) @@ -562,7 +562,7 @@ async def test_raises_for_single_summary(self, mocker: MockerFixture) -> None: with pytest.raises(ValueError, match="at least 2 summary chunks"): await recursively_resummarize( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", summaries=[_make_summary("only one", through=5)], encoding_name=DEFAULT_ENCODING_NAME, ) @@ -574,7 +574,7 @@ async def test_raises_for_empty_list(self, mocker: MockerFixture) -> None: with pytest.raises(ValueError, match="at least 2 summary chunks"): await recursively_resummarize( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", summaries=[], encoding_name=DEFAULT_ENCODING_NAME, ) @@ -591,7 +591,7 @@ async def test_raises_when_llm_returns_empty(self, mocker: MockerFixture) -> Non with pytest.raises(ValueError, match="no extractable text"): await recursively_resummarize( client=client, - model="openai/gpt-4o-mini", + model="openai/gpt-5.6-luna", summaries=summaries, encoding_name=DEFAULT_ENCODING_NAME, ) diff --git a/tests/unit/utils/test_conversation_compaction.py b/tests/unit/utils/test_conversation_compaction.py index 6756e3b76..e689208e6 100644 --- a/tests/unit/utils/test_conversation_compaction.py +++ b/tests/unit/utils/test_conversation_compaction.py @@ -17,7 +17,7 @@ from models.config import CompactionConfiguration, InferenceConfiguration from utils import conversation_compaction as cc -MODEL = "openai/gpt-4o-mini" +MODEL = "openai/gpt-5.6-luna" CONV = "conv_abc123" diff --git a/tests/unit/utils/test_otel_tracing.py b/tests/unit/utils/test_otel_tracing.py index fc916139a..a88ded9e4 100644 --- a/tests/unit/utils/test_otel_tracing.py +++ b/tests/unit/utils/test_otel_tracing.py @@ -188,7 +188,7 @@ def test_set_multiple_attributes(self, otel: Generator[Any, Any, Any]) -> None: span, { SpanAttributes.USER_ID: "user-456", - SpanAttributes.LLM_MODEL_ID: "gpt-4o-mini", + SpanAttributes.LLM_MODEL_ID: "gpt-5.6-luna", SpanAttributes.LLM_USAGE_INPUT_TOKENS: 100, SpanAttributes.LLM_USAGE_OUTPUT_TOKENS: 50, }, @@ -198,7 +198,7 @@ def test_set_multiple_attributes(self, otel: Generator[Any, Any, Any]) -> None: assert len(spans) == 1 attrs = spans[0].attributes assert attrs[SpanAttributes.USER_ID] == "user-456" - assert attrs[SpanAttributes.LLM_MODEL_ID] == "gpt-4o-mini" + assert attrs[SpanAttributes.LLM_MODEL_ID] == "gpt-5.6-luna" assert attrs[SpanAttributes.LLM_USAGE_INPUT_TOKENS] == 100 assert attrs[SpanAttributes.LLM_USAGE_OUTPUT_TOKENS] == 50 diff --git a/tests/unit/utils/test_responses.py b/tests/unit/utils/test_responses.py index 4e178c674..a3681e1d8 100644 --- a/tests/unit/utils/test_responses.py +++ b/tests/unit/utils/test_responses.py @@ -12,6 +12,7 @@ from ogx_api.openai_responses import ( AllowedToolsFilter, OpenAIResponseInputToolChoiceAllowedTools, + OpenAIResponseMessage, OpenAIResponseReasoning, ) from ogx_api.openai_responses import ApprovalFilter as OgxApprovalFilter @@ -67,6 +68,7 @@ InferenceConfiguration, ModelContextProtocolServer, RagStore, + SkillsConfiguration, ) from tests.unit.conftest import ( make_openai_model, @@ -84,6 +86,7 @@ build_tool_call_summary, build_tool_result_from_mcp_output_item_done, extract_rag_chunks_from_file_search_item, + extract_shield_moderation_text, extract_text_from_response_item, extract_text_from_response_items, extract_token_usage, @@ -91,6 +94,7 @@ filter_tools_by_allowed_entries, get_mcp_tools, get_rag_tools, + get_shield_moderation_input, get_topic_summary, is_server_deployed_output, maybe_get_topic_summary, @@ -381,6 +385,91 @@ def test_extract_text_from_response_items_with_list_content(self) -> None: assert result == "Part 1 Part 2" +class TestGetShieldModerationInput: + """Test cases for get_shield_moderation_input windowing.""" + + @staticmethod + def _msg( + role: str, content: str, *, item_id: Optional[str] = None + ) -> OpenAIResponseMessage: + """Build a real OpenAIResponseMessage for windowing tests.""" + return OpenAIResponseMessage( + role=cast("Any", role), content=content, id=item_id + ) + + def test_flag_false_returns_full_input(self) -> None: + """When after_last_refusal is False, return the full input unchanged.""" + items = [ + self._msg("user", "blocked"), + self._msg("assistant", "refusal", item_id="modr-1"), + self._msg("user", "clean"), + ] + assert get_shield_moderation_input(items, after_last_refusal=False) is items + + def test_string_input_unchanged(self) -> None: + """String input is returned unchanged even when the flag is set.""" + assert get_shield_moderation_input("hello", after_last_refusal=True) == "hello" + + def test_no_modr_item_returns_full_list(self) -> None: + """Without a modr- item, return the full list when the flag is set.""" + items = [ + self._msg("user", "one"), + self._msg("assistant", "two", item_id="resp_1"), + self._msg("user", "three"), + ] + assert get_shield_moderation_input(items, after_last_refusal=True) is items + + def test_windows_after_last_modr_item(self) -> None: + """Return only items after the last modr- refusal when the flag is set.""" + items = [ + self._msg("user", "blocked"), + self._msg("assistant", "refusal", item_id="modr-1"), + self._msg("user", "clean follow-up"), + ] + window = get_shield_moderation_input(items, after_last_refusal=True) + assert isinstance(window, list) + assert len(window) == 1 + assert extract_text_from_response_items(window) == "clean follow-up" + + def test_uses_last_modr_when_multiple(self) -> None: + """When multiple modr- items exist, window after the last one.""" + items = [ + self._msg("user", "bad-1"), + self._msg("assistant", "refusal-1", item_id="modr-1"), + self._msg("user", "bad-2"), + self._msg("assistant", "refusal-2", item_id="modr-2"), + self._msg("user", "clean"), + ] + window = get_shield_moderation_input(items, after_last_refusal=True) + assert isinstance(window, list) + assert len(window) == 1 + assert extract_text_from_response_items(window) == "clean" + + def test_empty_suffix_when_modr_is_last(self) -> None: + """When the last item is a modr- refusal, return an empty list.""" + items = [ + self._msg("user", "blocked"), + self._msg("assistant", "refusal", item_id="modr-1"), + ] + window = get_shield_moderation_input(items, after_last_refusal=True) + assert not window + + def test_extract_shield_moderation_text_windows_suffix(self) -> None: + """extract_shield_moderation_text returns text only from the windowed suffix.""" + items = [ + self._msg("user", "blocked earlier"), + self._msg("assistant", "refusal", item_id="modr-1"), + self._msg("user", "What is Kubernetes?"), + ] + text = extract_shield_moderation_text(items, after_last_refusal=True) + assert text.startswith("What is Kubernetes?") + assert "blocked earlier" not in text + + def test_extract_shield_moderation_text_string_input(self) -> None: + """String input is moderated as-is (plus empty attachments suffix).""" + assert extract_shield_moderation_text("hello") == "hello\n\n" + + class TestGetRAGTools: """Test cases for get_rag_tools utility function.""" @@ -1194,9 +1283,9 @@ def test_gpt_5_chat_no_reasoning(self) -> None: """gpt-5-chat does not use reasoning.""" assert model_reasoning_enabled_by_default("openai/gpt-5-chat-latest") is False - def test_gpt_4o_mini(self) -> None: - """gpt-4o-mini does not use reasoning.""" - assert model_reasoning_enabled_by_default("openai/gpt-4o-mini") is False + def test_gpt_5_6_luna_default_on(self) -> None: + """gpt-5.6-luna reasons by default when reasoning is omitted.""" + assert model_reasoning_enabled_by_default("openai/gpt-5.6-luna") is True class TestApplyReasoningForResolvedTools: @@ -1204,7 +1293,7 @@ class TestApplyReasoningForResolvedTools: _DEFAULT_ON_MODEL = "openai/gpt-5.6-terra" _OPT_IN_MODEL = "openai/gpt-5.4" - _NON_REASONING_MODEL = "openai/gpt-4o-mini" + _NON_REASONING_MODEL = "openai/gpt-5-chat-latest" @staticmethod def _sample_tools() -> list[InputTool]: @@ -1217,14 +1306,16 @@ def test_preserves_reasoning_when_no_tools(self) -> None: """Keep caller reasoning when the resolved request has no tools.""" reasoning = OpenAIResponseReasoning(effort="high") assert ( - apply_reasoning_for_resolved_tools(reasoning, None, self._DEFAULT_ON_MODEL) + apply_reasoning_for_resolved_tools( + reasoning, None, self._DEFAULT_ON_MODEL, None + ) == reasoning ) def test_preserves_none_reasoning_when_no_tools(self) -> None: """Leave reasoning unset when no tools are resolved.""" assert ( - apply_reasoning_for_resolved_tools(None, None, self._DEFAULT_ON_MODEL) + apply_reasoning_for_resolved_tools(None, None, self._DEFAULT_ON_MODEL, None) is None ) @@ -1232,7 +1323,7 @@ def test_skips_reasoning_for_non_reasoning_model_with_tools(self) -> None: """Do not inject reasoning for models that do not use it.""" assert ( apply_reasoning_for_resolved_tools( - None, self._sample_tools(), self._NON_REASONING_MODEL + None, self._sample_tools(), self._NON_REASONING_MODEL, None ) is None ) @@ -1241,7 +1332,7 @@ def test_skips_reasoning_for_opt_in_model_with_tools(self) -> None: """Do not inject reasoning for opt-in models that default to off.""" assert ( apply_reasoning_for_resolved_tools( - None, self._sample_tools(), self._OPT_IN_MODEL + None, self._sample_tools(), self._OPT_IN_MODEL, None ) is None ) @@ -1249,7 +1340,7 @@ def test_skips_reasoning_for_opt_in_model_with_tools(self) -> None: def test_sets_none_effort_when_tools_present_and_reasoning_missing(self) -> None: """Default to explicit effort none when tools are present.""" result = apply_reasoning_for_resolved_tools( - None, self._sample_tools(), self._DEFAULT_ON_MODEL + None, self._sample_tools(), self._DEFAULT_ON_MODEL, None ) assert result is not None assert result.effort == "none" @@ -1260,7 +1351,7 @@ def test_forces_none_effort_when_tools_present_and_reasoning_requested( """Override non-none reasoning effort when tools are present.""" reasoning = OpenAIResponseReasoning(effort="medium") result = apply_reasoning_for_resolved_tools( - reasoning, self._sample_tools(), self._DEFAULT_ON_MODEL + reasoning, self._sample_tools(), self._DEFAULT_ON_MODEL, None ) assert result is not None assert result.effort == "none" @@ -1269,13 +1360,54 @@ def test_keeps_none_effort_when_tools_present(self) -> None: """Preserve other reasoning fields when forcing effort to none.""" reasoning = OpenAIResponseReasoning(effort="none", summary="concise") result = apply_reasoning_for_resolved_tools( - reasoning, self._sample_tools(), self._DEFAULT_ON_MODEL + reasoning, self._sample_tools(), self._DEFAULT_ON_MODEL, None ) assert result is not None assert result is not reasoning assert result.effort == "none" assert result.summary == "concise" + def test_sets_none_effort_when_skills_configured_without_resolved_tools( + self, + ) -> None: + """Skills configuration counts as tool-bearing for reasoning-default models.""" + skills = SkillsConfiguration(paths=[Path("/app-root/skills/echo")]) + result = apply_reasoning_for_resolved_tools( + None, + None, + "openai/gpt-5.6-luna", + skills, + ) + assert result is not None + assert result.effort == "none" + + def test_skips_reasoning_for_skills_on_non_reasoning_model(self) -> None: + """Skills configuration alone does not affect non-reasoning models.""" + skills = SkillsConfiguration(paths=[Path("/app-root/skills/echo")]) + assert ( + apply_reasoning_for_resolved_tools( + None, + None, + self._NON_REASONING_MODEL, + skills, + ) + is None + ) + + def test_sets_none_effort_when_skills_configuration_object_has_empty_paths( + self, + ) -> None: + """Any non-None skills configuration object triggers the tools path.""" + skills = SkillsConfiguration() + result = apply_reasoning_for_resolved_tools( + None, + None, + self._DEFAULT_ON_MODEL, + skills, + ) + assert result is not None + assert result.effort == "none" + class TestResolveToolChoice: """Tests for resolve_tool_choice (ToolChoiceMode, AllowedTools, explicit/implicit tools).""" @@ -2424,6 +2556,44 @@ async def test_prepare_responses_params_sets_reasoning_none_for_default_on_model assert result.reasoning is not None assert result.reasoning.effort == "none" + @pytest.mark.asyncio + async def test_prepare_responses_params_sets_reasoning_none_when_skills_configured( + self, mocker: MockerFixture + ) -> None: + """prepare_responses_params forwards configuration.skills into reasoning logic.""" + mock_client = mock_async_ogx_client(mocker) + mock_client.openai.list = mocker.AsyncMock( + return_value=make_openai_models_list_response( + make_openai_model( + model_id="openai/gpt-5.6-luna", + provider_id="openai", + model_type="llm", + ) + ) + ) + + mock_conversation = mocker.Mock() + mock_conversation.id = "new_conv_id" + mock_client.conversations.create = mocker.AsyncMock( + return_value=mock_conversation + ) + + query_request = QueryRequest(query="test") # pyright: ignore[reportCallIssue] + mock_config = mocker.Mock() + mock_config.inference = InferenceConfiguration() + mock_config.skills = SkillsConfiguration(paths=[Path("/app-root/skills/echo")]) + mocker.patch("utils.responses.configuration", mock_config) + mocker.patch("utils.responses.get_system_prompt", return_value="System prompt") + mocker.patch("utils.responses.prepare_tools", return_value=None) + mocker.patch("utils.responses.prepare_input", return_value="test") + + result = await prepare_responses_params( + mock_client, query_request, None, "token" + ) + + assert result.reasoning is not None + assert result.reasoning.effort == "none" + @pytest.mark.asyncio async def test_prepare_responses_params_omits_reasoning_for_non_reasoning_model( self, mocker: MockerFixture @@ -2433,7 +2603,7 @@ async def test_prepare_responses_params_omits_reasoning_for_non_reasoning_model( mock_client.openai.list = mocker.AsyncMock( return_value=make_openai_models_list_response( make_openai_model( - model_id="openai/gpt-4o-mini", + model_id="openai/gpt-5-chat-latest", provider_id="openai", model_type="llm", ) diff --git a/tests/unit/utils/test_shields.py b/tests/unit/utils/test_shields.py index 2cc9bf964..f02f1915d 100644 --- a/tests/unit/utils/test_shields.py +++ b/tests/unit/utils/test_shields.py @@ -157,6 +157,7 @@ async def test_returns_blocked_on_first_block(self, mocker: MockerFixture) -> No assert isinstance(result, ShieldModerationBlocked) assert result.message == "rejected" + assert result.refusal_response.id == "modr-123" mock_shield.run.assert_called_once() @pytest.mark.asyncio @@ -223,7 +224,7 @@ async def test_raise_429_when_exceeds_quota(self, mocker: MockerFixture) -> None """Raise HTTP 429 when a shield raises ModelHTTPError with status 429.""" mock_shield = mocker.Mock() mock_shield.run = mocker.AsyncMock( - side_effect=ModelHTTPError(429, "openai/gpt-4o-mini", "Quota exceeded") + side_effect=ModelHTTPError(429, "openai/gpt-5.6-luna", "Quota exceeded") ) mocker.patch("utils.shields.build_shield", return_value=mock_shield) @@ -242,7 +243,7 @@ async def test_raise_413_when_exceeds_context_length( mock_shield = mocker.Mock() mock_shield.run = mocker.AsyncMock( side_effect=ModelHTTPError( - 413, "openai/gpt-4o-mini", "Context length exceeded" + 413, "openai/gpt-5.6-luna", "Context length exceeded" ) ) mocker.patch("utils.shields.build_shield", return_value=mock_shield) diff --git a/tests/unit/utils/test_token_estimator.py b/tests/unit/utils/test_token_estimator.py index fe62157bf..f4d95fb5a 100644 --- a/tests/unit/utils/test_token_estimator.py +++ b/tests/unit/utils/test_token_estimator.py @@ -228,18 +228,18 @@ class TestGetContextWindow: def test_returns_size_for_known_model(self) -> None: """A configured model returns its window size.""" config = InferenceConfiguration( - context_windows={"openai/gpt-4o-mini": 128000}, + context_windows={"openai/gpt-5.6-luna": 128000}, ) # pyright: ignore[reportCallIssue] - assert get_context_window("openai/gpt-4o-mini", config) == 128000 + assert get_context_window("openai/gpt-5.6-luna", config) == 128000 def test_returns_none_for_unknown_model(self) -> None: """An unconfigured model returns None — caller decides fallback.""" config = InferenceConfiguration( - context_windows={"openai/gpt-4o-mini": 128000}, + context_windows={"openai/gpt-5.6-luna": 128000}, ) # pyright: ignore[reportCallIssue] assert get_context_window("anthropic/claude-3", config) is None def test_returns_none_when_map_empty(self) -> None: """An empty context_windows dict returns None for any model.""" config = InferenceConfiguration() # pyright: ignore[reportCallIssue] - assert get_context_window("openai/gpt-4o-mini", config) is None + assert get_context_window("openai/gpt-5.6-luna", config) is None