Repository navigation
feat(proxy): plug into Envoy (ext_proc) and LiteLLM (guardrail) with one judge - #110
Open
CharmingGroot wants to merge 3 commits into
Open
CharmingGroot wants to merge 3 commits into
CharmingGroot wants to merge 3 commits into
Conversation
The reverse proxy answered three questions inline — may this endpoint be used, may this prompt leave, may these tool calls reach the agent. An Envoy ext_proc server and a LiteLLM guardrail endpoint must answer them the same way, and a second copy is the one that drifts. Judge holds the guard, format, inspectors, redaction policy and limits; ProxyState is Judge plus where to forward. Fixes a redaction bug found on the way: findings from every message were pooled and each message redacted with all of them, so a span found in one message overwrote the same offset in every other. Each text is now redacted with its own findings. Signed-off-by: Ho Geun Choi <ohyes9711@gmail.com>
…he same judge Envoy and LiteLLM are where teams already route LLM traffic. Both now reach the same Judge the reverse proxy uses, selected with --frontend. ext-proc: an envoy.service.ext_proc.v3 server. Request headers route the endpoint and ask the upstream for identity encoding; the request body is screened (a rewrite carries its new content-length); the response body is parsed and its tool calls judged, with refusals sent as ImmediateResponse before the client sees a byte. Bodies must be BUFFERED: pasu reads the modes Envoy reports and refuses a completion it would never be shown, since with response_body_mode NONE every tool call passed unseen. Verified against Envoy 1.37, including that bypass with the check disabled. litellm: the Generic Guardrail API. Prompts are screened and redacted in order; proposed tool calls judged; history tool calls on a request are not. LiteLLM streams while it consults a guardrail, so a streamed denied call reached the client first (LiteLLM 1.91). integrations/litellm/pasu_guardrail.py buffers a streamed completion until pasu has judged it, as LiteLLM's own tool_permission guardrail does; no chunk left in the same run. Signed-off-by: Ho Geun Choi <ohyes9711@gmail.com>
The README led with a layer diagram that assumed one way in (base_url). Two new diagrams replace it and the sidecar figure in the proxy section: attach.svg — three front-ends into one judge, the kernel under all of them; pipeline.svg — the four checks of a request, their refusals, and which reverse/ext_proc/LiteLLM step runs each. integrations/ gains litellm-streaming.svg, the measured difference between generic_guardrail_api and PasuGuardrail on a streamed tool call. The proxy section documents --frontend and the settings that matter on each gateway; the library example matches the current ProxyState/Judge. adapting.md describes a front-end as a seam; deployment.md adds running beside a gateway. Signed-off-by: Ho Geun Choi <ohyes9711@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on # — merge that first.
Teams already route LLM traffic through Envoy or LiteLLM. Rather than become another gateway, pasu now plugs into those. One
Judgeanswers three front-ends, selected with--frontend, with the same policy, audit stream and HITL.Before building, I surveyed how others integrate:
GuardrailPolicyand agentgateway's webhook see text only.tool_permission(name/argument regex). So pasu's value there is the single policy shared with the kernel layer,Ask, Korean PII redaction and one audit stream, not name matching.JudgeThe reverse proxy answered three questions inline: may this endpoint be used, may this prompt leave, may these tool calls reach the agent.
Judgeholds them transport-free.ProxyStateis nowJudgeplus where to forward.While doing this I found a redaction bug. Findings from every message were pooled, so a span found in one message overwrote the same offset in every other. Each text is now redacted with its own findings (regression test, mutation-checked).
--frontend ext-proc(Envoy)identityencoding.content-length, because Envoy checks it.ImmediateResponsesent before the client sees a byte.Bodies must be BUFFERED. pasu reads the body modes Envoy reports and refuses (500) a completion it would never be shown. Otherwise, with
response_body_mode: NONE, every tool call passes unseen: on Envoy 1.37 a build without this check let a denied shell call through with a 200.--frontend litellmThis speaks LiteLLM's Generic Guardrail API:
PASU_GUARDRAIL_API_KEY.LiteLLM consults a guardrail while it streams. With the built-in integration, a denied call's chunks reached the client before the block (measured on LiteLLM 1.91).
integrations/litellm/pasu_guardrail.pyfixes this: it subclassesgeneric_guardrail_apiand holds a streamed completion until pasu has judged it, as LiteLLM's owntool_permissiondoes. In the same run, no chunk left.Verified against the real thing
PasuGuardrail)[REDACTED:ko-rrn]fail_closed)integrations/has tested example configs and a coverage table. Two things in it are worth knowing: LiteLLM 1.91 wants a top-levelguardrails:key (its own example config fails to start), and Envoy's 200 ms defaultmessage_timeoutis too short for HITL.Dependencies
The
ext-procfeature, on by default, pinsenvoy-types0.7.3 andtonic0.14.5, the last releases within our 1.86 MSRV. Their licenses (Apache-2.0 and MIT) are on thedeny.tomlallowlist.Docs
docs/attach.svg: three ways into one judge.docs/pipeline.svg: the four checks, and where each runs in every front-end.docs/litellm-streaming.svg: the streaming comparison.deployment.mdandadapting.mdare updated.🤖 Generated with Claude Code