add --append-system-prompt, --max-turns, --max-budget-usd, --json-schema, --exclude-dynamic-system-prompt-sections mappings
This commit is contained in:
@@ -141,11 +141,17 @@ def _temperature_to_effort(temperature: float) -> str:
|
|||||||
|
|
||||||
def _build_command(
|
def _build_command(
|
||||||
model: str,
|
model: str,
|
||||||
system_prompt: Optional[str],
|
system_prompt,
|
||||||
effort: Optional[str] = None,
|
effort=None,
|
||||||
temperature: Optional[float] = None,
|
temperature=None,
|
||||||
fallback_model: Optional[str] = None,
|
fallback_model=None,
|
||||||
) -> list[str]:
|
system_prompt_mode="replace",
|
||||||
|
max_turns=None,
|
||||||
|
max_budget_usd=None,
|
||||||
|
json_schema=None,
|
||||||
|
exclude_dynamic_sections=False,
|
||||||
|
):
|
||||||
|
import json as _json
|
||||||
cmd = [
|
cmd = [
|
||||||
CLAUDE_BIN,
|
CLAUDE_BIN,
|
||||||
"--print",
|
"--print",
|
||||||
@@ -160,13 +166,11 @@ def _build_command(
|
|||||||
"--model", model,
|
"--model", model,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Determine effort level to pass to CLI
|
resolved_effort = None
|
||||||
resolved_effort: Optional[str] = None
|
|
||||||
if effort is not None and effort in _VALID_EFFORT_LEVELS:
|
if effort is not None and effort in _VALID_EFFORT_LEVELS:
|
||||||
resolved_effort = effort
|
resolved_effort = effort
|
||||||
elif temperature is not None:
|
elif temperature is not None:
|
||||||
resolved_effort = _temperature_to_effort(temperature)
|
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:
|
if resolved_effort is not None:
|
||||||
cmd += ["--effort", resolved_effort]
|
cmd += ["--effort", resolved_effort]
|
||||||
@@ -175,29 +179,54 @@ def _build_command(
|
|||||||
cmd += ["--fallback-model", fallback_model]
|
cmd += ["--fallback-model", fallback_model]
|
||||||
|
|
||||||
if system_prompt:
|
if system_prompt:
|
||||||
cmd += ["--system-prompt", 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
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Core: run subprocess and stream parsed events
|
# Core: run subprocess and stream parsed events
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async def _run_claude(
|
async def _run_claude(
|
||||||
model: str,
|
model: str,
|
||||||
system_prompt: Optional[str],
|
system_prompt,
|
||||||
turns: list[dict],
|
turns: list,
|
||||||
effort: Optional[str] = None,
|
effort=None,
|
||||||
temperature: Optional[float] = None,
|
temperature=None,
|
||||||
fallback_model: Optional[str] = None,
|
fallback_model=None,
|
||||||
) -> AsyncGenerator[dict, None]:
|
system_prompt_mode="replace",
|
||||||
|
max_turns=None,
|
||||||
|
max_budget_usd=None,
|
||||||
|
json_schema=None,
|
||||||
|
exclude_dynamic_sections=False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Yields dicts with keys:
|
Yields dicts with keys:
|
||||||
{"event": "delta", "text": "..."}
|
{"event": "delta", "text": "..."}
|
||||||
{"event": "done", "usage": {...}, "cost_usd": float | None, "rate_limit": dict | None}
|
{"event": "done", "usage": {...}, "cost_usd": float | None, "rate_limit": dict | None}
|
||||||
{"event": "error", "status": int, "message": "..."}
|
{"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]) + " ...")
|
log.debug("Spawning: %s", " ".join(cmd[:6]) + " ...")
|
||||||
env = _subprocess_env()
|
env = _subprocess_env()
|
||||||
|
|
||||||
@@ -340,12 +369,17 @@ async def _run_claude(
|
|||||||
async def _stream_response(
|
async def _stream_response(
|
||||||
request: Request,
|
request: Request,
|
||||||
model: str,
|
model: str,
|
||||||
system_prompt: Optional[str],
|
system_prompt,
|
||||||
turns: list[dict],
|
turns: list,
|
||||||
effort: Optional[str] = None,
|
effort=None,
|
||||||
temperature: Optional[float] = None,
|
temperature=None,
|
||||||
fallback_model: Optional[str] = None,
|
fallback_model=None,
|
||||||
) -> AsyncGenerator[str, 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}"
|
completion_id = f"chatcmpl-{uuid.uuid4().hex}"
|
||||||
created = int(time.time())
|
created = int(time.time())
|
||||||
|
|
||||||
@@ -354,6 +388,9 @@ async def _stream_response(
|
|||||||
async for ev in _run_claude(
|
async for ev in _run_claude(
|
||||||
model, system_prompt, turns,
|
model, system_prompt, turns,
|
||||||
effort=effort, temperature=temperature, fallback_model=fallback_model,
|
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
|
# Check if client disconnected
|
||||||
if await request.is_disconnected():
|
if await request.is_disconnected():
|
||||||
@@ -501,10 +538,51 @@ async def chat_completions(request: Request):
|
|||||||
if fallback_model is not None:
|
if fallback_model is not None:
|
||||||
fallback_model = str(fallback_model)
|
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",
|
_IGNORED = {"top_p", "frequency_penalty", "presence_penalty",
|
||||||
"logit_bias", "n", "best_of", "logprobs", "top_logprobs",
|
"logit_bias", "n", "best_of", "logprobs", "top_logprobs",
|
||||||
"response_format", "seed", "stop", "user"}
|
"seed", "stop", "user"}
|
||||||
for param in _IGNORED:
|
for param in _IGNORED:
|
||||||
if param in body:
|
if param in body:
|
||||||
log.debug("Ignoring unsupported parameter: %s", param)
|
log.debug("Ignoring unsupported parameter: %s", param)
|
||||||
@@ -519,6 +597,9 @@ async def chat_completions(request: Request):
|
|||||||
gen = await _stream_response(
|
gen = await _stream_response(
|
||||||
request, model, system_prompt, turns,
|
request, model, system_prompt, turns,
|
||||||
effort=effort, temperature=temperature, fallback_model=fallback_model,
|
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(
|
return StreamingResponse(
|
||||||
gen,
|
gen,
|
||||||
@@ -539,6 +620,9 @@ async def chat_completions(request: Request):
|
|||||||
async for ev in _run_claude(
|
async for ev in _run_claude(
|
||||||
model, system_prompt, turns,
|
model, system_prompt, turns,
|
||||||
effort=effort, temperature=temperature, fallback_model=fallback_model,
|
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":
|
if ev["event"] == "delta":
|
||||||
full_text += ev["text"]
|
full_text += ev["text"]
|
||||||
|
|||||||
Reference in New Issue
Block a user