From b26fa7092a62a8dfe68d06c37b4d99fc96c310f5 Mon Sep 17 00:00:00 2001 From: Sushant Gautam Date: Tue, 6 Oct 2026 11:18:03 +0200 Subject: [PATCH] feat(chat): enable bounded GenAI content capture --- .env.example | 12 ++++++++++++ chat/config.py | 2 +- chat/proxy.py | 2 +- deploy/openwebui-subpath/Caddyfile.subpath | 2 +- deploy/openwebui-subpath/README.md | 2 +- docker-compose.yml | 14 +++++++++++++- docs/chat.md | 13 +++++++++++++ pyproject.toml | 7 ++++--- uv.lock | 10 +++++----- 9 files changed, 51 insertions(+), 13 deletions(-) diff --git a/.env.example b/.env.example index fa2c1383..b8b4526f 100644 --- a/.env.example +++ b/.env.example @@ -116,6 +116,18 @@ SIMPLEAUDIT_STUDIO_URL=http://localhost:8000 #SIMPLEAUDIT_CHAT_OTLP_ENDPOINT=http://web:8000/otlp/v1/traces #OTEL_BASIC_AUTH_USERNAME= #OTEL_BASIC_AUTH_PASSWORD= +# Content capture is enabled for the protected Studio OTLP receiver when chat +# tracing is enabled. Set any flag to false to exclude that content type. +OTEL_GENAI_CAPTURE_INPUTS=true +OTEL_GENAI_CAPTURE_OUTPUTS=true +OTEL_GENAI_CAPTURE_SYSTEM_INSTRUCTIONS=true +OTEL_GENAI_CAPTURE_TOOL_ARGUMENTS=true +OTEL_GENAI_CAPTURE_TOOL_RESULTS=true +OTEL_GENAI_CAPTURE_RETRIEVAL_QUERY=true +OTEL_GENAI_CAPTURE_RETRIEVAL_DOCUMENTS=true +OTEL_GENAI_CAPTURE_EMBEDDING_TEXT=true +OTEL_GENAI_CAPTURE_EMBEDDING_VECTORS=false +OTEL_GENAI_CONTENT_MAX_LENGTH=2000 # Optional: switch Studio's OTLP listener (POST /otlp/v1/traces + credential # management) on or off. ON by default (preserves existing behavior). Set to # "off" to 404 the /otlp/* and /api/otlp/* routes and hide the OTLP button. diff --git a/chat/config.py b/chat/config.py index a3558a7c..af081f78 100644 --- a/chat/config.py +++ b/chat/config.py @@ -57,7 +57,7 @@ def is_disabled(value: str | None) -> bool: ENABLED = not is_disabled(MODE) #: The base path the Open WebUI subpath build serves at. Must match the -#: WEBUI_SUBPATH baked into the image (0.11.4.1-subpath -> /chat). Defined +#: WEBUI_SUBPATH baked into the image (0.11.4.3-subpath -> /chat). Defined #: first: UPSTREAM below bakes it into the default. SUBPATH = os.environ.get("SIMPLEAUDIT_CHAT_SUBPATH", "/chat") if not SUBPATH.startswith("/"): diff --git a/chat/proxy.py b/chat/proxy.py index bb4c0f98..7b77da1b 100644 --- a/chat/proxy.py +++ b/chat/proxy.py @@ -575,7 +575,7 @@ def start_open_webui(studio_port: int | None = None) -> subprocess.Popen: OWUI_WHEEL_URL = os.environ.get( "SIMPLEAUDIT_CHAT_WHEEL", "https://github.com/SushantGautam/open-webui/releases/download/" - "v0.11.4.1-subpath/open_webui-0.11.4.1-py3-none-any.whl", + "v0.11.4.3-subpath/open_webui-0.11.4.3-py3-none-any.whl", ) OWUI_PACKAGE = os.environ.get("SIMPLEAUDIT_CHAT_PACKAGE", "open-webui") diff --git a/deploy/openwebui-subpath/Caddyfile.subpath b/deploy/openwebui-subpath/Caddyfile.subpath index 35cb2c02..f4b4ec8b 100644 --- a/deploy/openwebui-subpath/Caddyfile.subpath +++ b/deploy/openwebui-subpath/Caddyfile.subpath @@ -3,7 +3,7 @@ # # The routing shape enabled by the subpath fork built via # deploy/openwebui-subpath/build.sh (published as -# ghcr.io/sushantgautam/open-webui:v0.11.4.1-subpath). It replaces the old +# ghcr.io/sushantgautam/open-webui:v0.11.4.3-subpath). It replaces the old # cross-origin iframe model (Caddyfile.chat on :8801) with one origin / one # port: # diff --git a/deploy/openwebui-subpath/README.md b/deploy/openwebui-subpath/README.md index 455fce11..f433215b 100644 --- a/deploy/openwebui-subpath/README.md +++ b/deploy/openwebui-subpath/README.md @@ -130,7 +130,7 @@ re-passes. Distribution context (fork, ghcr.io, pip caveats): [`DESIGN.md`](./DE publishing to ghcr.io (root on push, `-subpath` on `webui_subpath` dispatch). The cutover is done: `docker-compose.yml` pulls -`ghcr.io/sushantgautam/open-webui:v0.11.4.1-subpath` (set `WEBUI_SUBPATH=/chat`), +`ghcr.io/sushantgautam/open-webui:v0.11.4.3-subpath` (set `WEBUI_SUBPATH=/chat`), `chat-proxy` (Caddy, no profile) is the **only published port** and runs `Caddyfile.subpath`, Studio's wrapper page moved to `/playground/`, and `Caddyfile.chat` + the separate cross-origin port are retired. Embedded diff --git a/docker-compose.yml b/docker-compose.yml index 78819a39..6e364cb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -187,7 +187,7 @@ services: # forward /chat/* WITHOUT stripping the prefix (see the Caddyfile header). open-webui: profiles: ["chat"] - image: ${OPEN_WEBUI_IMAGE:-ghcr.io/sushantgautam/open-webui:v0.11.4.1-subpath} + image: ${OPEN_WEBUI_IMAGE:-ghcr.io/sushantgautam/open-webui:v0.11.4.3-subpath} environment: WEBUI_SUBPATH: /chat WEBUI_AUTH_TRUSTED_EMAIL_HEADER: X-Studio-Email @@ -213,6 +213,18 @@ services: OTEL_OTLP_SPAN_EXPORTER: "http" OTEL_EXPORTER_OTLP_ENDPOINT: ${SIMPLEAUDIT_CHAT_OTLP_ENDPOINT:-http://web:8000/otlp/v1/traces} OTEL_SERVICE_NAME: ${SIMPLEAUDIT_CHAT_OTLP_SERVICE_NAME:-open-webui} + # M13 content capture: opt in only for this protected Studio receiver. + # Keep vectors off; captured values are truncated by Open WebUI. + OTEL_GENAI_CAPTURE_INPUTS: ${OTEL_GENAI_CAPTURE_INPUTS:-true} + OTEL_GENAI_CAPTURE_OUTPUTS: ${OTEL_GENAI_CAPTURE_OUTPUTS:-true} + OTEL_GENAI_CAPTURE_SYSTEM_INSTRUCTIONS: ${OTEL_GENAI_CAPTURE_SYSTEM_INSTRUCTIONS:-true} + OTEL_GENAI_CAPTURE_TOOL_ARGUMENTS: ${OTEL_GENAI_CAPTURE_TOOL_ARGUMENTS:-true} + OTEL_GENAI_CAPTURE_TOOL_RESULTS: ${OTEL_GENAI_CAPTURE_TOOL_RESULTS:-true} + OTEL_GENAI_CAPTURE_RETRIEVAL_QUERY: ${OTEL_GENAI_CAPTURE_RETRIEVAL_QUERY:-true} + OTEL_GENAI_CAPTURE_RETRIEVAL_DOCUMENTS: ${OTEL_GENAI_CAPTURE_RETRIEVAL_DOCUMENTS:-true} + OTEL_GENAI_CAPTURE_EMBEDDING_TEXT: ${OTEL_GENAI_CAPTURE_EMBEDDING_TEXT:-true} + OTEL_GENAI_CAPTURE_EMBEDDING_VECTORS: ${OTEL_GENAI_CAPTURE_EMBEDDING_VECTORS:-false} + OTEL_GENAI_CONTENT_MAX_LENGTH: ${OTEL_GENAI_CONTENT_MAX_LENGTH:-2000} volumes: - open_webui_data:/app/backend/data restart: unless-stopped diff --git a/docs/chat.md b/docs/chat.md index 6d7f5397..f8c8138a 100644 --- a/docs/chat.md +++ b/docs/chat.md @@ -229,6 +229,16 @@ unset: the iframe loads the same-origin `/chat/`. | `SIMPLEAUDIT_CHAT_OTLP` | `false` | export Open WebUI's spans to Studio's OTLP listener | | `SIMPLEAUDIT_CHAT_OTLP_ENDPOINT`| full OTLP listener URL | the `/otlp/v1/traces` ingestion URL (embedded: `http://127.0.0.1:/otlp/v1/traces`, docker: `http://web:8000/otlp/v1/traces`) — the exporter uses it as-is | | `SIMPLEAUDIT_CHAT_OTLP_SERVICE_NAME` | `open-webui` | the service name Open WebUI tags its spans with | +| `OTEL_GENAI_CAPTURE_INPUTS` | `true` in the Compose example | capture model input messages | +| `OTEL_GENAI_CAPTURE_OUTPUTS` | `true` in the Compose example | capture model output messages | +| `OTEL_GENAI_CAPTURE_SYSTEM_INSTRUCTIONS` | `true` in the Compose example | capture system instructions | +| `OTEL_GENAI_CAPTURE_TOOL_ARGUMENTS` | `true` in the Compose example | capture tool arguments | +| `OTEL_GENAI_CAPTURE_TOOL_RESULTS` | `true` in the Compose example | capture tool results | +| `OTEL_GENAI_CAPTURE_RETRIEVAL_QUERY` | `true` in the Compose example | capture retrieval queries | +| `OTEL_GENAI_CAPTURE_RETRIEVAL_DOCUMENTS` | `true` in the Compose example | capture retrieved document content | +| `OTEL_GENAI_CAPTURE_EMBEDDING_TEXT` | `true` in the Compose example | capture embedding input text | +| `OTEL_GENAI_CAPTURE_EMBEDDING_VECTORS` | `false` | capture embedding vectors | +| `OTEL_GENAI_CONTENT_MAX_LENGTH` | `2000` | maximum characters per captured field | ## Exporting Open WebUI's spans to Studio (OTLP) @@ -263,6 +273,9 @@ So enabling it is one line: ```bash # .env (docker) — or the equivalent environment in embedded mode SIMPLEAUDIT_CHAT_OTLP=true +# The Compose example enables bounded GenAI content capture for the protected +# Studio receiver. Keep the receiver access-controlled and turn individual +# flags off when the corresponding content must not leave Open WebUI. ``` For an authenticated target (a `basic` or `bearer` OTLP credential instead of a diff --git a/pyproject.toml b/pyproject.toml index 2e1c711b..df46ad5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "simpleaudit-studio" -version = "0.5.7" +version = "0.5.8" description = "Production AI model audit platform — one-liner demo mode via uvx" readme = "README.md" requires-python = ">=3.11" @@ -24,8 +24,9 @@ dependencies = [ # package metadata, so the engine must be a registry dependency. 0.2.0 is # the first release containing run_scenario_reps (n_repetitions support); # 0.2.1 adds the native on_turn per-phase progress callback; 0.3.1 adds - # the traceparent-to-wire forwarding this repo's tracing relies on. - "simpleaudit==0.3.1", + # the traceparent-to-wire forwarding this repo's tracing relies on; + # 0.3.2 also preserves GenAI content attributes through OTLP parsing. + "simpleaudit==0.3.2", "cronsim==2.7", "psutil>=7.2.2", "opentelemetry-proto>=1.45.0", diff --git a/uv.lock b/uv.lock index 82833efe..4d1be32e 100644 --- a/uv.lock +++ b/uv.lock @@ -2410,21 +2410,21 @@ wheels = [ [[package]] name = "simpleaudit" -version = "0.3.1" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "any-llm-sdk" }, { name = "fsspec" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/9c/9648e1f09e236701e49f90beec8870c121c9c22b924c0fb7222ac5494c4d/simpleaudit-0.3.1.tar.gz", hash = "sha256:070e0522aba0954e013af4e66a06f77782172cfe848d119bd88cde4801ffd048", size = 1478920, upload-time = "2026-10-02T10:42:52.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/a3/c39f62c4326e404666430dab361aef5bf1482e972a923594d8cb13b0167e/simpleaudit-0.3.2.tar.gz", hash = "sha256:f16dcd25c6bb5099be466a4a62db4377028f1237de82ace989a50e5b48164793", size = 1479901, upload-time = "2026-10-06T08:51:55.177Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/26/b0506c7e43f72bab61e228b02b6e4443ce4a4ab9568e4c5cfdfa174168e0/simpleaudit-0.3.1-py3-none-any.whl", hash = "sha256:059443b8eba0fa0f7b8fc00cfd674436d405d8341219abc8eb2b0ed40bc654ad", size = 1362230, upload-time = "2026-10-02T10:42:50.279Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cc/da63fef79152ed5fae292c5dc3c041de085ef229365ebed274691d106b31/simpleaudit-0.3.2-py3-none-any.whl", hash = "sha256:8dd251854f7abad67abf6ab334611792b827bc911ee5c44a89327670ec5daee5", size = 1362503, upload-time = "2026-10-06T08:51:53.539Z" }, ] [[package]] name = "simpleaudit-studio" -version = "0.5.7" +version = "0.5.8" source = { virtual = "." } dependencies = [ { name = "caddyserver" }, @@ -2489,7 +2489,7 @@ requires-dist = [ { name = "requests", specifier = "==2.32.5" }, { name = "ruff", marker = "extra == 'dev'" }, { name = "sentry-sdk", extras = ["django"], specifier = ">=2.0" }, - { name = "simpleaudit", specifier = "==0.3.1" }, + { name = "simpleaudit", specifier = "==0.3.2" }, { name = "workos", specifier = "==10.4.0" }, ] provides-extras = ["postgres", "server", "dev", "e2e"]