Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions PROVIDER_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Optional:
```shell
export PROVIDER=openai
export MODEL=openai/gpt-4o-mini
export PREPROCESSOR_MODEL=openai/gpt-4o-mini
export DRAFTER_MODEL=openai/gpt-4o-mini
export OPENAI_BASE_URL=...
```

Expand Down Expand Up @@ -48,8 +48,8 @@ Startup emits one concise config line showing resolved `mode`, `base_url`,
`model`, and resolution `source` (`default`, `PROVIDER`, or
`OPENAI_BASE_URL override`).

`MODEL` and `PREPROCESSOR_MODEL` use LiteLLM format: `<provider>/<model>`.
`PREPROCESSOR_MODEL` is optional and defaults to `MODEL`.
`MODEL` and `DRAFTER_MODEL` use LiteLLM format: `<provider>/<model>`.
`DRAFTER_MODEL` is optional and defaults to `MODEL`.

The directive-drafter integration always uses heuristic-first processing with
the configured fallback model when needed.
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def _build_trace_text(
*,
original_input: str,
compiler_input: str,
preprocessor_output: str | None,
drafter_output: str | None,
decision: Decision | DecisionKind,
premise_before: str | None,
policies_before: Mapping[str, PolicyValue],
Expand All @@ -124,7 +124,7 @@ def _build_trace_text(
"Context Compiler trace",
f"- original_input: {original_input}",
f"- compiler_input: {compiler_input}",
f"- preprocessor_output: {preprocessor_output if preprocessor_output is not None else '(none)'}",
f"- drafter_output: {drafter_output if drafter_output is not None else '(none)'}",
f"- decision: {kind}",
f"- llm_called: {'yes' if llm_called else 'no'}",
]
Expand Down Expand Up @@ -224,20 +224,19 @@ def _create_directive_drafter(

def _get_directive_drafter() -> DirectiveDrafter:
config = resolve_provider_config(default_model="openai/gpt-4o-mini")
preprocessor_model = os.getenv("PREPROCESSOR_MODEL", "").strip() or config.model
return _create_directive_drafter(
preprocessor_model, config.api_key, config.base_url
)
drafter_model = os.getenv("DRAFTER_MODEL", "").strip()
drafter_model = drafter_model or config.model
return _create_directive_drafter(drafter_model, config.api_key, config.base_url)


def _preprocess_user_input(message: str) -> str | None:
def _draft_user_input(message: str) -> str | None:
try:
drafted_result = _get_directive_drafter().draft_directive(message)
logger.debug("preprocessor: drafted_result=%r", drafted_result)
logger.debug("drafter: drafted_result=%r", drafted_result)
return _extract_drafted_text(drafted_result)
except Exception:
# Safe no-op fallback: if drafter path fails, preserve basic behavior.
logger.debug("preprocessor: drafter_exception", exc_info=True)
logger.debug("drafter: exception", exc_info=True)
return None
return None

Expand All @@ -258,7 +257,7 @@ def _append_trace(
*,
original_input: str,
compiler_input: str,
preprocessor_output: str | None,
drafter_output: str | None,
decision: Decision | DecisionKind,
state_before: tuple[str | None, dict[str, PolicyValue]],
state_after: tuple[str | None, dict[str, PolicyValue]],
Expand All @@ -269,7 +268,7 @@ def _append_trace(
trace_text = _build_trace_text(
original_input=original_input,
compiler_input=compiler_input,
preprocessor_output=preprocessor_output,
drafter_output=drafter_output,
decision=decision,
premise_before=state_before[0],
policies_before=state_before[1],
Expand All @@ -293,30 +292,30 @@ def handle_turn(
approval_handler: ApprovalHandler = _default_approval_handler,
) -> str:
state_before = (engine.premise, dict(engine.policies))
preprocessd = _preprocess_user_input(user_input)
if preprocessd is None:
drafted_input = _draft_user_input(user_input)
if drafted_input is None:
messages = _build_messages(user_input, engine)
response_text = _call_litellm(messages)
return _append_trace(
response_text,
original_input=user_input,
compiler_input=user_input,
preprocessor_output=None,
drafter_output=None,
decision=DecisionKind.NO_DIRECTIVE,
state_before=state_before,
state_after=(engine.premise, dict(engine.policies)),
llm_called=True,
)

compile_input = preprocessd
logger.debug("preprocessor: engine_input=directive")
compile_input = drafted_input
logger.debug("drafter: engine_input=directive")
approved = approval_handler(compile_input)
if not approved:
return _append_trace(
"Directive rejected. No state change applied.",
original_input=user_input,
compiler_input=compile_input,
preprocessor_output=preprocessd,
drafter_output=drafted_input,
decision=DecisionKind.NO_DIRECTIVE,
state_before=state_before,
state_after=(engine.premise, dict(engine.policies)),
Expand All @@ -330,7 +329,7 @@ def handle_turn(
kind = DECISION_UPDATE
else:
kind = DecisionKind.NO_DIRECTIVE.value
logger.debug("preprocessor: decision=%s", kind)
logger.debug("drafter: decision=%s", kind)

if decision.kind == DecisionKind.ERROR:
response_text = (
Expand All @@ -340,7 +339,7 @@ def handle_turn(
response_text,
original_input=user_input,
compiler_input=compile_input,
preprocessor_output=preprocessd,
drafter_output=drafted_input,
decision=decision,
state_before=state_before,
state_after=(engine.premise, dict(engine.policies)),
Expand All @@ -352,7 +351,7 @@ def handle_turn(
response_text,
original_input=user_input,
compiler_input=compile_input,
preprocessor_output=preprocessd,
drafter_output=drafted_input,
decision=decision,
state_before=state_before,
state_after=(engine.premise, dict(engine.policies)),
Expand All @@ -364,7 +363,7 @@ def handle_turn(
response_text,
original_input=user_input,
compiler_input=compile_input,
preprocessor_output=preprocessd,
drafter_output=drafted_input,
decision=decision,
state_before=state_before,
state_after=(engine.premise, dict(engine.policies)),
Expand Down
9 changes: 4 additions & 5 deletions python/reference_integrations/litellm_proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ python.reference_integrations.litellm_proxy.context_compiler_precall_hook.proxy_
Optional env vars for directive-drafter fallback:

```shell
export PREPROCESSOR_MODEL=openai/gpt-4o-mini
export DRAFTER_MODEL=openai/gpt-4o-mini
```

`PREPROCESSOR_MODEL` is optional and defaults to `MODEL`.
`DRAFTER_MODEL` is optional and defaults to `MODEL`.

The directive-drafter integration always uses heuristic-first processing with
the configured fallback model when needed.
Expand All @@ -213,7 +213,7 @@ the configured fallback model when needed.

- Mixed-content user messages compile only text segments from the latest user
turn.
- `MODEL` and `PREPROCESSOR_MODEL` use LiteLLM format: `<provider>/<model>`.
- `MODEL` and `DRAFTER_MODEL` use LiteLLM format: `<provider>/<model>`.
- Corrupt or incompatible checkpoints fail clearly in persistent mode and do
not silently reset state.
- In the directive-drafter hook, drafter state context now comes from restored
Expand All @@ -230,8 +230,7 @@ the configured fallback model when needed.
explicit `stateless` mode
- proxy starts but upstream calls fail: check `OPENAI_API_KEY` and upstream
model/provider config in `config.example.yaml`
- directive-drafter fallback issues: `PREPROCESSOR_MODEL` defaults to `MODEL`;
set it explicitly only when using a separate fallback model
- directive-drafter fallback issues: `DRAFTER_MODEL` defaults to `MODEL`

## Opt-in Runtime Smoke Test

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,17 @@ def _create_directive_drafter(


def _get_directive_drafter() -> DirectiveDrafter:
preprocessor_model = os.getenv("PREPROCESSOR_MODEL", "").strip()
if not preprocessor_model:
preprocessor_model = os.getenv("MODEL", "").strip()
if not preprocessor_model:
drafter_model = os.getenv("DRAFTER_MODEL", "").strip()
if not drafter_model:
drafter_model = os.getenv("MODEL", "").strip()
if not drafter_model:
return DirectiveDrafter()

api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
return DirectiveDrafter()
return _create_directive_drafter(
preprocessor_model, api_key, os.getenv("OPENAI_BASE_URL") or None
drafter_model, api_key, os.getenv("OPENAI_BASE_URL") or None
)


Expand All @@ -98,7 +98,7 @@ def _draft_last_user_message(message: str) -> DraftResult:
return DirectiveDrafter().draft_directive(message)


class ContextCompilerPreCallHookWithPreprocessor(CustomLogger):
class ContextCompilerPreCallHookWithDrafter(CustomLogger):
async def async_pre_call_hook(
self,
user_api_key_dict: Any,
Expand Down Expand Up @@ -181,4 +181,4 @@ async def async_pre_call_hook(
return data


proxy_handler_instance = ContextCompilerPreCallHookWithPreprocessor()
proxy_handler_instance = ContextCompilerPreCallHookWithDrafter()
20 changes: 10 additions & 10 deletions python/reference_integrations/openwebui_pipe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Saved compiler state changes which turns the pipe handles locally and what it
forwards downstream. These examples show Open WebUI pipe behavior with and
without Directive Drafter preprocessing.
without Directive Drafter drafting.

## Core behavior

Expand Down Expand Up @@ -69,8 +69,8 @@ If using `open_webui_pipe_with_directive_drafter.py`:

- Install directive-drafter support if needed:
`pip install "context-compiler>=0.9.0dev13" "context-compiler-directive-drafter>=0.2.0dev5"`
- Optionally set `PREPROCESSOR_MODEL_ID` to use a separate fallback drafting model
- If `PREPROCESSOR_MODEL_ID` is unset, fallback uses `BASE_MODEL_ID`
- Optionally set `DRAFTER_MODEL_ID` to use a separate fallback drafting model
- If neither model id is set, fallback uses `BASE_MODEL_ID`

Model fallback output is structurally validated before handoff. This does not prove that the model interpreted the user correctly. The automated fallback path is experimental pending a separate source-aware acceptance policy and reviewed drafting workflow.

Expand All @@ -91,7 +91,7 @@ If frontmatter dependency installs are disabled, offline, or unavailable:
### Finding valid model ids

Use the Open WebUI model picker/list to copy exact model ids for `BASE_MODEL_ID`
(and optional `PREPROCESSOR_MODEL_ID` for the directive-drafter pipe).
(and optional `DRAFTER_MODEL_ID` for the directive-drafter pipe).

## Verify behavior

Expand Down Expand Up @@ -129,7 +129,7 @@ Advanced check:

### Directive-drafter pipe

Use this pipe when you want the same runtime behavior plus Directive Drafter preprocessing.
Use this pipe when you want the same runtime behavior plus Directive Drafter drafting.

When the drafter produces a `CanonicalDirective`, the pipe uses Open WebUI's
native `__event_call__` confirmation dialog for HITL approval. The lifecycle is:
Expand Down Expand Up @@ -240,13 +240,13 @@ rejection flows.

- `BASE_MODEL_ID is required`: set a valid Open WebUI model id in the function valves, or enable `ALLOW_MISSING_BASE_MODEL_FOR_DEBUG=true` only for local testing.
- `BASE_MODEL_ID was not found in Open WebUI models`: copy the exact id from `Admin Panel → Settings → Models`.
- `PREPROCESSOR_MODEL_ID was not found in Open WebUI models`: set a valid fallback model id or leave it unset to default to `BASE_MODEL_ID`.
- `PREPROCESSOR_MODEL_ID must not match the selected pipe model id`: choose a real backend model id, not the pipe model id itself.
- `PREPROCESSOR_MODEL_ID is invalid or not configured in Open WebUI`: the fallback route hit a missing model; fix the configured fallback model or unset it to reuse `BASE_MODEL_ID`.
- `DRAFTER_MODEL_ID was not found in Open WebUI models`: set a valid fallback model id or leave it unset to default to `BASE_MODEL_ID`.
- `DRAFTER_MODEL_ID must not match the selected pipe model id`: choose a real backend model id, not the pipe model id itself.
- `DRAFTER_MODEL_ID is invalid or not configured in Open WebUI`: the fallback route hit a missing model; fix the configured fallback model or unset it to reuse `BASE_MODEL_ID`.
- `ALLOW_MISSING_BASE_MODEL_FOR_DEBUG=true`: directive-only updates still run locally, but passthrough returns a deterministic debug message instead of calling a downstream model.
- imports fail after function upload: install `context-compiler>=0.9.0dev13` in the Open WebUI runtime, and add `context-compiler-directive-drafter>=0.2.0dev5` only for the Directive Drafter pipe, because the copied function runs from a temp/cached location.

## Fallback notes

- Fallback drafting uses `PREPROCESSOR_MODEL_ID` first, while the main passthrough path still forwards with `BASE_MODEL_ID`.
- If the fallback model returns `model not found`, the pipe normalizes that into the deterministic `PREPROCESSOR_MODEL_ID` misconfiguration message above.
- Fallback drafting uses `DRAFTER_MODEL_ID`, while the main passthrough path still forwards with `BASE_MODEL_ID`.
- If the fallback model returns `model not found`, the pipe normalizes that into the deterministic `DRAFTER_MODEL_ID` misconfiguration message above.
Loading