diff --git a/.github/workflows/e2e_tests_providers.yaml b/.github/workflows/e2e_tests_providers.yaml index 284ccb87f8..eeb754bbce 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/.konflux/build-args-konflux.conf b/.konflux/build-args-konflux.conf index 7d2b9202c1..3f4756a754 100644 --- a/.konflux/build-args-konflux.conf +++ b/.konflux/build-args-konflux.conf @@ -1,4 +1,4 @@ -BUILDER_BASE_IMAGE=quay.io/aipcc/base-images/cpu:3.5.2-1790703656 +BUILDER_BASE_IMAGE=quay.io/aipcc/base-images/cpu:3.5.5-1793018691 BUILDER_DNF_COMMAND=dnf -RUNTIME_BASE_IMAGE=quay.io/aipcc/base-images/cpu:3.5.2-1790703656 +RUNTIME_BASE_IMAGE=quay.io/aipcc/base-images/cpu:3.5.5-1793018691 RUNTIME_DNF_COMMAND=dnf diff --git a/.konflux/profiles.toml b/.konflux/profiles.toml index 62220c5c8c..a7890c9e54 100644 --- a/.konflux/profiles.toml +++ b/.konflux/profiles.toml @@ -1,12 +1,12 @@ [common] python_version = "3.12" platforms = ["x86_64", "aarch64"] -bootstrap_packages = ["maturin", "pip", "uv"] +bootstrap_packages = ["maturin", "uv", "pip"] [profiles.cpu] rhoai_index_url = "https://packages.redhat.com/api/pypi/public-rhai/rhoai/3.5/cpu-ubi9/simple/" output_suffix = "" tekton_files = [ - ".tekton/lightspeed-stack-0-8-pull-request.yaml", - ".tekton/lightspeed-stack-0-8-push.yaml", + ".tekton/lightspeed-stack-0-7-pull-request.yaml", + ".tekton/lightspeed-stack-0-7-push.yaml", ] diff --git a/.konflux/redhat.repo b/.konflux/redhat.repo index a8c6c65c31..334871f20a 100644 --- a/.konflux/redhat.repo +++ b/.konflux/redhat.repo @@ -11,6 +11,7 @@ metadata_expire = 86400 enabled_metadata = 0 sslclientkey = $SSL_CLIENT_KEY sslclientcert = $SSL_CLIENT_CERT +excludepkgs=*.i686 [rhel-9-for-$basearch-appstream-eus-rpms__9_DOT_6] name = Red Hat Enterprise Linux 9 for $basearch - AppStream - Extended Update Support (RPMs) @@ -25,6 +26,7 @@ metadata_expire = 86400 enabled_metadata = 0 sslclientkey = $SSL_CLIENT_KEY sslclientcert = $SSL_CLIENT_CERT +excludepkgs=*.i686 [rhel-9-for-$basearch-baseos-eus-rpms__9_DOT_6] name = Red Hat Enterprise Linux 9 for $basearch - BaseOS - Extended Update Support (RPMs) @@ -52,4 +54,4 @@ sslverifystatus = 1 metadata_expire = 86400 enabled_metadata = 0 sslclientkey = $SSL_CLIENT_KEY -sslclientcert = $SSL_CLIENT_CERT \ No newline at end of file +sslclientcert = $SSL_CLIENT_CERT diff --git a/.konflux/rpms.in.yaml b/.konflux/rpms.in.yaml index b841aa2a86..71821d62e3 100644 --- a/.konflux/rpms.in.yaml +++ b/.konflux/rpms.in.yaml @@ -9,8 +9,6 @@ packages: ] upgradePackages: [ - python3, - python3-libs, ] contentOrigin: repofiles: ["./redhat.repo"] diff --git a/.konflux/rpms.lock.yaml b/.konflux/rpms.lock.yaml index 98deeff703..587ab4a742 100644 --- a/.konflux/rpms.lock.yaml +++ b/.konflux/rpms.lock.yaml @@ -74,20 +74,6 @@ arches: name: info evr: 6.7-15.el9 sourcerpm: texinfo-6.7-15.el9.src.rpm - - url: https://cdn.redhat.com/content/eus/rhel9/9.6/aarch64/baseos/os/Packages/p/python3-3.9.21-2.el9_6.7.aarch64.rpm - repoid: rhel-9-for-aarch64-baseos-eus-rpms__9_DOT_6 - size: 25259 - checksum: sha256:8da6c570a5e33d89dd40077fbd85bf0c719fbfa9db07f0a22c3fec82429921f8 - name: python3 - evr: 3.9.21-2.el9_6.7 - sourcerpm: python3.9-3.9.21-2.el9_6.7.src.rpm - - url: https://cdn.redhat.com/content/eus/rhel9/9.6/aarch64/baseos/os/Packages/p/python3-libs-3.9.21-2.el9_6.7.aarch64.rpm - repoid: rhel-9-for-aarch64-baseos-eus-rpms__9_DOT_6 - size: 8462381 - checksum: sha256:57c86796858fff48efec988106d85c7b3e3ec63336f20d1d666e9756dfa988c0 - name: python3-libs - evr: 3.9.21-2.el9_6.7 - sourcerpm: python3.9-3.9.21-2.el9_6.7.src.rpm source: [] module_metadata: [] - arch: x86_64 @@ -162,19 +148,5 @@ arches: name: info evr: 6.7-15.el9 sourcerpm: texinfo-6.7-15.el9.src.rpm - - url: https://cdn.redhat.com/content/eus/rhel9/9.6/x86_64/baseos/os/Packages/p/python3-3.9.21-2.el9_6.7.x86_64.rpm - repoid: rhel-9-for-x86_64-baseos-eus-rpms__9_DOT_6 - size: 25312 - checksum: sha256:cc08460dae8a0833113110e2a8a38d2ede7c61142e580de8c0b697a3b0a5607a - name: python3 - evr: 3.9.21-2.el9_6.7 - sourcerpm: python3.9-3.9.21-2.el9_6.7.src.rpm - - url: https://cdn.redhat.com/content/eus/rhel9/9.6/x86_64/baseos/os/Packages/p/python3-libs-3.9.21-2.el9_6.7.x86_64.rpm - repoid: rhel-9-for-x86_64-baseos-eus-rpms__9_DOT_6 - size: 8473456 - checksum: sha256:0dd8473784ef930055b72de89db1d7d484134a02157ee46dbb9cfed42f0f5835 - name: python3-libs - evr: 3.9.21-2.el9_6.7 - sourcerpm: python3.9-3.9.21-2.el9_6.7.src.rpm source: [] module_metadata: [] diff --git a/.tekton/integration-tests/pipeline/lightspeed-stack-integration-test.yaml b/.tekton/integration-tests/pipeline/lightspeed-stack-integration-test.yaml index 54be0647b7..e3516e91ba 100644 --- a/.tekton/integration-tests/pipeline/lightspeed-stack-integration-test.yaml +++ b/.tekton/integration-tests/pipeline/lightspeed-stack-integration-test.yaml @@ -2,17 +2,16 @@ apiVersion: tekton.dev/v1beta1 kind: Pipeline metadata: - name: lightspeed-stack-0-8-integration-tests-pipeline + name: lightspeed-stack-0-7-integration-tests-pipeline spec: description: | This pipeline automates the process of running end-to-end tests for Lightspeed Stack - using a ROSA (Red Hat OpenShift Service on AWS cluster. The pipeline provisions - the ROSA cluster, installs the Lightspeed Stack, runs the tests, collects artifacts, - and finally deprovisions the ROSA cluster. + using an OpenShift CI HyperShift cluster. The pipeline provisions the cluster, + installs the Lightspeed Stack, runs the tests, and collects artifacts. params: - name: SNAPSHOT description: 'The JSON string representing the snapshot of the application under test (includes lightspeed-stack image).' - default: '{"components": [{"name":"lightspeed-stack-0-8", "containerImage": "quay.io/example/lightspeed-stack-0-8:latest"}]}' + default: '{"components": [{"name":"lightspeed-stack-0-7", "containerImage": "quay.io/example/lightspeed-stack-0-7:latest"}]}' type: string - name: llama-stack-image description: 'OGX runs from source on UBI (init container clones repo and installs deps). Parameter name kept for backwards compatibility.' @@ -20,66 +19,49 @@ spec: type: string - name: test-name description: 'The name of the test corresponding to a defined Konflux integration test.' - default: 'lightspeed-stack-0-8-e2e-tests' + default: 'lightspeed-stack-0-7-e2e-tests' - name: namespace description: 'Namespace to run tests in' default: 'lightspeed-stack' + - name: ocp-version + description: 'OpenShift minor version for the ephemeral HyperShift cluster.' + default: '4.19' + type: string + - name: compute-node-type + description: 'AWS instance type for the ephemeral HyperShift compute nodes.' + default: 'm5.xlarge' + type: string + - name: hosted-management-cluster + description: 'Hosted management cluster used to provision the ephemeral cluster.' + default: 'hosted-mgmt2' + type: string tasks: - - name: eaas-provision-space + - name: provision-cluster taskRef: resolver: git params: - name: url - value: https://github.com/konflux-ci/build-definitions.git + value: https://github.com/openshift/konflux-tasks - name: revision value: main - name: pathInRepo - value: task/eaas-provision-space/0.1/eaas-provision-space.yaml + value: tasks/provision-ephemeral-cluster/0.1/provision-ephemeral-cluster.yaml params: - - name: ownerKind - value: PipelineRun - name: ownerName value: $(context.pipelineRun.name) - name: ownerUid value: $(context.pipelineRun.uid) - - name: provision-cluster - runAfter: - - eaas-provision-space - taskSpec: - results: - - name: clusterName - value: "$(steps.create-cluster.results.clusterName)" - steps: - - name: pick-version - ref: - resolver: git - params: - - name: url - value: https://github.com/konflux-ci/build-definitions.git - - name: revision - value: main - - name: pathInRepo - value: stepactions/eaas-get-latest-openshift-version-by-prefix/0.1/eaas-get-latest-openshift-version-by-prefix.yaml - params: - - name: prefix - value: "4.19." - - name: create-cluster - ref: - resolver: git - params: - - name: url - value: https://github.com/konflux-ci/build-definitions.git - - name: revision - value: main - - name: pathInRepo - value: stepactions/eaas-create-ephemeral-cluster-hypershift-aws/0.1/eaas-create-ephemeral-cluster-hypershift-aws.yaml - params: - - name: eaasSpaceSecretRef - value: $(tasks.eaas-provision-space.results.secretRef) - - name: version - value: "$(steps.pick-version.results.version)" - - name: instanceType - value: "m5.large" + - name: workflow + value: hypershift-hostedcluster-workflow + - name: clusterProfile + value: aws-konflux-prod + - name: timeout + value: 1h + - name: releases + value: >- + {"latest":{"release":{"channel":"stable","version":"$(params.ocp-version)","architecture":"multi"}}} + - name: env + value: '{"COMPUTE_NODE_TYPE":"$(params.compute-node-type)","HOSTED_MANAGEMENT_CLUSTER":"$(params.hosted-management-cluster)"}' - name: get-stack-images description: Extract lightspeed-stack image and commit from SNAPSHOT (OGX runs from source in-pod) runAfter: @@ -117,8 +99,8 @@ spec: description: "commit sha to be used to store artifacts" script: | dnf -y install jq - echo -n "$(jq -r --arg n "lightspeed-stack-0-8" '.components[] | select(.name == $n) | .containerImage // ""' <<< "$SNAPSHOT")" > $(step.results.lightspeed-stack-image.path) - echo -n "$(jq -r --arg n "lightspeed-stack-0-8" '.components[] | select(.name == $n) | .source.git.revision // "latest"' <<< "$SNAPSHOT")" > $(step.results.commit.path) + echo -n "$(jq -r --arg n "lightspeed-stack-0-7" '.components[] | select(.name == $n) | .containerImage // ""' <<< "$SNAPSHOT")" > $(step.results.lightspeed-stack-image.path) + echo -n "$(jq -r --arg n "lightspeed-stack-0-7" '.components[] | select(.name == $n) | .source.git.revision // "latest"' <<< "$SNAPSHOT")" > $(step.results.commit.path) - name: echo-integration-params description: Echo all params passed to lightspeed-stack-integration-tests for verification before the test runs. runAfter: @@ -180,10 +162,10 @@ spec: value: $(tasks.get-stack-images.results.commit) - name: namespace value: "$(params.namespace)" - - name: spaceRequestSecretName - value: $(tasks.eaas-provision-space.results.secretRef) - - name: clusterName - value: $(tasks.provision-cluster.results.clusterName) + - name: clusterCredentialsSecretRef + value: $(tasks.provision-cluster.results.secretRef) + - name: ocp-version + value: $(params.ocp-version) runAfter: - echo-integration-params taskSpec: @@ -194,9 +176,9 @@ spec: - name: commit - name: namespace type: string - - name: spaceRequestSecretName + - name: clusterCredentialsSecretRef type: string - - name: clusterName + - name: ocp-version type: string results: - name: TEST_OUTPUT @@ -217,25 +199,9 @@ spec: secret: secretName: redhat-registry-io-pull-secret - name: credentials - emptyDir: {} + secret: + secretName: $(params.clusterCredentialsSecretRef) steps: - - name: get-kubeconfig - ref: - resolver: git - params: - - name: url - value: https://github.com/konflux-ci/build-definitions.git - - name: revision - value: main - - name: pathInRepo - value: stepactions/eaas-get-ephemeral-cluster-credentials/0.1/eaas-get-ephemeral-cluster-credentials.yaml - params: - - name: eaasSpaceSecretRef - value: $(params.spaceRequestSecretName) - - name: clusterName - value: "$(params.clusterName)" - - name: credentials - value: credentials - name: run-e2e-tests #onError: continue # Do not use onError: continue here unless a later step like @@ -262,7 +228,7 @@ spec: mountPath: /credentials env: - name: KUBECONFIG - value: "/credentials/$(steps.get-kubeconfig.results.kubeconfig)" + value: "/credentials/kubeconfig" - name: ARTIFACT_DIR value: "/workspace/artifacts" - name: SUITE_ID @@ -288,12 +254,12 @@ spec: echo "[e2e] 2/8 Installing deps (git, tar, jq, curl-minimal, python3, gettext for envsubst)..." microdnf -y install git tar jq curl-minimal python3 gettext echo "[e2e] 3/8 Downloading oc client..." - curl -sL -o oc.tar.gz https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/latest-4.19/openshift-client-linux-amd64-rhel9.tar.gz + curl -sL -o oc.tar.gz https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/latest-$(params.ocp-version)/openshift-client-linux-amd64-rhel9.tar.gz tar -xzf oc.tar.gz && chmod +x kubectl oc && mv oc kubectl /usr/local/bin/ echo "[e2e] 4/8 SNAPSHOT (length ${#SNAPSHOT} chars; clone URL fixed — SNAPSHOT is main/upstream, not fork)..." # Fixed fork + branch: Konflux SNAPSHOT points at main repo/rev, not the PR/fork under test. - REPO_URL=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-8") | .source.git.url // "https://github.com/lightspeed-core/lightspeed-stack.git"' <<< "$SNAPSHOT") - REPO_REV=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-8") | .source.git.revision // "main"' <<< "$SNAPSHOT") + REPO_URL=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-7") | .source.git.url // "https://github.com/lightspeed-core/lightspeed-stack.git"' <<< "$SNAPSHOT") + REPO_REV=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-7") | .source.git.revision // "main"' <<< "$SNAPSHOT") echo "[e2e] 5/8 Clone $REPO_URL @ $REPO_REV" git clone -q "$REPO_URL" /workspace/lightspeed-stack cd /workspace/lightspeed-stack && git fetch origin "$REPO_REV" && git checkout -q "$REPO_REV" @@ -319,7 +285,7 @@ spec: # - name: credentials # value: "credentials" # - name: kubeconfig - # value: "$(steps.get-kubeconfig.results.kubeconfig)" + # value: "kubeconfig" # - name: artifact-dir # value: "/workspace/konflux-artifacts" # # validate that the cluster resources are available in another tekton step @@ -359,4 +325,4 @@ spec: # - name: revision # value: main # - name: pathInRepo - # value: stepactions/fail-if-any-step-failed/0.1/fail-if-any-step-failed.yaml \ No newline at end of file + # value: stepactions/fail-if-any-step-failed/0.1/fail-if-any-step-failed.yaml diff --git a/.tekton/integration-tests/pipeline/lightspeed-stack-rhelai-test.yaml b/.tekton/integration-tests/pipeline/lightspeed-stack-rhelai-test.yaml index 5b24d6d276..b98f044723 100644 --- a/.tekton/integration-tests/pipeline/lightspeed-stack-rhelai-test.yaml +++ b/.tekton/integration-tests/pipeline/lightspeed-stack-rhelai-test.yaml @@ -2,20 +2,20 @@ apiVersion: tekton.dev/v1beta1 kind: Pipeline metadata: - name: lightspeed-stack-0-8-rhelai-tests-pipeline + name: lightspeed-stack-0-7-rhelai-tests-pipeline spec: description: | This pipeline provisions a RHEL AI instance on AWS (vLLM), an ephemeral - OpenShift cluster, deploys lightspeed-stack configured to use the RHEL AI - vLLM as inference provider, and runs the full e2e test suite. + OpenShift CI HyperShift cluster, deploys lightspeed-stack configured to use + the RHEL AI vLLM as inference provider, and runs the full e2e test suite. params: - name: SNAPSHOT description: 'The JSON string representing the snapshot of the application under test.' - default: '{"components": [{"name":"lightspeed-stack-0-8", "containerImage": "quay.io/example/lightspeed-stack-0-8:latest"}]}' + default: '{"components": [{"name":"lightspeed-stack-0-7", "containerImage": "quay.io/example/lightspeed-stack-0-7:latest"}]}' type: string - name: test-name description: 'The name of the test corresponding to a defined Konflux integration test.' - default: 'lightspeed-stack-0-8-rhelai-tests' + default: 'lightspeed-stack-0-7-rhelai-tests' - name: rhelai-version description: 'RHEL AI version to provision.' default: '3.4.0' @@ -39,6 +39,18 @@ spec: description: 'Enable debug output.' default: 'false' type: string + - name: ocp-version + description: 'OpenShift minor version for the ephemeral HyperShift cluster.' + default: '4.19' + type: string + - name: compute-node-type + description: 'AWS instance type for the ephemeral HyperShift compute nodes.' + default: 'm5.xlarge' + type: string + - name: hosted-management-cluster + description: 'Hosted management cluster used to provision the ephemeral cluster.' + default: 'hosted-mgmt2' + type: string tasks: # ── RHEL AI provisioning (MAPT) ── - name: provision-rhelai @@ -75,7 +87,7 @@ spec: path: token steps: - name: create-instance - image: quay.io/redhat-developer/mapt:v0.14.5 + image: quay.io/redhat-developer/mapt:v0.14.6 imagePullPolicy: Always env: - name: HOME @@ -199,63 +211,33 @@ spec: - name: spot value: "$(params.spot)" - # ── OpenShift cluster provisioning (same as existing e2e pipeline) ── - - name: eaas-provision-space + # ── OpenShift CI HyperShift cluster provisioning ── + - name: provision-cluster taskRef: resolver: git params: - name: url - value: https://github.com/konflux-ci/build-definitions.git + value: https://github.com/openshift/konflux-tasks - name: revision value: main - name: pathInRepo - value: task/eaas-provision-space/0.1/eaas-provision-space.yaml + value: tasks/provision-ephemeral-cluster/0.1/provision-ephemeral-cluster.yaml params: - - name: ownerKind - value: PipelineRun - name: ownerName value: $(context.pipelineRun.name) - name: ownerUid value: $(context.pipelineRun.uid) - - - name: provision-cluster - runAfter: - - eaas-provision-space - taskSpec: - results: - - name: clusterName - value: "$(steps.create-cluster.results.clusterName)" - steps: - - name: pick-version - ref: - resolver: git - params: - - name: url - value: https://github.com/konflux-ci/build-definitions.git - - name: revision - value: main - - name: pathInRepo - value: stepactions/eaas-get-latest-openshift-version-by-prefix/0.1/eaas-get-latest-openshift-version-by-prefix.yaml - params: - - name: prefix - value: "4.19." - - name: create-cluster - ref: - resolver: git - params: - - name: url - value: https://github.com/konflux-ci/build-definitions.git - - name: revision - value: main - - name: pathInRepo - value: stepactions/eaas-create-ephemeral-cluster-hypershift-aws/0.1/eaas-create-ephemeral-cluster-hypershift-aws.yaml - params: - - name: eaasSpaceSecretRef - value: $(tasks.eaas-provision-space.results.secretRef) - - name: version - value: "$(steps.pick-version.results.version)" - - name: instanceType - value: "m5.large" + - name: workflow + value: hypershift-hostedcluster-workflow + - name: clusterProfile + value: aws-konflux-prod + - name: timeout + value: 1h + - name: releases + value: >- + {"latest":{"release":{"channel":"stable","version":"$(params.ocp-version)","architecture":"multi"}}} + - name: env + value: '{"COMPUTE_NODE_TYPE":"$(params.compute-node-type)","HOSTED_MANAGEMENT_CLUSTER":"$(params.hosted-management-cluster)"}' - name: get-stack-images description: Extract lightspeed-stack image and commit from SNAPSHOT @@ -285,8 +267,8 @@ spec: type: string script: | dnf -y install jq - echo -n "$(jq -r --arg n "lightspeed-stack-0-8" '.components[] | select(.name == $n) | .containerImage // ""' <<< "$SNAPSHOT")" > $(step.results.lightspeed-stack-image.path) - echo -n "$(jq -r --arg n "lightspeed-stack-0-8" '.components[] | select(.name == $n) | .source.git.revision // "latest"' <<< "$SNAPSHOT")" > $(step.results.commit.path) + echo -n "$(jq -r --arg n "lightspeed-stack-0-7" '.components[] | select(.name == $n) | .containerImage // ""' <<< "$SNAPSHOT")" > $(step.results.lightspeed-stack-image.path) + echo -n "$(jq -r --arg n "lightspeed-stack-0-7" '.components[] | select(.name == $n) | .source.git.revision // "latest"' <<< "$SNAPSHOT")" > $(step.results.commit.path) # ── Full E2E tests (runs after both RHEL AI and OpenShift are ready) ── - name: rhelai-e2e-tests @@ -304,10 +286,10 @@ spec: value: $(tasks.get-stack-images.results.commit) - name: namespace value: "$(params.namespace)" - - name: spaceRequestSecretName - value: $(tasks.eaas-provision-space.results.secretRef) - - name: clusterName - value: $(tasks.provision-cluster.results.clusterName) + - name: clusterCredentialsSecretRef + value: $(tasks.provision-cluster.results.secretRef) + - name: ocp-version + value: $(params.ocp-version) - name: vllm-host value: "$(tasks.provision-rhelai.results.host)" - name: vllm-api-key @@ -321,9 +303,9 @@ spec: - name: commit - name: namespace type: string - - name: spaceRequestSecretName + - name: clusterCredentialsSecretRef type: string - - name: clusterName + - name: ocp-version type: string - name: vllm-host type: string @@ -342,25 +324,9 @@ spec: secret: secretName: quay-aipcc-password - name: credentials - emptyDir: {} + secret: + secretName: $(params.clusterCredentialsSecretRef) steps: - - name: get-kubeconfig - ref: - resolver: git - params: - - name: url - value: https://github.com/konflux-ci/build-definitions.git - - name: revision - value: main - - name: pathInRepo - value: stepactions/eaas-get-ephemeral-cluster-credentials/0.1/eaas-get-ephemeral-cluster-credentials.yaml - params: - - name: eaasSpaceSecretRef - value: $(params.spaceRequestSecretName) - - name: clusterName - value: "$(params.clusterName)" - - name: credentials - value: credentials - name: run-e2e-tests resources: requests: @@ -379,7 +345,7 @@ spec: mountPath: /credentials env: - name: KUBECONFIG - value: "/credentials/$(steps.get-kubeconfig.results.kubeconfig)" + value: "/credentials/kubeconfig" - name: ARTIFACT_DIR value: "/workspace/artifacts" - name: KONFLUX_BOOL @@ -407,11 +373,11 @@ spec: echo "[e2e] 2/8 Installing deps..." microdnf -y install git tar jq curl-minimal python3 gettext echo "[e2e] 3/8 Downloading oc client..." - curl -sL -o oc.tar.gz https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/latest-4.19/openshift-client-linux-amd64-rhel9.tar.gz + curl -sL -o oc.tar.gz https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/latest-$(params.ocp-version)/openshift-client-linux-amd64-rhel9.tar.gz tar -xzf oc.tar.gz && chmod +x kubectl oc && mv oc kubectl /usr/local/bin/ echo "[e2e] 4/8 VLLM_URL=$VLLM_URL" - REPO_URL=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-8") | .source.git.url // "https://github.com/lightspeed-core/lightspeed-stack.git"' <<< "$SNAPSHOT") - REPO_REV=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-8") | .source.git.revision // "main"' <<< "$SNAPSHOT") + REPO_URL=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-7") | .source.git.url // "https://github.com/lightspeed-core/lightspeed-stack.git"' <<< "$SNAPSHOT") + REPO_REV=$(jq -r '.components[] | select(.name == "lightspeed-stack-0-7") | .source.git.revision // "main"' <<< "$SNAPSHOT") echo "[e2e] 5/8 Clone $REPO_URL @ $REPO_REV" git clone -q "$REPO_URL" /workspace/lightspeed-stack cd /workspace/lightspeed-stack && git fetch origin "$REPO_REV" && git checkout -q "$REPO_REV" @@ -447,7 +413,7 @@ spec: path: token steps: - name: destroy-instance - image: quay.io/redhat-developer/mapt:v0.14.5 + image: quay.io/redhat-developer/mapt:v0.14.6 imagePullPolicy: Always env: - name: HOME diff --git a/.tekton/lightspeed-stack-0-8-pull-request.yaml b/.tekton/lightspeed-stack-0-7-pull-request.yaml similarity index 87% rename from .tekton/lightspeed-stack-0-8-pull-request.yaml rename to .tekton/lightspeed-stack-0-7-pull-request.yaml index caeae47b2e..79b78882dc 100644 --- a/.tekton/lightspeed-stack-0-8-pull-request.yaml +++ b/.tekton/lightspeed-stack-0-7-pull-request.yaml @@ -8,13 +8,12 @@ metadata: build.appstudio.redhat.com/target_branch: '{{target_branch}}' pipelinesascode.tekton.dev/cancel-in-progress: "true" pipelinesascode.tekton.dev/max-keep-runs: "3" - pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch - == "main" + pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch == "release/0.7" labels: - appstudio.openshift.io/application: lightspeed-core-0-8 - appstudio.openshift.io/component: lightspeed-stack-0-8 + appstudio.openshift.io/application: lightspeed-core-0-7 + appstudio.openshift.io/component: lightspeed-stack-0-7 pipelines.appstudio.openshift.io/type: build - name: lightspeed-stack-0-8-on-pull-request + name: lightspeed-stack-0-7-on-pull-request namespace: lightspeed-core-tenant spec: params: @@ -23,7 +22,7 @@ spec: - name: revision value: '{{revision}}' - name: output-image - value: quay.io/redhat-user-workloads/lightspeed-core-tenant/lightspeed-stack-0-8:on-pr-{{revision}} + value: quay.io/redhat-user-workloads/lightspeed-core-tenant/lightspeed-stack-0-7:on-pr-{{revision}} - name: image-expires-after value: 5d - name: build-platforms @@ -81,13 +80,11 @@ spec: name: output-image type: string - default: . - description: Path to the source code of an application's component from where - to build image. + description: Path to the source code of an application's component from where to build image. name: path-context type: string - default: Dockerfile - description: Path to the Dockerfile inside the context specified by parameter - path-context + description: Path to the Dockerfile inside the context specified by parameter path-context name: dockerfile type: string - default: "false" @@ -103,8 +100,7 @@ spec: name: prefetch-input type: string - default: "" - description: Image tag expiration time, time values could be something like - 1h, 2d, 3w for hours, days, and weeks, respectively. + description: Image tag expiration time, time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively. name: image-expires-after type: string - default: "false" @@ -116,8 +112,7 @@ spec: name: build-image-index type: string - default: docker - description: The format for the resulting image's mediaType. Valid values are - oci or docker. + description: The format for the resulting image's mediaType. Valid values are oci or docker. name: buildah-format type: string - default: "false" @@ -127,8 +122,7 @@ spec: description: Use the package registry proxy when prefetching dependencies name: enable-package-registry-proxy - default: . - description: Target directories in component's source code to scan with SAST - tools. Multiple values should be separated with commas. + description: Target directories in component's source code to scan with SAST tools. Multiple values should be separated with commas. name: sast-target-dirs type: string - default: [] @@ -140,30 +134,24 @@ spec: name: build-args-file type: string - default: "false" - description: Whether to enable privileged mode, should be used only with remote - VMs + description: Whether to enable privileged mode, should be used only with remote VMs name: privileged-nested type: string - default: "" - description: Sets the image created time and the SOURCE_DATE_EPOCH build argument. - On its own, it does not change file timestamps inside the layers (set rewrite-timestamp - to "true" for that). Leave empty to keep the actual build time. + description: Sets the image created time and the SOURCE_DATE_EPOCH build argument. On its own, it does not change file timestamps inside the layers (set rewrite-timestamp to "true" for that). Leave empty to keep the actual build time. name: source-date-epoch type: string - default: "false" - description: When "true", clamp file modification times in the image layers - to at most source-date-epoch. Does nothing unless source-date-epoch is set. + description: When "true", clamp file modification times in the image layers to at most source-date-epoch. Does nothing unless source-date-epoch is set. name: rewrite-timestamp type: string - default: "false" - description: When "true", omit the build history (history timestamps, layer - metadata, etc.) from the resulting image. + description: When "true", omit the build history (history timestamps, layer metadata, etc.) from the resulting image. name: omit-history type: string - default: - linux/x86_64 - description: List of platforms to build the container images on. The available - set of values is determined by the configuration of the multi-platform-controller. + description: List of platforms to build the container images on. The available set of values is determined by the configuration of the multi-platform-controller. name: build-platforms type: array results: @@ -189,7 +177,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:7854d7babe4136e53e337f6433d952532314433b48bb62b422ddc02700934d85 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:ba9c197132e5a1e80116485f3f38ef47aba1efb1aee9417a9e859a55c33bb9df - name: kind value: task resolver: bundles @@ -210,7 +198,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:a3678910c2bf9187a66a96066193cb41046fbb7097160bf2be6a374ed5e49d30 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:b3c05f2aab54b5b6d7e9b5a95931b2c6602574604842a4c430fdb3fbc3fb9fcb - name: kind value: task resolver: bundles @@ -236,7 +224,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.3@sha256:0c386f2c26b46b978280c5c54caac26bcd524975f8f7cc06bb080ba00eae3ad4 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.11.0@sha256:e670e563939f8c5582b64b1f693ae104a3f4cba4dfd651ecbd15f341307c86b9 - name: kind value: task resolver: bundles @@ -269,8 +257,6 @@ spec: - name: BUILD_ARGS value: - $(params.build-args[*]) - - name: BUILD_ARGS_FILE - value: $(params.build-args-file) - name: PRIVILEGED_NESTED value: $(params.privileged-nested) - name: SOURCE_URL @@ -289,10 +275,13 @@ spec: value: $(params.omit-history) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) - name: IMAGE_APPEND_PLATFORM value: "true" + - name: BUILD_ARGS_FILES + value: + - $(params.build-args-file) + - name: PREFETCH_ARTIFACT + value: $(tasks.prefetch-dependencies.results.PREFETCH_ARTIFACT) runAfter: - prefetch-dependencies taskRef: @@ -300,7 +289,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.3@sha256:88b971c800b3e71903ddad83da884d092e9beda8b51deaf980f6525e45ed043f + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.14.1@sha256:2ca404d74b6b81af71034f4a50490a2c4c5591d34c88ddb14fbb495659f80ff3 - name: kind value: task resolver: bundles @@ -322,7 +311,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.4@sha256:654446d8ec028b20ed6ada8a96486ff16692a25192755aa04a99cea8e0afb7e2 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.4@sha256:0c0cf90a91b0962a10400e3a8bdc60ab6369642e3563ad25598caca3d0304686 - name: kind value: task resolver: bundles @@ -334,8 +323,8 @@ spec: value: $(tasks.build-image-index.results.IMAGE_DIGEST) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) + - name: PREFETCH_ARTIFACT + value: $(tasks.prefetch-dependencies.results.PREFETCH_ARTIFACT) runAfter: - build-image-index taskRef: @@ -343,7 +332,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.2@sha256:45091b5022754d452b4de60c7d7f4c6e03626efff55f96e9af937400fe3506eb + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.4.0@sha256:dafbb41ecb20c9706224eb6d716bc1a3739fa6bbffe901dd2486810fa5cdd431 - name: kind value: task resolver: bundles @@ -365,7 +354,7 @@ spec: - name: name value: deprecated-image-check - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:0ccc688a77e9b7b0b8973c132a1e840844137e77f887be4a0bec8893b0776872 + value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:81b7cad8d5fd0148b157d622f4064f57d38aa33c142275050c8a4727d1e2144c - name: kind value: task resolver: bundles @@ -392,7 +381,7 @@ spec: - name: name value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:4d48db2ada6584bc7343ee11adb04d86f1ca9a2849dc718ccda861e092fcf41b + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1.1@sha256:4d2c2edcfa0e3616ceb3fe67534aa1955f73fc14209acf734f2d9c9f1f47de27 - name: kind value: task resolver: bundles @@ -417,7 +406,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:fa53ef450ba538addafee1211ff6ac15b55865ce652fec15ac99b554680cb642 + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:cc7d71d9be01e9221d8753a1f1a7caaf0fb93048df69e9a95c359068dc730578 - name: kind value: task resolver: bundles @@ -434,8 +423,6 @@ spec: value: $(tasks.build-image-index.results.IMAGE_URL) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) - name: ARGS value: --project-name=lightspeed-stack --report --org=dca2ca89-7e51-4a3a-b7a5-6ad5633057b8 - name: TARGET_DIRS @@ -447,7 +434,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:17b9587eba53fabaab35061275306bfe1f99661d55e14e22f62321fa7cb5ff3e + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5.1@sha256:53443febc3a87ea6d4baacffc70d6f7dbdd1b4e69e0852337661097c73433519 - name: kind value: task resolver: bundles @@ -474,7 +461,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.4@sha256:3203cd3bde82d9e4d1beb5f1366942346800cff89e392c920733eb0ea78d731b + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.4@sha256:6406168c03f83558b4ff454fec2503269ee8f522ea2cd2d1b293cc75d37df32a - name: kind value: task resolver: bundles @@ -511,7 +498,7 @@ spec: - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) + value: $(tasks.prefetch-dependencies.results.PREFETCH_ARTIFACT) - name: TARGET_DIRS value: $(params.sast-target-dirs) runAfter: @@ -559,8 +546,6 @@ spec: value: $(tasks.build-image-index.results.IMAGE_URL) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) - name: TARGET_DIRS value: $(params.sast-target-dirs) runAfter: @@ -570,7 +555,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:d00c884c78489a52dc638e6ced0a01429670d0754f5b1305ad576709cf660312 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1.1@sha256:83722286ec3297e3f66b0c220cec8895e0f745337e464c1d1c038ed737cf69fd - name: kind value: task resolver: bundles @@ -589,8 +574,6 @@ spec: value: $(params.sast-target-dirs) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) runAfter: - build-image-index taskRef: @@ -598,7 +581,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:15d654c9576b9c4ee1f78b09fce541acf9afe33f1c2363fd6219a79dbf62c95d + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4.1@sha256:2ee89730856f542d851d99b5d945963583a840c295354dc4257a0052841e35ab - name: kind value: task resolver: bundles @@ -620,7 +603,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.1@sha256:c177fc2b2482b1299e713c51bf258eba4972c016ef260d9f45335c2550dc7b2b + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.2@sha256:87e14517c2d35f245e31bd0c055954bd97beeb4d09c38bf82a91a1bed4ab826c - name: kind value: task resolver: bundles @@ -643,7 +626,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:4c567d11296850230fe32e76d129834d3bf337a0cae9989b64279b7cfaa4e735 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:e3521e7fdd7ffc728b95fbd611cde27cf25e473aae6298dfa76d310d8b3868f2 - name: kind value: task resolver: bundles @@ -660,7 +643,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:4b1856e41d087e53af26562fb16a490579777d9ff05af33011ce212d477c7cd5 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:3e9b994f069cd7179b07f3bacb569c13bb065a23326f41a0ec574ce66461ecbf - name: kind value: task resolver: bundles @@ -678,7 +661,7 @@ spec: pipeline: 4h tasks: 4h taskRunTemplate: - serviceAccountName: build-pipeline-lightspeed-stack-0-8 + serviceAccountName: build-pipeline-lightspeed-stack-0-7 workspaces: - name: git-auth secret: diff --git a/.tekton/lightspeed-stack-0-8-push.yaml b/.tekton/lightspeed-stack-0-7-push.yaml similarity index 87% rename from .tekton/lightspeed-stack-0-8-push.yaml rename to .tekton/lightspeed-stack-0-7-push.yaml index 0d5eb6b53c..b2ec995465 100644 --- a/.tekton/lightspeed-stack-0-8-push.yaml +++ b/.tekton/lightspeed-stack-0-7-push.yaml @@ -11,13 +11,12 @@ metadata: build.appstudio.redhat.com/target_branch: '{{target_branch}}' pipelinesascode.tekton.dev/cancel-in-progress: "false" pipelinesascode.tekton.dev/max-keep-runs: "3" - pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch - == "main" + pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch == "release/0.7" labels: - appstudio.openshift.io/application: lightspeed-core-0-8 - appstudio.openshift.io/component: lightspeed-stack-0-8 + appstudio.openshift.io/application: lightspeed-core-0-7 + appstudio.openshift.io/component: lightspeed-stack-0-7 pipelines.appstudio.openshift.io/type: build - name: lightspeed-stack-0-8-on-push + name: lightspeed-stack-0-7-on-push namespace: lightspeed-core-tenant spec: params: @@ -26,7 +25,7 @@ spec: - name: revision value: '{{revision}}' - name: output-image - value: quay.io/redhat-user-workloads/lightspeed-core-tenant/lightspeed-stack-0-8:{{revision}} + value: quay.io/redhat-user-workloads/lightspeed-core-tenant/lightspeed-stack-0-7:{{revision}} - name: build-platforms value: - linux/x86_64 @@ -82,13 +81,11 @@ spec: name: output-image type: string - default: . - description: Path to the source code of an application's component from where - to build image. + description: Path to the source code of an application's component from where to build image. name: path-context type: string - default: Dockerfile - description: Path to the Dockerfile inside the context specified by parameter - path-context + description: Path to the Dockerfile inside the context specified by parameter path-context name: dockerfile type: string - default: "false" @@ -104,8 +101,7 @@ spec: name: prefetch-input type: string - default: "" - description: Image tag expiration time, time values could be something like - 1h, 2d, 3w for hours, days, and weeks, respectively. + description: Image tag expiration time, time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively. name: image-expires-after type: string - default: "false" @@ -117,8 +113,7 @@ spec: name: build-image-index type: string - default: docker - description: The format for the resulting image's mediaType. Valid values are - oci or docker. + description: The format for the resulting image's mediaType. Valid values are oci or docker. name: buildah-format type: string - default: "false" @@ -128,8 +123,7 @@ spec: description: Use the package registry proxy when prefetching dependencies name: enable-package-registry-proxy - default: . - description: Target directories in component's source code to scan with SAST - tools. Multiple values should be separated with commas. + description: Target directories in component's source code to scan with SAST tools. Multiple values should be separated with commas. name: sast-target-dirs type: string - default: [] @@ -141,30 +135,24 @@ spec: name: build-args-file type: string - default: "false" - description: Whether to enable privileged mode, should be used only with remote - VMs + description: Whether to enable privileged mode, should be used only with remote VMs name: privileged-nested type: string - default: "" - description: Sets the image created time and the SOURCE_DATE_EPOCH build argument. - On its own, it does not change file timestamps inside the layers (set rewrite-timestamp - to "true" for that). Leave empty to keep the actual build time. + description: Sets the image created time and the SOURCE_DATE_EPOCH build argument. On its own, it does not change file timestamps inside the layers (set rewrite-timestamp to "true" for that). Leave empty to keep the actual build time. name: source-date-epoch type: string - default: "false" - description: When "true", clamp file modification times in the image layers - to at most source-date-epoch. Does nothing unless source-date-epoch is set. + description: When "true", clamp file modification times in the image layers to at most source-date-epoch. Does nothing unless source-date-epoch is set. name: rewrite-timestamp type: string - default: "false" - description: When "true", omit the build history (history timestamps, layer - metadata, etc.) from the resulting image. + description: When "true", omit the build history (history timestamps, layer metadata, etc.) from the resulting image. name: omit-history type: string - default: - linux/x86_64 - description: List of platforms to build the container images on. The available - set of values is determined by the configuration of the multi-platform-controller. + description: List of platforms to build the container images on. The available set of values is determined by the configuration of the multi-platform-controller. name: build-platforms type: array results: @@ -190,7 +178,7 @@ spec: - name: name value: init - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:7854d7babe4136e53e337f6433d952532314433b48bb62b422ddc02700934d85 + value: quay.io/konflux-ci/tekton-catalog/task-init:0.4.3@sha256:ba9c197132e5a1e80116485f3f38ef47aba1efb1aee9417a9e859a55c33bb9df - name: kind value: task resolver: bundles @@ -211,7 +199,7 @@ spec: - name: name value: git-clone-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:a3678910c2bf9187a66a96066193cb41046fbb7097160bf2be6a374ed5e49d30 + value: quay.io/konflux-ci/tekton-catalog/task-git-clone-oci-ta:0.2.6@sha256:b3c05f2aab54b5b6d7e9b5a95931b2c6602574604842a4c430fdb3fbc3fb9fcb - name: kind value: task resolver: bundles @@ -237,7 +225,7 @@ spec: - name: name value: prefetch-dependencies-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.10.3@sha256:0c386f2c26b46b978280c5c54caac26bcd524975f8f7cc06bb080ba00eae3ad4 + value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies-oci-ta:0.11.0@sha256:e670e563939f8c5582b64b1f693ae104a3f4cba4dfd651ecbd15f341307c86b9 - name: kind value: task resolver: bundles @@ -270,8 +258,6 @@ spec: - name: BUILD_ARGS value: - $(params.build-args[*]) - - name: BUILD_ARGS_FILE - value: $(params.build-args-file) - name: PRIVILEGED_NESTED value: $(params.privileged-nested) - name: SOURCE_URL @@ -290,10 +276,13 @@ spec: value: $(params.omit-history) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) - name: IMAGE_APPEND_PLATFORM value: "true" + - name: BUILD_ARGS_FILES + value: + - $(params.build-args-file) + - name: PREFETCH_ARTIFACT + value: $(tasks.prefetch-dependencies.results.PREFETCH_ARTIFACT) runAfter: - prefetch-dependencies taskRef: @@ -301,7 +290,7 @@ spec: - name: name value: buildah-remote-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.12.3@sha256:88b971c800b3e71903ddad83da884d092e9beda8b51deaf980f6525e45ed043f + value: quay.io/konflux-ci/tekton-catalog/task-buildah-remote-oci-ta:0.14.1@sha256:2ca404d74b6b81af71034f4a50490a2c4c5591d34c88ddb14fbb495659f80ff3 - name: kind value: task resolver: bundles @@ -323,7 +312,7 @@ spec: - name: name value: build-image-index - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.4@sha256:654446d8ec028b20ed6ada8a96486ff16692a25192755aa04a99cea8e0afb7e2 + value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.4@sha256:0c0cf90a91b0962a10400e3a8bdc60ab6369642e3563ad25598caca3d0304686 - name: kind value: task resolver: bundles @@ -335,8 +324,8 @@ spec: value: $(tasks.build-image-index.results.IMAGE_DIGEST) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) + - name: PREFETCH_ARTIFACT + value: $(tasks.prefetch-dependencies.results.PREFETCH_ARTIFACT) runAfter: - build-image-index taskRef: @@ -344,7 +333,7 @@ spec: - name: name value: source-build-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.3.2@sha256:45091b5022754d452b4de60c7d7f4c6e03626efff55f96e9af937400fe3506eb + value: quay.io/konflux-ci/tekton-catalog/task-source-build-oci-ta:0.4.0@sha256:dafbb41ecb20c9706224eb6d716bc1a3739fa6bbffe901dd2486810fa5cdd431 - name: kind value: task resolver: bundles @@ -366,7 +355,7 @@ spec: - name: name value: deprecated-image-check - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:0ccc688a77e9b7b0b8973c132a1e840844137e77f887be4a0bec8893b0776872 + value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:81b7cad8d5fd0148b157d622f4064f57d38aa33c142275050c8a4727d1e2144c - name: kind value: task resolver: bundles @@ -393,7 +382,7 @@ spec: - name: name value: roxctl-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1@sha256:4d48db2ada6584bc7343ee11adb04d86f1ca9a2849dc718ccda861e092fcf41b + value: quay.io/konflux-ci/tekton-catalog/task-roxctl-scan:0.1.1@sha256:4d2c2edcfa0e3616ceb3fe67534aa1955f73fc14209acf734f2d9c9f1f47de27 - name: kind value: task resolver: bundles @@ -418,7 +407,7 @@ spec: - name: name value: ecosystem-cert-preflight-checks - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:fa53ef450ba538addafee1211ff6ac15b55865ce652fec15ac99b554680cb642 + value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:cc7d71d9be01e9221d8753a1f1a7caaf0fb93048df69e9a95c359068dc730578 - name: kind value: task resolver: bundles @@ -435,8 +424,6 @@ spec: value: $(tasks.build-image-index.results.IMAGE_URL) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) - name: ARGS value: --project-name=lightspeed-stack --report --org=dca2ca89-7e51-4a3a-b7a5-6ad5633057b8 - name: TARGET_DIRS @@ -448,7 +435,7 @@ spec: - name: name value: sast-snyk-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5@sha256:17b9587eba53fabaab35061275306bfe1f99661d55e14e22f62321fa7cb5ff3e + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check-oci-ta:0.5.1@sha256:53443febc3a87ea6d4baacffc70d6f7dbdd1b4e69e0852337661097c73433519 - name: kind value: task resolver: bundles @@ -475,7 +462,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.4@sha256:3203cd3bde82d9e4d1beb5f1366942346800cff89e392c920733eb0ea78d731b + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3.4@sha256:6406168c03f83558b4ff454fec2503269ee8f522ea2cd2d1b293cc75d37df32a - name: kind value: task resolver: bundles @@ -512,7 +499,7 @@ spec: - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) + value: $(tasks.prefetch-dependencies.results.PREFETCH_ARTIFACT) - name: TARGET_DIRS value: $(params.sast-target-dirs) runAfter: @@ -560,8 +547,6 @@ spec: value: $(tasks.build-image-index.results.IMAGE_URL) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) - name: TARGET_DIRS value: $(params.sast-target-dirs) runAfter: @@ -571,7 +556,7 @@ spec: - name: name value: sast-shell-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1@sha256:d00c884c78489a52dc638e6ced0a01429670d0754f5b1305ad576709cf660312 + value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check-oci-ta:0.1.1@sha256:83722286ec3297e3f66b0c220cec8895e0f745337e464c1d1c038ed737cf69fd - name: kind value: task resolver: bundles @@ -590,8 +575,6 @@ spec: value: $(params.sast-target-dirs) - name: SOURCE_ARTIFACT value: $(tasks.prefetch-dependencies.results.SOURCE_ARTIFACT) - - name: CACHI2_ARTIFACT - value: $(tasks.prefetch-dependencies.results.CACHI2_ARTIFACT) runAfter: - build-image-index taskRef: @@ -599,7 +582,7 @@ spec: - name: name value: sast-unicode-check-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4@sha256:15d654c9576b9c4ee1f78b09fce541acf9afe33f1c2363fd6219a79dbf62c95d + value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check-oci-ta:0.4.1@sha256:2ee89730856f542d851d99b5d945963583a840c295354dc4257a0052841e35ab - name: kind value: task resolver: bundles @@ -616,7 +599,7 @@ spec: value: $(tasks.build-image-index.results.IMAGE_DIGEST) - name: ADDITIONAL_TAGS value: - - $(tasks.clone-repository.results.short-commit) + - $(tasks.clone-repository.results.short-commit) runAfter: - build-image-index taskRef: @@ -624,7 +607,7 @@ spec: - name: name value: apply-tags - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.1@sha256:c177fc2b2482b1299e713c51bf258eba4972c016ef260d9f45335c2550dc7b2b + value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.3.2@sha256:87e14517c2d35f245e31bd0c055954bd97beeb4d09c38bf82a91a1bed4ab826c - name: kind value: task resolver: bundles @@ -647,7 +630,7 @@ spec: - name: name value: push-dockerfile-oci-ta - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:4c567d11296850230fe32e76d129834d3bf337a0cae9989b64279b7cfaa4e735 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile-oci-ta:0.3.1@sha256:e3521e7fdd7ffc728b95fbd611cde27cf25e473aae6298dfa76d310d8b3868f2 - name: kind value: task resolver: bundles @@ -664,7 +647,7 @@ spec: - name: name value: rpms-signature-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:4b1856e41d087e53af26562fb16a490579777d9ff05af33011ce212d477c7cd5 + value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2.2@sha256:3e9b994f069cd7179b07f3bacb569c13bb065a23326f41a0ec574ce66461ecbf - name: kind value: task resolver: bundles @@ -682,7 +665,7 @@ spec: pipeline: 4h tasks: 4h taskRunTemplate: - serviceAccountName: build-pipeline-lightspeed-stack-0-8 + serviceAccountName: build-pipeline-lightspeed-stack-0-7 workspaces: - name: git-auth secret: diff --git a/Makefile b/Makefile index ed8c1ebf9b..3245821922 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 c66072ce31..f57a07c533 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/deploy/lightspeed-stack/Containerfile b/deploy/lightspeed-stack/Containerfile index 6160daf5e1..bfd70e1783 100644 --- a/deploy/lightspeed-stack/Containerfile +++ b/deploy/lightspeed-stack/Containerfile @@ -94,8 +94,6 @@ COPY --from=builder /app-root/LICENSE /licenses/ USER root # Additional tools for derived images -# Note: --allowerasing is only supported by dnf, not microdnf (used by the -# *-minimal base images), so it is only appended when RUNTIME_DNF_COMMAND=dnf. RUN ${RUNTIME_DNF_COMMAND} install -y --nodocs --setopt=keepcache=0 --setopt=tsflags=nodocs jq patch && \ ${RUNTIME_DNF_COMMAND} update -y --nodocs --setopt=keepcache=0 --setopt=tsflags=nodocs @@ -128,7 +126,7 @@ ENTRYPOINT ["/app-root/entrypoint.sh"] LABEL vendor="Red Hat, Inc." \ name="lightspeed-core/lightspeed-stack-rhel9" \ com.redhat.component="lightspeed-core/lightspeed-stack" \ - cpe="cpe:/a:redhat:lightspeed_core:0.8::el9" \ + cpe="cpe:/a:redhat:lightspeed_core:0.7::el9" \ io.k8s.display-name="Lightspeed Stack" \ summary="A service that provides a REST API for the Lightspeed Core Stack." \ description="Lightspeed Core Stack (LCS) is an AI-powered assistant that provides answers to product questions using backend LLM services, agents, and RAG databases." \ diff --git a/docker-compose-library.yaml b/docker-compose-library.yaml index 07e13e24ea..b5b2a2d22d 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 7ae0e60b38..707d6af4de 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 4377501c79..d49f00712c 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 306ca165bb..7a97e91d52 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 9abe1dbc0f..6399e359d2 100644 --- a/docs/design/conversation-compaction/conversation-compaction.md +++ b/docs/design/conversation-compaction/conversation-compaction.md @@ -61,7 +61,7 @@ R9 Compaction configuration must be admin-configurable via YAML: threshold ratio, fixed token floor, and buffer zone size. R10 -After compaction, lightspeed-stack builds the LLM input explicitly — the summaries plus the recent verbatim turns plus the new query — and stops passing the OGX `conversation` parameter for that request, because OGX always reloads the full message history when the `conversation` parameter is set (verified empirically on OGX 0.6.0; see the Changelog and the spike doc). The summary is still written into the conversation as a marked item so it appears in the OGX Conversations API, but the marker is lightspeed-stack's own boundary bookkeeping, not an OGX selection mechanism. The `conversation_id` is preserved across the whole conversation, and the full history (including pre-compaction turns) remains in the conversation's items for UI/audit. Because the `conversation` parameter is no longer sent in compacted mode, lightspeed-stack appends each completed turn to the conversation itself. +After compaction, lightspeed-stack builds the LLM input explicitly — the summaries plus the recent verbatim turns plus the new query — and stops passing the OGX `conversation` parameter for that request, because OGX always reloads the full message history when the `conversation` parameter is set (verified empirically on OGX 0.6.0; see the Changelog and the spike doc). The summary is still written into the conversation as a marked item so it appears in the Conversations API, but the marker is lightspeed-stack's own boundary bookkeeping, not an OGX selection mechanism. The `conversation_id` is preserved across the whole conversation, and the full history (including pre-compaction turns) remains in the conversation's items for UI/audit. Because the `conversation` parameter is no longer sent in compacted mode, lightspeed-stack appends each completed turn to the conversation itself. This applies to every endpoint that builds context from a growing conversation and calls the Responses API: `/v1/query`, `/v1/streaming_query`, the A2A executor, and `/v1/responses`. (The `/v1/rlsapi` inference path is stateless — no stored conversation — and is therefore out of scope.) @@ -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). @@ -229,11 +229,11 @@ As built, the cache is the **preferred source of truth** for summary text at run ## Changed request flow after compaction -After compaction, lightspeed-stack writes the summary as a marked conversation item (a message whose text begins with a recognizable sentinel) so it appears in the OGX Conversations API and serves as lightspeed-stack's own boundary marker. +After compaction, lightspeed-stack writes the summary as a marked conversation item (a message whose text begins with a recognizable sentinel) so it appears in the Conversations API and serves as lightspeed-stack's own boundary marker. When building context for a compacted conversation, lightspeed-stack fetches the conversation items, reads the active summaries from the summary cache (LCORE-1571) — falling back to the marker texts when no persisting cache is configured — takes the items after the last marker as the recent verbatim buffer, and sends `[summaries] + [recent items] + [new query]` as **explicit input**, **without** the `conversation` parameter. This is necessary because OGX reloads the *full* stored message history whenever the `conversation` parameter is set — there is no marker-based selection hook (verified empirically; see the Changelog). Each completed turn is then appended back to the conversation items by lightspeed-stack, since OGX no longer auto-stores it. -This preserves a single continuous conversation identity. The `conversation_id` never changes and the user sees one conversation in the UI. OGX stores the full history, including the summary marker items, and returns it through its Conversations API. lightspeed-stack's own `GET /v1/conversations/{conversation_id}` leaves the marker items out of the chat history it returns (LCORE-3909): they are stored as user messages, but the user never sent them. Stored conversations are not migrated: the markers must stay in storage as the fallback source of truth, so they are filtered when the conversation is read. +This preserves a single continuous conversation identity. The `conversation_id` never changes, the user sees one conversation in the UI, and the Conversations API returns the full history including the summary marker items. ## API response changes @@ -323,7 +323,6 @@ Add `compaction` field to the root `Configuration` class. | `src/models/api/responses/successful/query.py` | `context_status` on `QueryResponse` (non-streaming `/v1/query`) — LCORE-1573 | | `src/models/common/agents/stream_payloads.py` | `context_status` on `EndEventData`, the streaming SSE `end` event payload (`StreamingQueryResponse` is docs-only and intentionally skipped) — LCORE-1573 | | `src/cache/` (all backends) | `ConversationSummary` storage — LCORE-1571 | -| `src/app/endpoints/conversations_v1.py` | Pass the stored items through `exclude_marker_items()` before building the chat history — LCORE-3909 | ## How compaction is invoked @@ -384,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 @@ -436,16 +435,6 @@ mid-conversation on an already-compacted conversation reverts it to full-history replay (unsupported — see Configuration). The `CompactionResult` mode flag was renamed `summarized` → `compacted` for clarity. -**2026-09-28 — Markers are left out of the conversation history (LCORE-3909).** -`GET /v1/conversations/{conversation_id}` filters the marker items when the -conversation is read. A marker is a user message that starts with the sentinel, -with or without the covered-item count. The markers stay in storage as the -fallback source of truth, and stored conversations are not migrated. -`GET /v2/conversations/{conversation_id}` reads the conversation cache, which -never holds a marker, and is unchanged. Earlier revisions of this document said -that the Conversations API returns the marker items; that holds for the OGX -Conversations API only. - # Appendix A: PoC Evidence A proof-of-concept was built and tested. 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 cea25facbf..d495540c7d 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 2521970c99..8327336131 100644 --- a/docs/design/ogx-config-merge/ogx-config-merge.md +++ b/docs/design/ogx-config-merge/ogx-config-merge.md @@ -41,8 +41,7 @@ Key shape: `llama_stack` YAML-section alias is still accepted) + external `run.yaml`) is preserved during a deprecation window; mutually exclusive with the unified *synthesis inputs* (a non-empty - `inference.providers`, a non-empty `vector_store.providers`, or an - `ogx.config` block). + `inference.providers` or a `ogx.config` block). ## Why @@ -120,8 +119,7 @@ detail that LCORE owns, not an operator-facing artifact. location for debugging. - **R11:** Shape detection determines mode. Unified mode is signalled by the presence of any *synthesis input* — a non-empty top-level - `inference.providers`, a non-empty top-level `vector_store.providers`, - or an `ogx.config` block; legacy mode by + `inference.providers` or a `ogx.config` block; legacy mode by `library_client_config_path`. An optional `config_format_version` field is accepted but must agree with the detected shape when present. See the "Mode detection" table under Architecture for the full matrix. @@ -180,10 +178,10 @@ lightspeed-stack.yaml (unified mode) └────────────┬───────────────┘ and `library_client_config_path`. │ Configuration (typed) ▼ - ┌────────────────────────────┐ Baseline selection (profile / default / - │ Synthesizer │ byo-llm / empty) + high-level sections - │ synthesize_configuration │ + native_override deep-merge, then - │ (ogx_config…) │ enrichment (BYOK RAG, Solr/OKP, Azure). + ┌────────────────────────────┐ Baseline selection (profile / + │ Synthesizer │ default / empty) + enrichment + │ synthesize_configuration │ (BYOK RAG, Solr/OKP) + high-level + │ (ogx_config…) │ sections + native_override deep-merge. └────────────┬───────────────┘ │ synthesized run.yaml (dict) ▼ @@ -199,23 +197,23 @@ lightspeed-stack.yaml (unified mode) ### Trigger mechanism At LCORE startup (library mode): if any synthesis input is present (a -non-empty top-level `inference.providers`, a non-empty top-level -`vector_store.providers`, or an `ogx.config` block), the synthesizer produces a `run.yaml` dict, writes it to disk, +non-empty top-level `inference.providers`, or a `ogx.config` +block), the synthesizer produces a `run.yaml` dict, writes it to disk, and passes the path to the library client. At OGX container startup (server mode): the container's entrypoint script invokes `python3 /opt/app-root/ogx_configuration.py -c --i -o /tmp/generated-run.yaml` (`-i` is used in legacy mode only). The Python CLI auto-detects unified vs legacy +-o /opt/app-root/run.yaml`. The Python CLI auto-detects unified vs legacy by the same synthesis-input check; in unified mode it synthesizes and writes the output; in legacy mode it performs in-place enrichment as before. ### Mode detection -*Synthesis inputs* are the top-level high-level sections (a non-empty -`inference.providers` or a non-empty `vector_store.providers`; future -`rag`, …) and the `ogx.config` block. The loaded `lightspeed-stack.yaml` maps to a mode as follows: +*Synthesis inputs* are the top-level high-level sections (v1: a non-empty +`inference.providers`; future `rag`, …) and the `ogx.config` +block. The loaded `lightspeed-stack.yaml` maps to a mode as follows: | Shape | Mode | |---|---| @@ -258,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: @@ -475,16 +473,15 @@ not have had a full release with a working migration path. Releases: is `byo-llm`, strip the built-in conditional OpenAI inference row. `empty` and `profile:` are unchanged. 3. Run `dedupe_providers_vector_io` on the baseline. -4. If top-level `inference.providers` is non-empty → +4. Apply existing enrichment: `enrich_byok_rag`, `enrich_solr` (Azure + Entra ID intentionally stays separate because it's a `.env` + side-effect, not an `ogx_config` mutation). +5. If top-level `inference.providers` is non-empty → `apply_high_level_inference(ogx_config, lcs_config["inference"])`. -5. Unless the baseline is `empty` → `ensure_mcp_tool_runtime(ogx_config)`. 6. If `unified` and `unified.native_override` non-empty → `deep_merge_list_replace(ogx_config, native_override)`. -7. Apply existing enrichment, after the override merge (LCORE-3370): - `enrich_azure_entra_id_inference`, `enrich_byok_rag`, `enrich_solr`, - `enrich_vector_store`. -8. `dedupe_providers_vector_io` again. -9. Return the final dict. +7. `dedupe_providers_vector_io` again for good measure. +8. Return the final dict. **`_load_library_client` fork point** (in `src/client/ogx.py`). The check is "is there a synthesis input?", which spans the root `inference.providers` @@ -591,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 ... ``` @@ -632,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 ... ``` @@ -655,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 9d695cc013..30bb77be06 100644 --- a/docs/devel_doc/container_orchestration.md +++ b/docs/devel_doc/container_orchestration.md @@ -298,6 +298,9 @@ The Makefile passes these environment variables to the OGX container: #### OKP/Solr RAG Configuration +> [!WARNING] +> **Deprecated:** The Solr vector provider used by OKP RAG is deprecated. + **For OKP (Offline Knowledge Portal) RAG:** - `RH_SERVER_OKP`: OKP server URL (e.g., `http://localhost:8081`) - `SOLR_URL`: Solr server URL @@ -311,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 8ea645ece9..809811c90c 100644 --- a/docs/devel_doc/openapi.json +++ b/docs/devel_doc/openapi.json @@ -13,7 +13,7 @@ "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - "version": "0.7.0" + "version": "0.7.1" }, "servers": [ { @@ -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, @@ -16286,7 +16292,7 @@ "additionalProperties": false, "type": "object", "title": "OkpConfiguration", - "description": "OKP (Offline Knowledge Portal) provider configuration.\n\nControls provider-specific behaviour for the OKP vector store.\nOnly relevant when ``\"okp\"`` is listed in ``rag.retrieval.inline.sources``\nor ``rag.retrieval.tool.sources``." + "description": "OKP (Offline Knowledge Portal) provider configuration.\n\nDeprecated: The Solr vector provider used by OKP RAG is deprecated.\n\nControls provider-specific behaviour for the OKP vector store.\nOnly relevant when ``\"okp\"`` is listed in ``rag.retrieval.inline.sources``\nor ``rag.retrieval.tool.sources``." }, "OpenAIResponseAnnotationCitation": { "properties": { @@ -19680,7 +19686,7 @@ "okp": { "$ref": "#/components/schemas/OkpConfiguration", "title": "OKP configuration", - "description": "OKP provider settings. Only used when 'okp' is listed in retrieval.inline.sources or retrieval.tool.sources." + "description": "Deprecated: The Solr vector provider used by OKP RAG is deprecated. OKP provider settings. Only used when 'okp' is listed in retrieval.inline.sources or retrieval.tool.sources." }, "retrieval": { "$ref": "#/components/schemas/RetrievalConfiguration", @@ -22073,7 +22079,7 @@ "additionalProperties": false, "type": "object", "title": "SolrVectorSearchRequest", - "description": "LCORE Solr inline RAG options for vector_io.query (mode and provider filters).\n\nAttributes:\n mode: Solr vector_io search mode. When omitted, the configured OKP default is used.\n filters: Solr provider filter payload passed through as params['solr'].\n\nLegacy clients may send a plain JSON object with filter keys only;\nthat object is accepted as filters with mode unset (server default applies)." + "description": "LCORE Solr inline RAG options for vector_io.query (mode and provider filters).\n\nDeprecated: The Solr vector provider used by OKP RAG is deprecated.\n\nAttributes:\n mode: Solr vector_io search mode. When omitted, the configured OKP default is used.\n filters: Solr provider filter payload passed through as params['solr'].\n\nLegacy clients may send a plain JSON object with filter keys only;\nthat object is accepted as filters with mode unset (server default applies)." }, "SplunkConfiguration": { "properties": { diff --git a/docs/devel_doc/openapi.md b/docs/devel_doc/openapi.md index ff872ff8c1..0e14d59633 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 4320a4b552..afed57998e 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/migrations/v0.7.0.md b/docs/migrations/v0.7.0.md index 382ecaf4d3..a6fdceeba5 100644 --- a/docs/migrations/v0.7.0.md +++ b/docs/migrations/v0.7.0.md @@ -1,5 +1,8 @@ # Migrating to v0.7.0 +> [!WARNING] +> **Deprecated:** The Solr vector provider used by OKP RAG is deprecated. + ## Table of Contents * [RAG Configuration](#rag-configuration) @@ -27,6 +30,8 @@ API response fields were renamed without backward-compatible aliases. ```yaml llama_stack: url: http://localhost:8321 + config: + profile: default ``` **After (v0.7.0 — preferred):** @@ -34,6 +39,8 @@ llama_stack: ```yaml ogx: url: http://localhost:8321 + config: + profile: default ``` **Still accepted in v0.7.0 (deprecated):** @@ -41,6 +48,8 @@ ogx: ```yaml llama_stack: url: http://localhost:8321 + config: + profile: default ``` A startup warning is logged when the deprecated `llama_stack` key is used. diff --git a/docs/models/common.json b/docs/models/common.json index df1bbcd4eb..fec4c9f5ea 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 79ddaf4339..bf22405eba 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 e5b0936472..e7d43a633e 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 247cdca3d8..80675365cc 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 4a56726b38..4eb2679992 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 70c737f2d7..53bd69d43e 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 cca5a265ef..f9bf763414 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 f3bee36f4d..ac5dec1aa9 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 0000000000..2b46368d8d --- /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 cb8890e9fa..4a2365eaf5 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 c873028e9e..eecd3653af 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 b73aeaa853..e9bf51509d 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 e7f5221fa3..6611cbdc3d 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 @@ -136,10 +136,6 @@ The `buffer_turns` setting specifies a target number of recent turns to preserve When compaction occurs, a marker message (prefixed with `[lightspeed:compaction-summary]`) is appended to the conversation in OGX. This marker serves as a fallback for reconstructing the compacted state in cache-less deployments or after cache eviction. -The marker is internal bookkeeping, not something the user said. It stays in storage, but `GET /v1/conversations/{conversation_id}` leaves it out of the returned chat history, so clients see only the turns the user and the assistant exchanged. `GET /v2/conversations/{conversation_id}` reads the conversation cache, which never holds markers. - -The prefix is reserved. A user message that begins with `[lightspeed:compaction-summary]` cannot be told apart from a marker, so `GET /v1/conversations/{conversation_id}` leaves it out as well, whether or not compaction is enabled. - ### Per-conversation locking Compaction acquires a per-conversation lock to prevent concurrent requests on the same conversation from racing during summarization. If multiple requests arrive simultaneously, they are serialized. The lock is automatically released after processing. @@ -158,7 +154,7 @@ When compaction is disabled (the default), requests that cause the conversation **Does compaction lose information?** -Compaction summarizes older turns, so fine-grained details from early in the conversation may be condensed. The full original conversation history remains stored in OGX and is retrievable via the conversations API; the summaries themselves are not part of that history and are not returned. The LLM simply receives a summary instead of the full transcript for inference. +Compaction summarizes older turns, so fine-grained details from early in the conversation may be condensed. The full original conversation history remains stored in OGX and is retrievable via the conversations API. The LLM simply receives a summary instead of the full transcript for inference. **Does compaction use extra tokens?** diff --git a/docs/user_doc/deployment_guide.md b/docs/user_doc/deployment_guide.md index 6840d33de7..9c5ed0dec0 100644 --- a/docs/user_doc/deployment_guide.md +++ b/docs/user_doc/deployment_guide.md @@ -182,46 +182,6 @@ synthesizer evolves. -### High-level inference providers - -LLM providers can be declared directly in `lightspeed-stack.yaml`, in the -top-level `inference.providers` section, without writing any OGX -configuration: - -```yaml -ogx: - use_as_library_client: true - config: - baseline: byo-llm -inference: - default_provider: openai - default_model: gpt-4o-mini - providers: - - type: openai - api_key_env: OPENAI_API_KEY - allowed_models: - - gpt-4o-mini -``` - -`api_key_env` names the environment variable that holds the key. The -synthesized configuration contains only a `${env.OPENAI_API_KEY}` reference; -the value is never written to disk. A complete file is in -[examples/lightspeed-stack-unified-byo-llm.yaml](../../examples/lightspeed-stack-unified-byo-llm.yaml). - -`ogx.config.baseline` selects the starting point of the synthesis: - -| Baseline | Meaning | -|---|---| -| `byo-llm` | The built-in baseline without any LLM provider. Declare yours under `inference.providers`. Recommended. | -| `default` | The built-in baseline including a built-in OpenAI provider that is enabled when `OPENAI_API_KEY` is set. | -| `empty` | Starts from an empty configuration. Used by `--migrate-config`. | - -> [!WARNING] -> The built-in OpenAI provider in `baseline: default` is deprecated in -> release 0.7 (a startup warning is logged) and will be removed in release -> 0.8. Set `baseline: byo-llm` and declare your LLM providers under -> `inference.providers`. - ### OGX as a server When this mode is selected, OGX is started as a separate REST API service. All communication with OGX is performed via REST API calls, which means that OGX can run on a separate machine if needed. @@ -735,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 @@ -1349,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/okp_guide.md b/docs/user_doc/okp_guide.md index 0f44294d17..f2736751f9 100644 --- a/docs/user_doc/okp_guide.md +++ b/docs/user_doc/okp_guide.md @@ -3,6 +3,9 @@ This document explains how to deploy the Offline Knowledge Portal (OKP) as a RAG source and configure Lightspeed Stack and OGX to use it. You will: +> [!WARNING] +> **Deprecated:** The Solr vector provider used by OKP RAG is deprecated. + * Deploy and verify the OKP Solr service * Configure Lightspeed Stack for OKP (inline or tool RAG) * Install dependencies and launch Lightspeed Stack diff --git a/docs/user_doc/rag_guide.md b/docs/user_doc/rag_guide.md index ee6c43cb51..faee5dcd0b 100644 --- a/docs/user_doc/rag_guide.md +++ b/docs/user_doc/rag_guide.md @@ -31,6 +31,9 @@ This document explains how to configure and customize your RAG pipeline. You wil Lightspeed Core Stack (LCS) supports two complementary RAG strategies: +> [!WARNING] +> **Deprecated:** The Solr vector provider used by OKP RAG is deprecated. + - **Inline RAG**: context is fetched from BYOK vector stores and/or OKP and injected before the LLM request. No tool calls are required. - **Tool RAG**: the LLM can call the `file_search` tool during generation to retrieve context on demand from BYOK vector stores and/or OKP. @@ -284,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: [...] @@ -530,4 +533,3 @@ When `vector_store` is configured, `default_provider` overwrites `vector_stores.default_provider_id` and `default_embedding_model` during unified synthesis. Annotation settings are not managed by that enricher — keep them in the OGX baseline/profile or `native_override`. - diff --git a/docs/user_doc/shields_guide.md b/docs/user_doc/shields_guide.md index 93f095c9a9..71bd841942 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/docs/user_doc/splunk.md b/docs/user_doc/splunk.md index 9815597b9c..b693b475f2 100644 --- a/docs/user_doc/splunk.md +++ b/docs/user_doc/splunk.md @@ -85,7 +85,7 @@ Events follow the rlsapi telemetry format for consistency with existing analytic "system_id": "abc-def-123", "total_llm_tokens": 0, "request_id": "req_xyz789", - "cla_version": "CLA/0.7.0", + "cla_version": "CLA/0.7.1", "system_os": "RHEL", "system_version": "9.3", "system_arch": "x86_64" diff --git a/examples/azure-run.yaml b/examples/azure-run.yaml index 17195a8d05..e924eca724 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-a2a-state-pg.yaml b/examples/lightspeed-stack-a2a-state-pg.yaml index 337302cad1..6a6bfa7f45 100644 --- a/examples/lightspeed-stack-a2a-state-pg.yaml +++ b/examples/lightspeed-stack-a2a-state-pg.yaml @@ -9,9 +9,7 @@ service: access_log: true ogx: use_as_library_client: true - # Unified mode: the run.yaml next to this file is the synthesis profile. - config: - profile: run.yaml + library_client_config_path: run.yaml user_data_collection: feedback_enabled: true feedback_storage: "/tmp/data/feedback" diff --git a/examples/lightspeed-stack-a2a-state-sqlite.yaml b/examples/lightspeed-stack-a2a-state-sqlite.yaml index 4e6282b683..adff530ba1 100644 --- a/examples/lightspeed-stack-a2a-state-sqlite.yaml +++ b/examples/lightspeed-stack-a2a-state-sqlite.yaml @@ -9,9 +9,7 @@ service: access_log: true ogx: use_as_library_client: true - # Unified mode: the run.yaml next to this file is the synthesis profile. - config: - profile: run.yaml + library_client_config_path: run.yaml user_data_collection: feedback_enabled: true feedback_storage: "/tmp/data/feedback" diff --git a/examples/lightspeed-stack-azure-entraid-lib.yaml b/examples/lightspeed-stack-azure-entraid-lib.yaml index 2091d0da92..7256cb0351 100644 --- a/examples/lightspeed-stack-azure-entraid-lib.yaml +++ b/examples/lightspeed-stack-azure-entraid-lib.yaml @@ -12,10 +12,7 @@ ogx: # use_as_library_client: false # Alternative for "as library use" use_as_library_client: true - # Unified mode: azure-run.yaml next to this file is the synthesis profile - # (relative paths resolve against this file's directory). - config: - profile: azure-run.yaml + library_client_config_path: examples/azure-run.yaml # url: http://localhost:8321 # api_key: xyzzy user_data_collection: diff --git a/examples/lightspeed-stack-byok-okp-rag.yaml b/examples/lightspeed-stack-byok-okp-rag.yaml index 156038dbae..1d3141f7e5 100644 --- a/examples/lightspeed-stack-byok-okp-rag.yaml +++ b/examples/lightspeed-stack-byok-okp-rag.yaml @@ -52,6 +52,7 @@ rag: db_path: /tmp/kb.faiss score_multiplier: 1.2 # Weight for this vector store's results (Inline RAG only) + # DEPRECATED: The Solr vector provider used by OKP RAG is deprecated. # OKP provider settings (only used when 'okp' is listed in retrieval sources) okp: offline: true # true = use parent_id for source URLs, false = use reference_url diff --git a/examples/lightspeed-stack-conversation-cache-pg.yaml b/examples/lightspeed-stack-conversation-cache-pg.yaml index dab1b447e2..aecbe728bc 100644 --- a/examples/lightspeed-stack-conversation-cache-pg.yaml +++ b/examples/lightspeed-stack-conversation-cache-pg.yaml @@ -8,9 +8,7 @@ service: access_log: true ogx: use_as_library_client: true - # Unified mode: the run.yaml next to this file is the synthesis profile. - config: - profile: run.yaml + library_client_config_path: run.yaml user_data_collection: feedback_enabled: true feedback_storage: "/tmp/data/feedback" diff --git a/examples/lightspeed-stack-conversation-cache-sqlite.yaml b/examples/lightspeed-stack-conversation-cache-sqlite.yaml index 24fd6b80a0..1e766febd9 100644 --- a/examples/lightspeed-stack-conversation-cache-sqlite.yaml +++ b/examples/lightspeed-stack-conversation-cache-sqlite.yaml @@ -8,9 +8,7 @@ service: access_log: true ogx: use_as_library_client: true - # Unified mode: the run.yaml next to this file is the synthesis profile. - config: - profile: run.yaml + library_client_config_path: run.yaml user_data_collection: feedback_enabled: true feedback_storage: "/tmp/data/feedback" diff --git a/examples/lightspeed-stack-shields.yaml b/examples/lightspeed-stack-shields.yaml index 2a9eb4e830..24ebf827bc 100644 --- a/examples/lightspeed-stack-shields.yaml +++ b/examples/lightspeed-stack-shields.yaml @@ -8,9 +8,7 @@ service: access_log: true ogx: use_as_library_client: true - # Unified mode: the run.yaml next to this file is the synthesis profile. - config: - profile: run.yaml + library_client_config_path: run.yaml user_data_collection: feedback_enabled: true feedback_storage: "/tmp/data/feedback" @@ -24,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/lightspeed-stack-skills.yaml b/examples/lightspeed-stack-skills.yaml index af0e104706..f40fd1b2f3 100644 --- a/examples/lightspeed-stack-skills.yaml +++ b/examples/lightspeed-stack-skills.yaml @@ -8,9 +8,7 @@ service: access_log: true ogx: use_as_library_client: true - # Unified mode: the run.yaml next to this file is the synthesis profile. - config: - profile: run.yaml + library_client_config_path: run.yaml user_data_collection: feedback_enabled: true feedback_storage: "/tmp/data/feedback" diff --git a/examples/lightspeed-stack-unified-byo-llm.yaml b/examples/lightspeed-stack-unified-byo-llm.yaml deleted file mode 100644 index 0820d19479..0000000000 --- a/examples/lightspeed-stack-unified-byo-llm.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# Unified single-file configuration: no run.yaml is needed. -# -# Lightspeed Core Stack synthesizes the OGX run configuration at startup from -# this file. `baseline: byo-llm` starts from the built-in baseline without any -# LLM provider ("bring your own LLM"); the providers are declared under the -# top-level `inference.providers` section. -# -# The API key is read from the environment variable named by `api_key_env`; -# its value is never written to disk. -name: Lightspeed Core Service (LCS) -service: - host: localhost - port: 8080 - auth_enabled: false - workers: 1 - color_log: true - access_log: true -ogx: - use_as_library_client: true - config: - baseline: byo-llm -user_data_collection: - feedback_enabled: true - feedback_storage: "/tmp/data/feedback" - transcripts_enabled: true - transcripts_storage: "/tmp/data/transcripts" -authentication: - module: "noop" -inference: - default_provider: openai - default_model: gpt-4o-mini - providers: - - type: openai - api_key_env: OPENAI_API_KEY - allowed_models: - - gpt-4o-mini diff --git a/examples/profiles/openai-remote.yaml b/examples/profiles/openai-remote.yaml index 268fc599dd..ca0a43ed51 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 5150cd9b4c..c2684a1ef3 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 c2b33ead1f..4655463390 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 71b0433997..05f89d7b06 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/conversations_v1.py b/src/app/endpoints/conversations_v1.py index d326ca0c1c..001a03e8b3 100644 --- a/src/app/endpoints/conversations_v1.py +++ b/src/app/endpoints/conversations_v1.py @@ -46,7 +46,6 @@ from models.database.conversations import ( UserConversation, ) -from utils.conversation_compaction import exclude_marker_items from utils.conversations import ( build_conversation_turns_from_items, get_all_conversation_items, @@ -294,11 +293,9 @@ async def get_conversation_endpoint_handler( # pylint: disable=too-many-locals, conversation_id, ) - # Build conversation turns from items and populate turns metadata. - # Compaction summary markers are stored as user messages, but the - # user never sent them, so they are not part of the history. + # Build conversation turns from items and populate turns metadata chat_history = build_conversation_turns_from_items( - exclude_marker_items(items), db_turns, conversation.created_at + items, db_turns, conversation.created_at ) span.set_attribute("conversations.found", True) diff --git a/src/app/endpoints/responses.py b/src/app/endpoints/responses.py index df12cbfe4e..eac32bd582 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 3c6c6355d5..4ae9347c8b 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 8c87112f44..60d059022b 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 809b6963f7..c40d4b7983 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 575d6e5356..00e906760c 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/common/query.py b/src/models/common/query.py index 3facfbf9ce..fbc297eeb8 100644 --- a/src/models/common/query.py +++ b/src/models/common/query.py @@ -137,6 +137,8 @@ def validate_image_attachment(self) -> Self: class SolrVectorSearchRequest(BaseModel): """LCORE Solr inline RAG options for vector_io.query (mode and provider filters). + Deprecated: The Solr vector provider used by OKP RAG is deprecated. + Attributes: mode: Solr vector_io search mode. When omitted, the configured OKP default is used. filters: Solr provider filter payload passed through as params['solr']. diff --git a/src/models/config.py b/src/models/config.py index cedac1ef43..c0ac0de92b 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: @@ -2670,6 +2676,8 @@ def validate_unique_rag_ids(self) -> Self: class OkpConfiguration(ConfigurationBase): """OKP (Offline Knowledge Portal) provider configuration. + Deprecated: The Solr vector provider used by OKP RAG is deprecated. + Controls provider-specific behaviour for the OKP vector store. Only relevant when ``"okp"`` is listed in ``rag.retrieval.inline.sources`` or ``rag.retrieval.tool.sources``. @@ -2730,7 +2738,8 @@ class RagConfiguration(ConfigurationBase): okp: OkpConfiguration = Field( default_factory=OkpConfiguration, title="OKP configuration", - description=f"OKP provider settings. Only used when '{constants.OKP_RAG_ID}' " + description=f"Deprecated: The Solr vector provider used by OKP RAG is deprecated. " + f"OKP provider settings. Only used when '{constants.OKP_RAG_ID}' " "is listed in retrieval.inline.sources or retrieval.tool.sources.", ) diff --git a/src/observability/__init__.py b/src/observability/__init__.py index b00904aabc..8d77c07da9 100644 --- a/src/observability/__init__.py +++ b/src/observability/__init__.py @@ -36,7 +36,7 @@ org_id="12345678", system_id="abc-def-123", request_id="req_xyz789", - cla_version="CLA/0.7.0", + cla_version="CLA/0.7.1", system_os="RHEL", system_version="9.3", system_arch="x86_64", diff --git a/src/utils/pydantic_ai_helpers.py b/src/utils/pydantic_ai_helpers.py index 57861cc99e..42fc348c3e 100644 --- a/src/utils/pydantic_ai_helpers.py +++ b/src/utils/pydantic_ai_helpers.py @@ -53,6 +53,7 @@ def _skills_capability( return SkillsCapability( directories=[str(path) for path in skills_config.paths], validate=False, + exclude_tools=["run_skill_script"], ) diff --git a/src/utils/responses.py b/src/utils/responses.py index 852d10af4d..8775364fc7 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/src/version.py b/src/version.py index 80d90939b9..a89c2a3c3a 100644 --- a/src/version.py +++ b/src/version.py @@ -9,4 +9,4 @@ # [tool.pdm.version] # source = "file" # path = "src/version.py" -__version__ = "0.7.0" +__version__ = "0.7.1" diff --git a/tests/benchmarks/data/python_1000_lines.py b/tests/benchmarks/data/python_1000_lines.py index 3fd21b4cb2..f94a2fd51d 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 6923612ad0..9a02a793f8 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 a336936984..98b789af74 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 19c299ab98..aac2209b5d 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 9ae7b389d9..acca3a2272 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 396b24fcb5..eba50a042d 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 87f6000017..ea199c6c03 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 22fa0caf20..d800664eda 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 72bf84370e..70cc11b0aa 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 7880b4f415..c5c87ff018 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 3448351e49..a556c9cf5b 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 eeae1ec81f..b9a85478bf 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 542e7cc4c1..81bf881889 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 29e377e821..592071d101 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 ba361be6d6..4aa4b45626 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 f410eb44a6..f62cc1b3ee 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 8702c2b5b4..8820e796d1 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 2ee300367b..1d27379af1 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 227792a2f8..f6ee927fa0 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 94675e16c3..b11a3f5bee 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 778c04d940..8d01eb489a 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 6b4ea3c091..8530ef7c79 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 5a5021bf0a..2fc4225aee 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 f93d322972..f9fdd3fa75 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 14342a3ebf..7c282a46ac 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 e7cd3668e7..c5ccd63478 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 cf6dd64b03..d377bd71c2 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 a6e1353066..f5be1b7a25 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 941f5657b3..7d6bbc4a4a 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 ff1e3510cf..880c9ea702 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 82dd0c32bc..de82b73fa0 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 294603b168..aca1c6f955 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 798d407c1d..c7f2097285 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 1b87206346..a259df0cf0 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 f598622ae1..6d00941875 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 31f93aa5da..04044f7c49 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 c72db0572d..c0dbe8f980 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 1a5233af3e..1afecb4d84 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 40220313bc..d3a456f9fe 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 e9966dbf6c..f0a4d9e8cc 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 6418577ce7..d43b7c138b 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 3c7a1b7c45..d203e7af61 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 ece1c9c668..0e64e64222 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 6393142b5b..71488a02df 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 089fb0afbf..46719c545f 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 b3df26828f..8dd94d04df 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 576b1950ac..db8ea6009b 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 731c39b5d3..a465c99dc3 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 228b2d40b2..ac6a12982b 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 18890c9fc6..777756f5aa 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 7f9b31dd55..e9d93b3719 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 34ace93fae..1c182a0ff9 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 7a31fc7af6..068985a403 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 a40465ccac..4226e2c825 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 0647f4c87f..82aefde14a 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/conversation-compaction.feature b/tests/e2e/features/conversation-compaction.feature index 0619851ff3..fcf48771e1 100644 --- a/tests/e2e/features/conversation-compaction.feature +++ b/tests/e2e/features/conversation-compaction.feature @@ -73,7 +73,6 @@ Feature: Conversation compaction | green-harbor | When I use REST API conversation endpoint with conversation_id from above using HTTP GET method Then The status code of the response is 200 - And The conversation history holds 5 turns and no compaction summary marker And The conversation history includes the following user queries | User query | | My OpenShift cluster is named aurora-prod-7 and it runs in the datacenter called north-quarry. Remember both names and reply with OK only. | diff --git a/tests/e2e/features/environment.py b/tests/e2e/features/environment.py index 2080fee9c6..612b43e0d7 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/info.feature b/tests/e2e/features/info.feature index 663d26bb04..bdd924d312 100644 --- a/tests/e2e/features/info.feature +++ b/tests/e2e/features/info.feature @@ -18,10 +18,9 @@ Feature: Info tests Scenario: Check if info endpoint is working When I access REST API endpoint "info" using HTTP GET method Then The status code of the response is 200 - And The body of the response has proper name Lightspeed Core Service (LCS) and version 0.7.0 + And The body of the response has proper name Lightspeed Core Service (LCS) and version 0.7.1 And The body of the response has ogx version 1.2.5 - Scenario: Check if tools endpoint is working When I access REST API endpoint "tools" using HTTP GET method Then The status code of the response is 200 diff --git a/tests/e2e/features/query.feature b/tests/e2e/features/query.feature index c8e98efb55..796ac6d88c 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 4a942ac05d..a717782ff7 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 9427f5447a..f817e8ac86 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/e2e/features/skills.feature b/tests/e2e/features/skills.feature index baec0a0be8..3d919828c6 100644 --- a/tests/e2e/features/skills.feature +++ b/tests/e2e/features/skills.feature @@ -101,37 +101,6 @@ Feature: Agent skills tests "toolgroup_id": "builtin::agent-skills", "server_source": "builtin", "type": "tool" - }, - { - "identifier": "run_skill_script", - "description": "Execute a skill script that performs actions or computations.\n\nScripts are executable programs provided by skills that can perform actions\n(API calls, file operations), process data (transformations, analysis), or\ngenerate outputs (reports, visualizations).\n\nWhen to use this:\n- When a skill's instructions tell you to run a specific script\n- To perform automated tasks that the skill provides\n- For data processing, API interactions, or file operations", - "parameters": [ - { - "name": "skill_name", - "description": "Name of the skill containing the script.", - "parameter_type": "string", - "required": true, - "default": null - }, - { - "name": "script_name", - "description": "Exact name of the script as listed in the skill.\nExamples: \"analyze.py\", \"scripts/analyze.py\", \"scripts/deploy.sh\", \"scripts/runner\"\nMust match exactly - do not infer or guess.", - "parameter_type": "string", - "required": true, - "default": null - }, - { - "name": "args", - "description": "Arguments required by the script.\nKeys must match the parameter names in the script's schema.", - "parameter_type": "object", - "required": false, - "default": null - } - ], - "provider_id": "agent-skills", - "toolgroup_id": "builtin::agent-skills", - "server_source": "builtin", - "type": "tool" } ] } diff --git a/tests/e2e/features/steps/conversation_compaction.py b/tests/e2e/features/steps/conversation_compaction.py index 2367fd8019..d78ac040b1 100644 --- a/tests/e2e/features/steps/conversation_compaction.py +++ b/tests/e2e/features/steps/conversation_compaction.py @@ -19,9 +19,6 @@ from tests.e2e.features.steps.common import get_active_lightspeed_stack_config_basename from tests.e2e.utils.utils import absolute_repo_path, is_prow_environment -COMPACTION_MARKER_PREFIX = "[lightspeed:compaction-summary]" -"""Prefix of the message compaction stores each summary in.""" - def _active_fixture_path(context: Context) -> str: """Resolve the fixture file the last ``The service uses ...`` step applied. @@ -139,35 +136,6 @@ def check_history_includes_user_queries(context: Context) -> None: ) -@then("The conversation history holds {turns:d} turns and no compaction summary marker") -def check_history_holds_no_marker(context: Context, turns: int) -> None: - """Assert the history is the turns that were sent, with no summary marker (LCORE-3909). - - Compaction keeps each summary in the stored conversation as a synthetic - user message that starts with a fixed prefix. The prefix is spelled out - here, not imported, because steps never import from ``src/``. - - The number of turns is checked as well, so the step does not depend on the - prefix alone: a marker is a user message, and every one of them returned - would show up as a turn of its own. - """ - assert context.response is not None, "Request needs to be performed first" - response_json = context.response.json() - assert "chat_history" in response_json, "chat_history not found in response" - chat_history = response_json["chat_history"] - markers = [ - message["content"] - for turn in chat_history - for message in turn.get("messages", []) - if message.get("content", "").startswith(COMPACTION_MARKER_PREFIX) - ] - assert not markers, f"compaction summary markers in the history: {markers!r}" - assert len(chat_history) == turns, ( - f"the history holds {len(chat_history)} turns, expected {turns}: " - f"{[turn.get('messages', []) for turn in chat_history]!r}" - ) - - @then("The streamed response contains a compaction event before the first token") def check_compaction_event_precedes_tokens(context: Context) -> None: """Assert the stream announced compaction before any answer token (R12).""" diff --git a/tests/e2e/features/steps/unified_mode.py b/tests/e2e/features/steps/unified_mode.py index e9541f4257..3e110ed2c1 100644 --- a/tests/e2e/features/steps/unified_mode.py +++ b/tests/e2e/features/steps/unified_mode.py @@ -1,6 +1,6 @@ """Step definitions for the unified-mode e2e features (LCORE-2343). -Only the startup-log evidence step lives here. Everything else the four +Only the startup-log evidence step lives here. Everything else the five ``unified-mode-*.feature`` files need — applying a configuration, restarting containers, hitting ``readiness`` and ``query`` — resolves through the generic steps, and the configuration validation, migration and synthesis assertions diff --git a/tests/integration/endpoints/test_compaction_conversations_api.py b/tests/integration/endpoints/test_compaction_conversations_api.py deleted file mode 100644 index 5de78795fe..0000000000 --- a/tests/integration/endpoints/test_compaction_conversations_api.py +++ /dev/null @@ -1,316 +0,0 @@ -"""Integration tests for compaction markers and the conversations API (LCORE-3909). - -Compaction stores each summary in the conversation as a synthetic user message -(a *marker*). The marker has to stay in storage, because it is the fallback -source of truth when no conversation cache is configured, but it is not -something the user said, so the conversations API must not return it. -""" - -# pylint: disable=too-many-arguments -# pylint: disable=too-many-positional-arguments - -from collections.abc import Generator -from datetime import UTC, datetime - -import pytest -from fastapi import Request -from ogx_api.openai_responses import OpenAIResponseMessage -from pytest_mock import AsyncMockType, MockerFixture -from sqlalchemy.orm import Session - -from app.endpoints.conversations_v1 import ( - get_conversation_endpoint_handler as get_conversation_v1, -) -from app.endpoints.conversations_v2 import ( - get_conversation_endpoint_handler as get_conversation_v2, -) -from app.endpoints.query import query_endpoint_handler -from authentication.interface import AuthTuple -from cache.sqlite_cache import SQLiteCache -from configuration import AppConfig -from models.api.requests import QueryRequest -from models.api.responses.successful import ConversationResponse -from models.compaction import ConversationSummary -from models.config import SQLiteDatabaseConfiguration -from models.database.conversations import UserTurn -from tests.integration.conftest import InMemoryConversationStore -from tests.integration.endpoints._compaction_helpers import ( - CONV_ID_LLAMA, - DEFAULT_MODEL_RESPONSE, - DEFAULT_SUMMARY_TEXT, - EXISTING_CONV_ID, - TEST_MODEL, - assert_marker_count, - create_existing_conversation, - enable_compaction, - marker, - msg, -) -from utils.conversation_compaction import MARKER_SENTINEL - -NEW_QUERY = "What else can you help with?" - - -@pytest.fixture(name="conversation_cache") -def conversation_cache_fixture( - test_config: AppConfig, - mocker: MockerFixture, -) -> Generator[SQLiteCache, None, None]: - """Configure an in-memory SQLite conversation cache. - - The v2 conversations endpoints read this cache, and compaction keeps its - summaries in it when one is configured. - """ - test_config.conversation_cache_configuration.type = "sqlite" - cache = SQLiteCache(SQLiteDatabaseConfiguration(db_path=":memory:")) - cache.connect() - cache.initialize_cache() - mocker.patch.object( - type(test_config), - "conversation_cache", - new_callable=mocker.PropertyMock, - return_value=cache, - ) - yield cache - - -def _returned_messages(response: ConversationResponse) -> list[tuple[str, str]]: - """Flatten a conversation response into ``(type, content)`` pairs.""" - return [ - (message.type, message.content) - for turn in response.chat_history - for message in turn.messages - ] - - -def _add_turn_metadata( - db_session: Session, turn_number: int, provider: str, model: str -) -> None: - """Store the metadata row the service keeps for one completed turn.""" - now = datetime.now(UTC) - db_session.add( - UserTurn( - conversation_id=EXISTING_CONV_ID, - turn_number=turn_number, - started_at=now, - completed_at=now, - provider=provider, - model=model, - ) - ) - db_session.commit() - - -def _stored_turns() -> list[OpenAIResponseMessage]: - """Two turns long enough to cross the compaction threshold of the tests.""" - return [ - msg("user", "question one " * 20), - msg("assistant", "answer one " * 20), - msg("user", "question two " * 20), - msg("assistant", "answer two " * 20), - ] - - -def _fake_summarization( - mocker: MockerFixture, mock_query_agent: AsyncMockType, covered_items: int -) -> None: - """Replace the summarization LLM call and set the answer of the main turn.""" - mock_query_agent.model.last_output_items = [ - OpenAIResponseMessage(role="assistant", content=DEFAULT_MODEL_RESPONSE) - ] - mocker.patch( - "utils.conversation_compaction.summarize_chunk", - new_callable=mocker.AsyncMock, - return_value=ConversationSummary( - summary_text=DEFAULT_SUMMARY_TEXT, - summarized_through_turn=covered_items, - token_count=6, - created_at="2026-08-10T00:00:00Z", - model_used=TEST_MODEL, - ), - ) - - -class TestConversationsApiCompactionMarkers: - """Compaction markers stay in storage and out of the conversation history.""" - - @pytest.mark.asyncio - async def test_marker_written_by_a_compaction_is_not_returned( - self, - test_config: AppConfig, - mock_ogx_client: AsyncMockType, - mock_query_agent: AsyncMockType, - mock_conversation_store: InMemoryConversationStore, - test_request: Request, - test_auth: AuthTuple, - patch_db_session: Session, - mocker: MockerFixture, - ) -> None: - """A request that compacts, then a read of the same conversation. - - The marker is whatever the running compaction writes, not one built by - the test, so the check holds for the shape the service produces. - - Verifies: - - the compacting request stored a marker - - the conversation read returns the three real turns and no marker - - the marker is still in storage after the read - """ - _ = mock_ogx_client - - enable_compaction(test_config) - user_id, _, _, _ = test_auth - create_existing_conversation(patch_db_session, user_id) - - stored_turns = _stored_turns() - await mock_conversation_store.create( - conversation_id=CONV_ID_LLAMA, items=stored_turns - ) - _fake_summarization(mocker, mock_query_agent, len(stored_turns)) - - await query_endpoint_handler( - request=test_request, - query_request=QueryRequest( - query=NEW_QUERY, conversation_id=EXISTING_CONV_ID - ), - auth=test_auth, - mcp_headers={}, - ) - assert_marker_count(mock_conversation_store, CONV_ID_LLAMA, 1) - - response = await get_conversation_v1( - request=test_request, - conversation_id=EXISTING_CONV_ID, - auth=test_auth, - ) - - assert _returned_messages(response) == [ - ("user", "question one " * 20), - ("assistant", "answer one " * 20), - ("user", "question two " * 20), - ("assistant", "answer two " * 20), - ("user", NEW_QUERY), - ("assistant", DEFAULT_MODEL_RESPONSE), - ] - assert len(response.chat_history) == 3 - assert_marker_count(mock_conversation_store, CONV_ID_LLAMA, 1) - - @pytest.mark.asyncio - async def test_stored_markers_of_both_shapes_are_not_returned( - self, - test_config: AppConfig, - mock_ogx_client: AsyncMockType, - mock_conversation_store: InMemoryConversationStore, - non_admin_test_request: Request, - test_auth: AuthTuple, - patch_db_session: Session, - ) -> None: - """Conversations that already hold markers need no migration. - - The stored conversation has a marker without a covered-item count and - one with it, the shape LCORE-4219 introduces. Both are left out on - read, and every turn keeps its own metadata: a marker counted as a turn - would hand each turn the metadata of its neighbour. - """ - _ = test_config - _ = mock_ogx_client - - user_id, _, _, _ = test_auth - create_existing_conversation(patch_db_session, user_id) - for turn_number in (1, 2, 3): - _add_turn_metadata( - patch_db_session, - turn_number, - provider=f"provider-{turn_number}", - model=f"model-{turn_number}", - ) - - await mock_conversation_store.create( - conversation_id=CONV_ID_LLAMA, - items=[ - msg("user", "question one"), - msg("assistant", "answer one"), - marker("summary of turn one"), - msg("user", "question two"), - msg("assistant", "answer two"), - msg("user", f"{MARKER_SENTINEL} [covers:5] summary of turns one, two"), - msg("user", "question three"), - msg("assistant", "answer three"), - ], - ) - - response = await get_conversation_v1( - request=non_admin_test_request, - conversation_id=EXISTING_CONV_ID, - auth=test_auth, - ) - - assert _returned_messages(response) == [ - ("user", "question one"), - ("assistant", "answer one"), - ("user", "question two"), - ("assistant", "answer two"), - ("user", "question three"), - ("assistant", "answer three"), - ] - assert [(turn.provider, turn.model) for turn in response.chat_history] == [ - ("provider-1", "model-1"), - ("provider-2", "model-2"), - ("provider-3", "model-3"), - ] - assert_marker_count(mock_conversation_store, CONV_ID_LLAMA, 2) - - @pytest.mark.asyncio - async def test_v2_history_holds_no_marker_after_a_compaction( - self, - test_config: AppConfig, - mock_ogx_client: AsyncMockType, - mock_query_agent: AsyncMockType, - mock_conversation_store: InMemoryConversationStore, - conversation_cache: SQLiteCache, - test_request: Request, - test_auth: AuthTuple, - patch_db_session: Session, - mocker: MockerFixture, - ) -> None: - """The v2 endpoint reads the conversation cache, which never holds a marker. - - With a cache configured, a compaction keeps its summary there as well. - The summary goes to the cache's own summary storage and the turn is - cached with the query as the user sent it, so the v2 history holds the - turn and nothing else. - """ - _ = mock_ogx_client - - enable_compaction(test_config) - user_id, _, _, _ = test_auth - create_existing_conversation(patch_db_session, user_id) - - stored_turns = _stored_turns() - await mock_conversation_store.create( - conversation_id=CONV_ID_LLAMA, items=stored_turns - ) - _fake_summarization(mocker, mock_query_agent, len(stored_turns)) - - await query_endpoint_handler( - request=test_request, - query_request=QueryRequest( - query=NEW_QUERY, conversation_id=EXISTING_CONV_ID - ), - auth=test_auth, - mcp_headers={}, - ) - assert_marker_count(mock_conversation_store, CONV_ID_LLAMA, 1) - summaries = conversation_cache.get_summaries(user_id, CONV_ID_LLAMA, False) - assert [summary.summary_text for summary in summaries] == [DEFAULT_SUMMARY_TEXT] - - response = await get_conversation_v2( - request=test_request, - conversation_id=EXISTING_CONV_ID, - auth=test_auth, - ) - - assert _returned_messages(response) == [ - ("user", NEW_QUERY), - ("assistant", DEFAULT_MODEL_RESPONSE), - ] diff --git a/tests/integration/test_unified_synthesis.py b/tests/integration/test_unified_synthesis.py index 0d5f9adbaa..cc0fbe3ccf 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_conversations.py b/tests/unit/app/endpoints/test_conversations.py index b8018cadcf..c1898b7d37 100644 --- a/tests/unit/app/endpoints/test_conversations.py +++ b/tests/unit/app/endpoints/test_conversations.py @@ -38,7 +38,6 @@ from models.config import Action from models.database.conversations import UserConversation, UserTurn from tests.unit.utils.auth_helpers import mock_authorization_resolvers -from utils.conversation_compaction import MARKER_SENTINEL from utils.conversations import build_conversation_turns_from_items MOCK_AUTH = ("mock_user_id", "mock_username", False, "mock_token") @@ -760,77 +759,6 @@ async def test_successful_conversation_retrieval( ] assert actual_history == expected_chat_history - @pytest.mark.asyncio - async def test_compaction_markers_are_not_returned( - self, - mocker: MockerFixture, - setup_configuration: AppConfig, - expected_chat_history: list[dict[str, Any]], - dummy_request: Request, - mock_conversation: MockType, - ) -> None: - """Test that compaction summary markers are left out of the chat history. - - Compaction stores each summary as a synthetic user message (LCORE-3909). - Both marker shapes are covered: the one recording a covered-item count - (LCORE-4219) and the older one without it. Every turn also has to keep - its own metadata, which a marker counted as a turn would shift. - """ - mock_authorization_resolvers(mocker) - mocker.patch( - "app.endpoints.conversations_v1.configuration", setup_configuration - ) - mocker.patch("app.endpoints.conversations_v1.check_suid", return_value=True) - - mock_db_turns = [ - create_mock_db_turn( - mocker, 1, "2024-01-01T00:01:00Z", "2024-01-01T00:01:05Z" - ), - create_mock_db_turn( - mocker, 2, "2024-01-01T00:02:00Z", "2024-01-01T00:02:03Z" - ), - ] - mock_database_session( - mocker, query_result=[mock_conversation], db_turns=mock_db_turns - ) - - mock_client = mocker.AsyncMock() - mock_items = mocker.Mock() - mock_items.data = [ - mocker.Mock(type="message", role="user", content="Hello"), - mocker.Mock(type="message", role="assistant", content="Hi there!"), - mocker.Mock( - type="message", - role="user", - content=f"{MARKER_SENTINEL} summary of the greeting", - ), - mocker.Mock(type="message", role="user", content="How are you?"), - mocker.Mock( - type="message", role="assistant", content="I'm doing well, thanks!" - ), - mocker.Mock( - type="message", - role="user", - content=f"{MARKER_SENTINEL} [covers:5] summary of both turns", - ), - ] - mock_items.has_more = False - mock_client.items.list = mocker.AsyncMock(return_value=mock_items) - - mock_client_holder = mocker.patch( - "app.endpoints.conversations_v1.AsyncOgxClientHolder" - ) - mock_client_holder.return_value.get_client.return_value = mock_client - - response = await get_conversation_endpoint_handler( - request=dummy_request, conversation_id=VALID_CONVERSATION_ID, auth=MOCK_AUTH - ) - - actual_history = [ - turn.model_dump(exclude_none=True) for turn in response.chat_history - ] - assert actual_history == expected_chat_history - @pytest.mark.asyncio async def test_retrieve_conversation_returns_none( self, diff --git a/tests/unit/app/endpoints/test_responses.py b/tests/unit/app/endpoints/test_responses.py index 27da0c1a1a..28f3aab5a4 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 f223aaec53..1e07b09591 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 d3869d188f..bbd4c472f6 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/app/endpoints/test_tools.py b/tests/unit/app/endpoints/test_tools.py index 1a0e9ff41d..1403997b58 100644 --- a/tests/unit/app/endpoints/test_tools.py +++ b/tests/unit/app/endpoints/test_tools.py @@ -353,7 +353,6 @@ async def test_tools_endpoint_includes_agent_capability_tools( assert "list_skills" in tool_ids assert "load_skill" in tool_ids assert "read_skill_resource" in tool_ids - assert "run_skill_script" in tool_ids list_skills = next( tool for tool in response.tools if tool.identifier == "list_skills" diff --git a/tests/unit/cache/test_in_memory_cache.py b/tests/unit/cache/test_in_memory_cache.py index 2b11aace59..ea24cc436a 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 36fb82da23..79f7a2aedf 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 0ce2015c6e..604ebba8f5 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 2fdb9ba1a2..3a3e1eab9b 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 71fc9cf1d3..f01c9e2e93 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 0fcabed965..66907f883d 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 4d8e1e69a7..ec5d7ac1c3 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 8f323e8ef1..d409bd8b2d 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 f20ba5b558..7379e14998 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/observability/formats/test_rlsapi.py b/tests/unit/observability/formats/test_rlsapi.py index 0ef21e3588..1163952c16 100644 --- a/tests/unit/observability/formats/test_rlsapi.py +++ b/tests/unit/observability/formats/test_rlsapi.py @@ -65,7 +65,7 @@ def test_builds_event_with_token_counts(mocker: MockerFixture) -> None: org_id="org1", system_id="sys1", request_id="req_1", - cla_version="CLA/1.0", + cla_version=f"CLA/{__version__}", system_os="RHEL", system_version="9.4", system_arch="x86_64", 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 f35a0a551a..c8cc3a8c3f 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 2b44259648..d8b394cd78 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 a13f436471..f1ef7d39ca 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 0912a34c32..2f68f5f76b 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 6df3a10f4d..2f711a3aa5 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 2cefb84686..4f3e2ae3f3 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 2dce7605a9..8226bf35c1 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 0eda1dcce0..300879bad2 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 b5bea40f7f..e689208e6e 100644 --- a/tests/unit/utils/test_conversation_compaction.py +++ b/tests/unit/utils/test_conversation_compaction.py @@ -9,10 +9,7 @@ import pytest from fastapi import HTTPException -from ogx_api.openai_responses import ( - OpenAIResponseInputMessageContentText, - OpenAIResponseMessage, -) +from ogx_api.openai_responses import OpenAIResponseMessage from pytest_mock import MockerFixture from models.common.responses.responses_api_params import ResponsesApiParams @@ -20,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" @@ -810,70 +807,3 @@ async def holder() -> None: except asyncio.CancelledError: pass assert "conv_cancel" not in cc._conversation_locks - - -# --- markers are bookkeeping, not conversation history (LCORE-3909) --- - - -def test_exclude_marker_items_drops_both_marker_shapes() -> None: - """Markers are dropped whether or not they record a covered-item count. - - LCORE-4219 introduces the shape `` [covers:N] ``; the - markers already stored read `` ``. The turns around - them are returned unchanged and in order. - """ - turns = [ - _msg("user", "first question"), - _msg("assistant", "first answer"), - _msg("user", "second question"), - _msg("assistant", "second answer"), - ] - counted_marker = _msg("user", f"{cc.MARKER_SENTINEL} [covers:2] newer summary") - items = [*turns[:2], _marker("older summary"), *turns[2:], counted_marker] - - assert cc.exclude_marker_items(items) == turns - - -def test_exclude_marker_items_drops_marker_with_content_parts() -> None: - """A marker is recognized when its content is a list of parts, not a string.""" - marker = OpenAIResponseMessage( - role="user", - content=[ - OpenAIResponseInputMessageContentText( - text=f"{cc.MARKER_SENTINEL} summary kept as a content part" - ) - ], - ) - question = _msg("user", "a question") - - assert cc.exclude_marker_items([marker, question]) == [question] - - -def test_exclude_marker_items_keeps_everything_else() -> None: - """Tool items and messages that only mention the sentinel are not markers.""" - items = [ - _msg("user", f"what does {cc.MARKER_SENTINEL} mean?"), - SimpleNamespace(type="function_call", name="lookup"), - _msg("assistant", "it is an internal prefix"), - ] - - assert cc.exclude_marker_items(items) == items - - -def test_exclude_marker_items_keeps_other_roles() -> None: - """Markers are written as user messages, so an answer is never one. - - An answer that happens to start with the sentinel is something the - assistant said, and the history has to show it. - """ - items = [ - _msg("user", "which prefix marks a summary?"), - _msg("assistant", f"{cc.MARKER_SENTINEL} is the prefix"), - ] - - assert cc.exclude_marker_items(items) == items - - -def test_exclude_marker_items_empty() -> None: - """An empty conversation stays empty.""" - assert cc.exclude_marker_items([]) == [] diff --git a/tests/unit/utils/test_otel_tracing.py b/tests/unit/utils/test_otel_tracing.py index fc916139a3..a88ded9e42 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_pydantic_ai.py b/tests/unit/utils/test_pydantic_ai.py index 2ed416dea4..39c2a19e06 100644 --- a/tests/unit/utils/test_pydantic_ai.py +++ b/tests/unit/utils/test_pydantic_ai.py @@ -458,7 +458,6 @@ def test_returns_skills_tools_when_configured( "list_skills", "load_skill", "read_skill_resource", - "run_skill_script", ] assert all( tool.provider_id == "agent-skills" diff --git a/tests/unit/utils/test_responses.py b/tests/unit/utils/test_responses.py index 4e178c674c..a3681e1d86 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 2cc9bf9648..f02f1915d4 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 fe62157bf8..f4d95fb5ad 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