Skip to content

Commit 5ad4753

Browse files
lesebclaude
andauthored
refactor!: remove Safety API and replace with moderation_endpoint (#5291)
## Summary Remove the entire Safety API subsystem and replace it with a single `moderation_endpoint` config field on the responses provider. Guardrails are now a simple boolean toggle. ### Before ``` Client → POST /v1/moderations → SafetyRouter → ShieldsRoutingTable → Provider → external service Client → Responses API (guardrails=["llama-guard"]) → safety_api.run_moderation() → same chain ``` ### After ``` Client → Responses API (guardrails=true) → httpx.post(moderation_endpoint) → external service ``` No proxy endpoint, no routing table, no provider abstraction, no model IDs. One config field, one boolean, one HTTP call. ### What's removed - **`Api.safety` and `Api.shields`** — enum values, protocol definitions, routing tables, routers - **`/v1/moderations`** — standalone endpoint (users call external moderation services directly) - **`/v1/shields`** — shield introspection endpoints - **`/v1/safety/run-shield`** — native shield endpoint - **All safety providers** — `inline::llama-guard`, `inline::code-scanner`, `inline::prompt-guard`, `remote::bedrock`, `remote::nvidia`, `remote::sambanova`, `remote::passthrough` - **Safety infrastructure** — `SafetyRouter`, `ShieldsRoutingTable`, `SafetyConfig`, `ShieldStore`, `ShieldToModerationMixin`, `ShieldRunnerMixin`, `ResponseGuardrailSpec` - **Shield registration endpoints** — `POST /v1/shields`, `DELETE /v1/shields/{identifier}` ### What's added - **`moderation_endpoint`** on `BuiltinResponsesImplConfig` — URL of an OpenAI-compatible `/v1/moderations` endpoint. Documented contract: accepts `POST {"input": "text"}`, returns `{"results": [{"flagged": bool, "categories": {...}}]}` - **Response format validation** — logs a warning with a link to the OpenAI moderations docs if the endpoint returns an unexpected format - **`guardrails: bool`** — simplified from a list of model IDs to a boolean toggle via `extra_body` - **`x-extra-body-field`** — new OpenAPI generator mechanism to keep `guardrails` typed server-side while hidden from the public schema ### Configuration ```yaml providers: responses: - provider_id: builtin provider_type: inline::builtin config: moderation_endpoint: "https://api.openai.com/v1/moderations" ``` ```python client.responses.create( model="gpt-4o", input="Hello", extra_body={"guardrails": True}, ) ``` ### Rationale - `/v1/moderations` as a passthrough adds a network hop for zero value — clients can call any moderation service directly - The only server-side value is guardrails during generation (mid-generation moderation checks aren't composable from the client). This is now a direct HTTP call - 6 safety providers with different auth, config, and edge cases is too much maintenance. Users point `moderation_endpoint` at OpenAI (free), Azure, or any compatible service - Letting clients pass URLs in requests would be an SSRF vector, so `moderation_endpoint` is server-config only ## Test plan - [x] `uv run pre-commit run --all-files` passes - [x] Distribution codegen passes - [x] Provider codegen passes - [x] OpenAPI spec generation passes (46 paths, 65 operations) - [x] Unit tests fixed and passing (streaming guardrail test, lazy imports, env var tests) - [x] OpenAI coverage baseline updated (92.9%) - [ ] Full CI (integration tests) BREAKING CHANGE: Safety API removed entirely. `/v1/moderations`, `/v1/shields`, `/v1/safety/run-shield` endpoints removed. All safety providers removed. `guardrails` field changed from list to boolean. Configure `moderation_endpoint` on the responses provider config. --------- Signed-off-by: Sébastien Han <seb@redhat.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f199a03 commit 5ad4753

1,131 files changed

Lines changed: 1439 additions & 78096 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ jobs:
109109
id: changed-files
110110
if: github.event_name == 'pull_request'
111111
run: |
112-
CHANGED=$(gh pr diff ${{ github.event.pull_request.number }} --name-only)
112+
CHANGED=$(gh api --paginate "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --jq '.[].filename')
113113
# Pass as a single-line escaped string for the argument
114114
{
115115
echo "files<<EOF"

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Human contributors should follow the conventions in CONTRIBUTING.md.
77
## Project Overview
88

99
OGX is an API server implementing the OpenAI Responses API, Chat Completions,
10-
Embeddings, and supporting APIs (files, vector stores, batches, eval, safety). It supports
10+
Embeddings, and supporting APIs (files, vector stores, batches, eval, and responses guardrails). It supports
1111
multiple inference backends (OpenAI, Azure, Bedrock, vLLM, Ollama, WatsonX, etc.) through
1212
a provider architecture.
1313

ARCHITECTURE.md

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This document describes the internal architecture of OGX for contributors and AI
44

55
## System Overview
66

7-
OGX is a server that exposes a unified API for AI capabilities: inference, agents, safety, vector storage, evaluation, and more. It is provider-agnostic: the same API works whether the backend is Ollama, OpenAI, vLLM, Fireworks, or dozens of other services.
7+
OGX is a server that exposes a unified API for AI capabilities: inference, responses orchestration, vector storage, tool execution, evaluation, and more. It is provider-agnostic: the same API works whether the backend is Ollama, OpenAI, vLLM, Fireworks, or dozens of other services.
88

99
The codebase is split into two packages:
1010

@@ -30,7 +30,7 @@ Route Dispatch
3030
v
3131
Router (src/ogx/core/routers/)
3232
|
33-
|-- Looks up the resource (model, shield, etc.) in the RoutingTable
33+
|-- Looks up the resource (model, vector store, tool group, etc.) in the RoutingTable
3434
|-- Resolves which provider handles this resource
3535
|-- Enforces access control policies
3636
|
@@ -80,7 +80,7 @@ Each provider spec declares:
8080

8181
### Provider Registry
8282

83-
`src/ogx/providers/registry/` contains one file per API (e.g., `inference.py`, `safety.py`). Each file defines an `available_providers()` function that returns all `ProviderSpec` objects for that API. The registry is loaded at startup by `get_provider_registry()` in `core/distribution.py`.
83+
`src/ogx/providers/registry/` contains one file per API (e.g., `inference.py`, `responses.py`). Each file defines an `available_providers()` function that returns all `ProviderSpec` objects for that API. The registry is loaded at startup by `get_provider_registry()` in `core/distribution.py`.
8484

8585
### Provider Resolution
8686

@@ -105,22 +105,18 @@ Api.models (RoutingTable) <--> Api.inference (Router)
105105

106106
The full list of auto-routed pairs is defined in `builtin_automatically_routed_apis()` in `core/distribution.py`:
107107

108-
| Routing Table API | Router API |
109-
|-----------------------|------------------|
110-
| `Api.models` | `Api.inference` |
111-
| `Api.shields` | `Api.safety` |
112-
| `Api.datasets` | `Api.datasetio` |
113-
| `Api.scoring_functions` | `Api.scoring` |
114-
| `Api.benchmarks` | `Api.eval` |
115-
| `Api.tool_groups` | `Api.tool_runtime` |
116-
| `Api.vector_stores` | `Api.vector_io` |
108+
| Routing Table API | Router API |
109+
|---------------------|--------------------|
110+
| `Api.models` | `Api.inference` |
111+
| `Api.tool_groups` | `Api.tool_runtime` |
112+
| `Api.vector_stores` | `Api.vector_io` |
117113

118114
## The API Layer (`ogx_api`)
119115

120116
The `ogx_api` package defines all public-facing types and protocols:
121117

122-
- **Protocols** -- Python `Protocol` classes like `Inference`, `Safety` that define the API contract. HTTP routes are defined via FastAPI routers in `fastapi_routes.py` modules.
123-
- **Data Types** -- Pydantic models for requests, responses, and resources (e.g., `Model`, `Shield`, `ChatCompletionRequest`).
118+
- **Protocols** -- Python `Protocol` classes like `Inference`, `Responses` that define the API contract. HTTP routes are defined via FastAPI routers in `fastapi_routes.py` modules.
119+
- **Data Types** -- Pydantic models for requests, responses, and resources (e.g., `Model`, `VectorStore`, `ChatCompletionRequest`).
124120
- **Provider Specs** -- `InlineProviderSpec`, `RemoteProviderSpec`, and related types that define how providers are declared.
125121
- **Internal utilities** -- KVStore and SqlStore abstract interfaces live here so third-party providers can use them without depending on the full server.
126122

@@ -171,7 +167,7 @@ Used by: inference store (chat completion logs), conversations, prompts.
171167

172168
### Distribution Registry
173169

174-
`src/ogx/core/store/` implements `DistributionRegistry`, which tracks all registered resources (models, shields, datasets, etc.) across providers. It persists to the configured KVStore so resources survive server restarts.
170+
`src/ogx/core/store/` implements `DistributionRegistry`, which tracks all registered resources (models, vector stores, tool groups, prompts, etc.) across providers. It persists to the configured KVStore so resources survive server restarts.
175171

176172
## Configuration
177173

@@ -184,19 +180,15 @@ version: 2
184180
distro_name: starter
185181
apis:
186182
- inference
187-
- agents
188-
- safety
183+
- responses
184+
- vector_io
189185
# ...
190186
providers:
191187
inference:
192188
- provider_id: ollama
193189
provider_type: remote::ollama
194190
config:
195191
base_url: ${env.OLLAMA_URL:=http://localhost:11434/v1}
196-
safety:
197-
- provider_id: llama-guard
198-
provider_type: inline::llama-guard
199-
config: {}
200192
storage:
201193
type: sqlite
202194
db_path: ...
@@ -264,14 +256,13 @@ src/
264256
ogx_api/ # API definitions package (separate pip package)
265257
inference/ # Inference protocol, models, FastAPI routes
266258
responses/ # Responses API protocol and routes
267-
safety/ # Safety protocol and routes
268259
datatypes.py # Shared data types
269260
providers/ # Provider spec types
270261
internal/ # KVStore/SqlStore interfaces
271262
ogx/ # Server implementation
272263
core/
273264
server/ # FastAPI server, auth, routing
274-
routers/ # API-specific routers (inference, safety, etc.)
265+
routers/ # API-specific routers (inference, responses, etc.)
275266
routing_tables/ # Resource-to-provider mapping
276267
storage/ # KVStore and SqlStore backends
277268
store/ # Distribution registry

benchmarking/k8s-benchmark/apply.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export POSTGRES_DB=ogx
1515
export POSTGRES_PASSWORD=ogx
1616

1717
export INFERENCE_MODEL=meta-llama/Llama-3.2-3B-Instruct
18-
export SAFETY_MODEL=meta-llama/Llama-Guard-3-1B
1918

2019
export BENCHMARK_INFERENCE_MODEL=$INFERENCE_MODEL
2120
export OGX_WORKERS=4

benchmarking/k8s-benchmark/stack-configmap.yaml

Lines changed: 32 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ data:
77
- agents
88
- files
99
- inference
10-
- files
11-
- safety
1210
- tool_runtime
1311
- vector_io
1412
providers:
@@ -32,43 +30,29 @@ data:
3230
config:
3331
storage_dir: ${env.FILES_STORAGE_DIR:=~/.ogx/distributions/starter/files}
3432
metadata_store:
35-
type: sqlite
36-
db_path: ${env.SQLITE_STORE_DIR:=~/.ogx/distributions/starter}/files_metadata.db
33+
table_name: files_metadata
34+
backend: sql_default
3735
vector_io:
3836
- provider_id: ${env.ENABLE_CHROMADB:+chromadb}
3937
provider_type: remote::chromadb
4038
config:
4139
url: ${env.CHROMADB_URL:=}
42-
kvstore:
43-
type: postgres
44-
host: ${env.POSTGRES_HOST:=localhost}
45-
port: ${env.POSTGRES_PORT:=5432}
46-
db: ${env.POSTGRES_DB:=ogx}
47-
user: ${env.POSTGRES_USER:=ogx}
48-
password: ${env.POSTGRES_PASSWORD:=ogx}
49-
safety:
50-
- provider_id: llama-guard
51-
provider_type: inline::llama-guard
52-
config:
53-
excluded_categories: []
40+
persistence:
41+
namespace: vector_io::chroma_remote
42+
backend: kv_default
5443
agents:
5544
- provider_id: builtin
5645
provider_type: inline::builtin
5746
config:
58-
persistence_store:
59-
type: postgres
60-
host: ${env.POSTGRES_HOST:=localhost}
61-
port: ${env.POSTGRES_PORT:=5432}
62-
db: ${env.POSTGRES_DB:=ogx}
63-
user: ${env.POSTGRES_USER:=ogx}
64-
password: ${env.POSTGRES_PASSWORD:=ogx}
65-
responses_store:
66-
type: postgres
67-
host: ${env.POSTGRES_HOST:=localhost}
68-
port: ${env.POSTGRES_PORT:=5432}
69-
db: ${env.POSTGRES_DB:=ogx}
70-
user: ${env.POSTGRES_USER:=ogx}
71-
password: ${env.POSTGRES_PASSWORD:=ogx}
47+
persistence:
48+
agent_state:
49+
namespace: agents
50+
backend: kv_default
51+
responses:
52+
table_name: responses
53+
backend: sql_default
54+
max_write_queue_size: 10000
55+
num_writers: 4
7256
tool_runtime:
7357
- provider_id: brave-search
7458
provider_type: remote::brave-search
@@ -118,25 +102,24 @@ data:
118102
prompts:
119103
backend: kv_default
120104
namespace: prompts
121-
models:
122-
- metadata:
123-
embedding_dimension: 768
124-
model_id: nomic-embed-text-v1.5
125-
provider_id: sentence-transformers
126-
model_type: embedding
127-
- metadata: {}
128-
model_id: Qwen/Qwen3-Reranker-0.6B
129-
provider_id: transformers
130-
model_type: rerank
131-
- model_id: ${env.INFERENCE_MODEL}
132-
provider_id: vllm-inference
133-
model_type: llm
134-
shields:
135-
- shield_id: ${env.SAFETY_MODEL:=meta-llama/Llama-Guard-3-1B}
136-
vector_dbs: []
137-
datasets: []
138-
scoring_fns: []
139-
benchmarks: []
105+
registered_resources:
106+
models:
107+
- metadata:
108+
embedding_dimension: 768
109+
model_id: nomic-embed-text-v1.5
110+
provider_id: sentence-transformers
111+
model_type: embedding
112+
- metadata: {}
113+
model_id: Qwen/Qwen3-Reranker-0.6B
114+
provider_id: transformers
115+
model_type: rerank
116+
- model_id: ${env.INFERENCE_MODEL}
117+
provider_id: vllm-inference
118+
model_type: llm
119+
vector_dbs: []
120+
datasets: []
121+
scoring_fns: []
122+
benchmarks: []
140123
server:
141124
port: 8323
142125
kind: ConfigMap

benchmarking/k8s-benchmark/stack-k8s.yaml.template

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,12 @@ spec:
4040
value: "5432"
4141
- name: INFERENCE_MODEL
4242
value: "${INFERENCE_MODEL}"
43-
- name: SAFETY_MODEL
44-
value: "${SAFETY_MODEL}"
4543
- name: TAVILY_SEARCH_API_KEY
4644
value: "${TAVILY_SEARCH_API_KEY}"
4745
- name: VLLM_URL
4846
value: http://vllm-server.default.svc.cluster.local:8000/v1
4947
- name: VLLM_MAX_TOKENS
5048
value: "3072"
51-
- name: VLLM_SAFETY_URL
52-
value: http://vllm-server-safety.default.svc.cluster.local:8001/v1
5349
- name: VLLM_TLS_VERIFY
5450
value: "false"
5551
- name: OGX_LOGGING

benchmarking/k8s-benchmark/stack_run_config.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ apis:
44
- agents
55
- files
66
- inference
7-
- files
8-
- safety
97
- tool_runtime
108
- vector_io
119
providers:
@@ -39,11 +37,6 @@ providers:
3937
persistence:
4038
namespace: vector_io::chroma_remote
4139
backend: kv_default
42-
safety:
43-
- provider_id: llama-guard
44-
provider_type: inline::llama-guard
45-
config:
46-
excluded_categories: []
4740
agents:
4841
- provider_id: builtin
4942
provider_type: inline::builtin
@@ -120,8 +113,6 @@ registered_resources:
120113
- model_id: ${env.INFERENCE_MODEL}
121114
provider_id: vllm-inference
122115
model_type: llm
123-
shields:
124-
- shield_id: ${env.SAFETY_MODEL:=meta-llama/Llama-Guard-3-1B}
125116
vector_dbs: []
126117
datasets: []
127118
scoring_fns: []

benchmarking/rag/config.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ storage:
9090
backend: kv_default
9191
registered_resources:
9292
models: []
93-
shields: []
9493
vector_dbs: []
9594
server:
9695
port: 8321
@@ -154,6 +153,4 @@ vector_stores:
154153
default_timeout_seconds: 120
155154
default_max_concurrency: 3
156155
max_document_tokens: 100000
157-
safety:
158-
default_shield_id: null
159156
connectors: []

client-sdks/openapi/templates/python/lib/cli/ogx_client.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ def _get_version():
3333
from .post_training import post_training
3434
from .providers import providers
3535
from .scoring_functions import scoring_functions
36-
from .shields import shields
3736
from .toolgroups import toolgroups
3837
from .vector_stores import vector_stores
3938

@@ -92,7 +91,6 @@ def ogx_client(ctx, endpoint: str, api_key: str, config: str | None):
9291
# Register all subcommands
9392
ogx_client.add_command(models, "models")
9493
ogx_client.add_command(vector_stores, "vector_stores")
95-
ogx_client.add_command(shields, "shields")
9694
ogx_client.add_command(eval_tasks, "eval_tasks")
9795
ogx_client.add_command(providers, "providers")
9896
ogx_client.add_command(datasets, "datasets")

client-sdks/openapi/templates/python/lib/cli/shields/__init__.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)