diff --git a/.env.example b/.env.example index 942be36a..db9da30a 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ OPENAI_API_KEY=sk-... ORGMEMORY_AI_MODEL_CHAT=openai ORGMEMORY_AI_MODEL_EMBEDDING=openai +ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT= ORGMEMORY_ASSISTANT_RETRIEVAL_ENGINE=GRAPH_RAG ORGMEMORY_OPENAI_BASE_URL=https://api.openai.com/v1 ORGMEMORY_OPENAI_MODEL=gpt-5.6-sol diff --git a/.tegami/2026-08-06-assistant-chat-reasoning-effort.md b/.tegami/2026-08-06-assistant-chat-reasoning-effort.md new file mode 100644 index 00000000..3109b97c --- /dev/null +++ b/.tegami/2026-08-06-assistant-chat-reasoning-effort.md @@ -0,0 +1,11 @@ +--- +packages: + orgmemory: patch +subject: Keep Assistant tool calls compatible with OpenAI +--- + +## Fixes + +Fresh production deployments now set Answer reasoning to `none` so the +Assistant's governed Skill tools work with `gpt-5.6-sol` on OpenAI Chat +Completions without requiring an organization route workaround. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b1620bb0..d8eb4078 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -403,8 +403,8 @@ extraction defaults to `gpt-5.4-mini`; the `ORGMEMORY_GRAPH_EXTRACTION_MODEL` deployment override is independent from the Assistant model. New Graph jobs pin reasoning effort in schema-v2 processing profiles while persisted schema-v1 bytes and hashes remain executable. -The verified ZM production route uses `gpt-5.6-sol` for Answer, -`gpt-5.6-luna` with reasoning `none` for Keyword Planning, and +The verified ZM production route uses `gpt-5.6-sol` with reasoning `none` for +Answer, `gpt-5.6-luna` with reasoning `none` for Keyword Planning, and `gpt-5.4-mini` with provider-default reasoning for Graph Extraction. Immutable Knowledge Asset embedding profiles still pin the provider/model used by derived indexes. The default diff --git a/apps/api/src/main/resources/application.yml b/apps/api/src/main/resources/application.yml index 3b6e9cbb..188f6ea9 100644 --- a/apps/api/src/main/resources/application.yml +++ b/apps/api/src/main/resources/application.yml @@ -103,6 +103,7 @@ orgmemory: assistant-chat: gateway-id: ${ORGMEMORY_ASSISTANT_GATEWAY:openai} model-id: ${ORGMEMORY_OPENAI_MODEL:gpt-5.6-sol} + open-ai-reasoning-effort: ${ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT:} keyword-planning: gateway-id: ${ORGMEMORY_KEYWORD_GATEWAY:${ORGMEMORY_ASSISTANT_GATEWAY:openai}} model-id: ${ORGMEMORY_KEYWORD_MODEL:${ORGMEMORY_OPENAI_MODEL:gpt-5.6-sol}} diff --git a/apps/api/src/test/java/com/orgmemory/api/security/ProductionAiGatewayConfigurationBindingTests.java b/apps/api/src/test/java/com/orgmemory/api/security/ProductionAiGatewayConfigurationBindingTests.java index 4bcbfe64..7abaee37 100644 --- a/apps/api/src/test/java/com/orgmemory/api/security/ProductionAiGatewayConfigurationBindingTests.java +++ b/apps/api/src/test/java/com/orgmemory/api/security/ProductionAiGatewayConfigurationBindingTests.java @@ -47,6 +47,33 @@ void prodCredentialOverrideRetainsTheCompleteOpenAiGatewayDefinition() { }); } + @Test + void prodAssistantRouteUsesExplicitNoneReasoningEffort() { + new ApplicationContextRunner() + .withInitializer(new ConfigDataApplicationContextInitializer()) + .withUserConfiguration(AiModelGatewayConfiguration.class) + .withSystemProperties( + "OPENAI_API_KEY=redacted-base-key", + "ORGMEMORY_OPENAI_API_KEY=redacted-prod-key", + "ORGMEMORY_OPENAI_REASONING_EFFORT_SUPPORTED=true", + "ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT=none") + .withPropertyValues("spring.profiles.active=prod") + .run(context -> { + assertNull(context.getStartupFailure()); + assertEquals( + "none", + context.getEnvironment().getProperty( + "orgmemory.ai.routes.assistant-chat.open-ai-reasoning-effort")); + + AiGatewayProperties properties = + context.getBean(AiGatewayProperties.class); + assertEquals( + OpenAiReasoningEffort.NONE, + properties.route(AiWorkload.ASSISTANT_CHAT) + .openAiReasoningEffort()); + }); + } + @Test void prodKeywordRouteRetainsItsIndependentDeploymentModel() { new ApplicationContextRunner() diff --git a/apps/worker/src/main/resources/application.yml b/apps/worker/src/main/resources/application.yml index 15199f50..a1955790 100644 --- a/apps/worker/src/main/resources/application.yml +++ b/apps/worker/src/main/resources/application.yml @@ -78,6 +78,7 @@ orgmemory: assistant-chat: gateway-id: ${ORGMEMORY_ASSISTANT_GATEWAY:openai} model-id: ${ORGMEMORY_OPENAI_MODEL:gpt-5.6-sol} + open-ai-reasoning-effort: ${ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT:} keyword-planning: gateway-id: ${ORGMEMORY_KEYWORD_GATEWAY:${ORGMEMORY_ASSISTANT_GATEWAY:openai}} model-id: ${ORGMEMORY_KEYWORD_MODEL:${ORGMEMORY_OPENAI_MODEL:gpt-5.6-sol}} diff --git a/apps/worker/src/test/java/com/orgmemory/worker/ProductionAiGatewayConfigurationBindingTests.java b/apps/worker/src/test/java/com/orgmemory/worker/ProductionAiGatewayConfigurationBindingTests.java index b6bd48ae..c8131364 100644 --- a/apps/worker/src/test/java/com/orgmemory/worker/ProductionAiGatewayConfigurationBindingTests.java +++ b/apps/worker/src/test/java/com/orgmemory/worker/ProductionAiGatewayConfigurationBindingTests.java @@ -45,6 +45,33 @@ void prodCredentialOverrideRetainsTheCompleteOpenAiGatewayDefinition() { }); } + @Test + void prodAssistantRouteUsesExplicitNoneReasoningEffort() { + new ApplicationContextRunner() + .withInitializer(new ConfigDataApplicationContextInitializer()) + .withUserConfiguration(AiModelGatewayConfiguration.class) + .withSystemProperties( + "OPENAI_API_KEY=redacted-base-key", + "ORGMEMORY_OPENAI_API_KEY=redacted-prod-key", + "ORGMEMORY_OPENAI_REASONING_EFFORT_SUPPORTED=true", + "ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT=none") + .withPropertyValues("spring.profiles.active=prod") + .run(context -> { + assertNull(context.getStartupFailure()); + assertEquals( + "none", + context.getEnvironment().getProperty( + "orgmemory.ai.routes.assistant-chat.open-ai-reasoning-effort")); + + AiGatewayProperties properties = + context.getBean(AiGatewayProperties.class); + assertEquals( + OpenAiReasoningEffort.NONE, + properties.route(AiWorkload.ASSISTANT_CHAT) + .openAiReasoningEffort()); + }); + } + @Test void prodKeywordRouteRetainsItsIndependentDeploymentModel() { new ApplicationContextRunner() diff --git a/docs/specs/domains/ai-model-control-plane.md b/docs/specs/domains/ai-model-control-plane.md index c07b9d1b..763e2530 100644 --- a/docs/specs/domains/ai-model-control-plane.md +++ b/docs/specs/domains/ai-model-control-plane.md @@ -5,7 +5,7 @@ Source: `core/src/main/java/com/orgmemory/core/ai`, API/worker `application*.yml`, and `apps/web/src/features/admin/components/admin-language-models-page.tsx`. -Reconciled: `2026-08-04-assistant-composer-model-picker (2e5907b1)`. +Reconciled: `2026-08-06-assistant-chat-reasoning-effort (c3da6b25)`. ## Current Behavior @@ -88,10 +88,12 @@ enqueued jobs; it neither starts reindexing nor changes queued/completed jobs. The fixed live evaluation approved `gpt-5.6-luna` with reasoning `none` for Keyword Planning but rejected it for Graph Extraction. Graph therefore retains -`gpt-5.4-mini`; Answer retains `gpt-5.6-sol`. ZM production and the production -Compose defaults now use that evaluated split. The general development -configuration remains capability-off by default because an arbitrary custom -OpenAI-compatible endpoint has not proved `reasoning_effort` support. +`gpt-5.4-mini`; Answer retains `gpt-5.6-sol` with explicit reasoning `none` so +its fixed function tools remain valid on OpenAI Chat Completions. ZM production +and the production Compose defaults use that route split. The general +development configuration remains capability-off and leaves route effort empty +by default because an arbitrary custom OpenAI-compatible endpoint has not +proved `reasoning_effort` support. Index Settings is a separate read-only surface. The embedding provider, model, dimensions, and cosine metric cannot be mutated through the chat control plane; diff --git a/docs/tests/domains/ai-model-control-plane.md b/docs/tests/domains/ai-model-control-plane.md index 51356d82..43143c73 100644 --- a/docs/tests/domains/ai-model-control-plane.md +++ b/docs/tests/domains/ai-model-control-plane.md @@ -8,7 +8,7 @@ Source: `core/src/test/java/com/orgmemory/core/ai`, `apps/web/src/features/admin/components/provider-logo.test.tsx`, `apps/web/test/e2e/admin-language-models.spec.ts`, and the admin web build. -Reconciled: `2026-08-04-assistant-composer-model-picker (2e5907b1)`. +Reconciled: `2026-08-06-assistant-chat-reasoning-effort (c3da6b25)`. | Behavior | Evidence | Status | | --- | --- | --- | @@ -36,5 +36,5 @@ Reconciled: `2026-08-04-assistant-composer-model-picker (2e5907b1)`. | Read-only Index Settings compiles as a production route | web lint, typecheck, and build | covered | | Keyword is editable, Graph is visible/read-only with future-jobs-only copy, and the backend rejects Graph mutation | `admin-language-models.spec.ts`, `AiGatewayAdministrationServiceTests` | covered | | Fixed bilingual live evaluation records validity, recall/yield, failures, and p95 without raw prompts/evidence; Keyword Luna passes and Graph Luna fails independently | `evaluation/tests/test_workload_routing_runner.py`, increment `evaluation-result.json` | covered | -| Production defaults and the shared-ZM export preserve the approved Answer/Keyword/Graph split and explicit Keyword reasoning through the bound API/worker runtime properties | API/worker `ProductionAiGatewayConfigurationBindingTests`, production Compose validation, `test-export-team-dev-config.sh`, ZM runtime environment inspection | covered and operator verified | +| Production defaults preserve the approved Answer/Keyword/Graph split, explicit Answer and Keyword reasoning `none`, and provider-default Graph reasoning through the bound API/worker runtime properties | API/worker `ProductionAiGatewayConfigurationBindingTests`, production Compose validation, ZM runtime environment inspection | covered and operator verified | | Live provider credentials/model responses | no deterministic CI credential | operator verification required | diff --git a/infrastructure/deployment/compose.production.yaml b/infrastructure/deployment/compose.production.yaml index 65cd222c..21df7afb 100644 --- a/infrastructure/deployment/compose.production.yaml +++ b/infrastructure/deployment/compose.production.yaml @@ -65,6 +65,7 @@ x-spring-environment: &spring-environment ORGMEMORY_OPENAI_MODEL: ${ORGMEMORY_OPENAI_MODEL:-gpt-5.6-sol} ORGMEMORY_KEYWORD_MODEL: ${ORGMEMORY_KEYWORD_MODEL:-gpt-5.6-luna} ORGMEMORY_OPENAI_REASONING_EFFORT_SUPPORTED: ${ORGMEMORY_OPENAI_REASONING_EFFORT_SUPPORTED:-true} + ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT: ${ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT:-none} ORGMEMORY_KEYWORD_OPENAI_REASONING_EFFORT: ${ORGMEMORY_KEYWORD_OPENAI_REASONING_EFFORT:-none} ORGMEMORY_GRAPH_QUERY_KEYWORD_CACHE_TTL: ${ORGMEMORY_GRAPH_QUERY_KEYWORD_CACHE_TTL:-24h} ORGMEMORY_GRAPH_QUERY_MAXIMUM_CONCURRENT_SPACES: ${ORGMEMORY_GRAPH_QUERY_MAXIMUM_CONCURRENT_SPACES:-4} diff --git a/infrastructure/deployment/production.env.example b/infrastructure/deployment/production.env.example index efc0c457..18f1e141 100644 --- a/infrastructure/deployment/production.env.example +++ b/infrastructure/deployment/production.env.example @@ -75,6 +75,7 @@ ORGMEMORY_KEYWORD_MODEL=gpt-5.6-luna # The official OpenAI gateway was production-verified with reasoning_effort. # Custom compatible gateways must independently prove the same capability. ORGMEMORY_OPENAI_REASONING_EFFORT_SUPPORTED=true +ORGMEMORY_ASSISTANT_OPENAI_REASONING_EFFORT=none ORGMEMORY_KEYWORD_OPENAI_REASONING_EFFORT=none ORGMEMORY_GRAPH_EXTRACTION_OPENAI_REASONING_EFFORT= ORGMEMORY_GRAPH_QUERY_KEYWORD_CACHE_TTL=24h