add --append-system-prompt, --max-turns, --max-budget-usd, --json-schema, --exclude-dynamic-system-prompt-sections mappings

This commit is contained in:
2026-05-22 16:56:27 +02:00
parent ac0459ffa5
commit a781abb862
+109 -25
View File
@@ -141,11 +141,17 @@ def _temperature_to_effort(temperature: float) -> str:
def _build_command(
model: str,
system_prompt: Optional[str],
effort: Optional[str] = None,
temperature: Optional[float] = None,
fallback_model: Optional[str] = None,
) -> list[str]:
system_prompt,
effort=None,
temperature=None,
fallback_model=None,
system_prompt_mode="replace",
max_turns=None,
max_budget_usd=None,
json_schema=None,
exclude_dynamic_sections=False,
):
import json as _json
cmd = [
CLAUDE_BIN,
"--print",
@@ -160,13 +166,11 @@ def _build_command(
"--model", model,
]
# Determine effort level to pass to CLI
resolved_effort: Optional[str] = None
resolved_effort = None
if effort is not None and effort in _VALID_EFFORT_LEVELS:
resolved_effort = effort
elif temperature is not None:
resolved_effort = _temperature_to_effort(temperature)
# If both are None, don't add --effort (let CLI use its default)
if resolved_effort is not None:
cmd += ["--effort", resolved_effort]
@@ -175,29 +179,54 @@ def _build_command(
cmd += ["--fallback-model", fallback_model]
if system_prompt:
if system_prompt_mode == "append":
cmd += ["--append-system-prompt", system_prompt]
else:
cmd += ["--system-prompt", system_prompt]
if max_turns is not None:
cmd += ["--max-turns", str(max_turns)]
if max_budget_usd is not None:
cmd += ["--max-budget-usd", str(max_budget_usd)]
if json_schema is not None:
cmd += ["--json-schema", _json.dumps(json_schema)]
if exclude_dynamic_sections:
cmd += ["--exclude-dynamic-system-prompt-sections"]
return cmd
# ---------------------------------------------------------------------------
# Core: run subprocess and stream parsed events
# ---------------------------------------------------------------------------
async def _run_claude(
model: str,
system_prompt: Optional[str],
turns: list[dict],
effort: Optional[str] = None,
temperature: Optional[float] = None,
fallback_model: Optional[str] = None,
) -> AsyncGenerator[dict, None]:
system_prompt,
turns: list,
effort=None,
temperature=None,
fallback_model=None,
system_prompt_mode="replace",
max_turns=None,
max_budget_usd=None,
json_schema=None,
exclude_dynamic_sections=False,
):
"""
Yields dicts with keys:
{"event": "delta", "text": "..."}
{"event": "done", "usage": {...}, "cost_usd": float | None, "rate_limit": dict | None}
{"event": "error", "status": int, "message": "..."}
"""
cmd = _build_command(model, system_prompt, effort=effort, temperature=temperature, fallback_model=fallback_model)
cmd = _build_command(
model, system_prompt,
effort=effort, temperature=temperature, fallback_model=fallback_model,
system_prompt_mode=system_prompt_mode, max_turns=max_turns,
max_budget_usd=max_budget_usd, json_schema=json_schema,
exclude_dynamic_sections=exclude_dynamic_sections,
)
log.debug("Spawning: %s", " ".join(cmd[:6]) + " ...")
env = _subprocess_env()
@@ -340,12 +369,17 @@ async def _run_claude(
async def _stream_response(
request: Request,
model: str,
system_prompt: Optional[str],
turns: list[dict],
effort: Optional[str] = None,
temperature: Optional[float] = None,
fallback_model: Optional[str] = None,
) -> AsyncGenerator[str, None]:
system_prompt,
turns: list,
effort=None,
temperature=None,
fallback_model=None,
system_prompt_mode="replace",
max_turns=None,
max_budget_usd=None,
json_schema=None,
exclude_dynamic_sections=False,
):
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
created = int(time.time())
@@ -354,6 +388,9 @@ async def _stream_response(
async for ev in _run_claude(
model, system_prompt, turns,
effort=effort, temperature=temperature, fallback_model=fallback_model,
system_prompt_mode=system_prompt_mode, max_turns=max_turns,
max_budget_usd=max_budget_usd, json_schema=json_schema,
exclude_dynamic_sections=exclude_dynamic_sections,
):
# Check if client disconnected
if await request.is_disconnected():
@@ -501,10 +538,51 @@ async def chat_completions(request: Request):
if fallback_model is not None:
fallback_model = str(fallback_model)
# Warn about unsupported / silently-ignored params
# Extract new CLI-mapped parameters from extra_body
# system_prompt_mode: "replace" (default, --system-prompt) or "append" (--append-system-prompt)
system_prompt_mode: str = str(extra_body.get("system_prompt_mode", "replace"))
if system_prompt_mode not in ("replace", "append"):
system_prompt_mode = "replace"
max_turns: Optional[int] = None
raw_max_turns = extra_body.get("max_turns")
if raw_max_turns is not None:
try:
max_turns = int(raw_max_turns)
except (TypeError, ValueError):
pass
max_budget_usd: Optional[float] = None
raw_budget = extra_body.get("max_budget_usd")
if raw_budget is not None:
try:
max_budget_usd = float(raw_budget)
except (TypeError, ValueError):
pass
# response_format.json_schema → --json-schema
json_schema: Optional[dict] = None
response_format = body.get("response_format") or {}
if isinstance(response_format, dict):
if response_format.get("type") == "json_schema":
js = response_format.get("json_schema")
if isinstance(js, dict):
# OpenAI wraps it: {name, strict, schema} — pass the schema sub-dict to CLI
json_schema = js.get("schema") or js
elif response_format.get("type") == "json_object":
# Instruct via system prompt append instead of --json-schema
json_schema = {"type": "object"}
# Also allow direct extra_body.json_schema override
if extra_body.get("json_schema"):
json_schema = extra_body["json_schema"]
# exclude_dynamic_system_prompt_sections improves cache reuse in multi-user deployments
exclude_dynamic_sections: bool = bool(extra_body.get("exclude_dynamic_system_prompt_sections", False))
# Warn about other unsupported / silently-ignored params
_IGNORED = {"top_p", "frequency_penalty", "presence_penalty",
"logit_bias", "n", "best_of", "logprobs", "top_logprobs",
"response_format", "seed", "stop", "user"}
"seed", "stop", "user"}
for param in _IGNORED:
if param in body:
log.debug("Ignoring unsupported parameter: %s", param)
@@ -519,6 +597,9 @@ async def chat_completions(request: Request):
gen = await _stream_response(
request, model, system_prompt, turns,
effort=effort, temperature=temperature, fallback_model=fallback_model,
system_prompt_mode=system_prompt_mode, max_turns=max_turns,
max_budget_usd=max_budget_usd, json_schema=json_schema,
exclude_dynamic_sections=exclude_dynamic_sections,
)
return StreamingResponse(
gen,
@@ -539,6 +620,9 @@ async def chat_completions(request: Request):
async for ev in _run_claude(
model, system_prompt, turns,
effort=effort, temperature=temperature, fallback_model=fallback_model,
system_prompt_mode=system_prompt_mode, max_turns=max_turns,
max_budget_usd=max_budget_usd, json_schema=json_schema,
exclude_dynamic_sections=exclude_dynamic_sections,
):
if ev["event"] == "delta":
full_text += ev["text"]