Skip to content
Closed
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
8 changes: 4 additions & 4 deletions contextual_orchestrator/cost_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,12 +583,12 @@ def _seed_dimension_catalog(self) -> None:
ph = self._placeholder()
cur = self._conn.cursor()
for order, (name, label, _column) in enumerate(ATTRIBUTION_DIMENSION_CATALOG):
cur.execute(
cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the DB-API placeholder char is interpolated; the value is bound.
f"SELECT 1 FROM cost_attribution_dimensions WHERE dimension_name = {ph}", # nosec B608 - ph is a DB-API placeholder.
(name,),
)
if cur.fetchone() is None:
cur.execute(
cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only DB-API placeholder chars are interpolated; values are bound.
"INSERT INTO cost_attribution_dimensions "
f"(dimension_name, dimension_label, dimension_order) VALUES ({ph}, {ph}, {ph})", # nosec B608 - ph is a DB-API placeholder.
(name, label, order),
Expand All @@ -602,7 +602,7 @@ def append(self, record: UsageRecord) -> None:
placeholders = ", ".join(ph for _ in _USAGE_COLUMNS)
columns = ", ".join(_USAGE_COLUMNS)
cur = self._conn.cursor()
cur.execute(
cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: columns are the fixed _USAGE_COLUMNS constant; values are bound.
f"INSERT INTO llm_usage_records ({columns}) VALUES ({placeholders})", # nosec B608 - columns are fixed _USAGE_COLUMNS.
tuple(row.get(column) for column in _USAGE_COLUMNS),
)
Expand All @@ -622,7 +622,7 @@ def query(self, start: Optional[int] = None, end: Optional[int] = None) -> List[
where = f" WHERE {' AND '.join(clauses)}" if clauses else ""
columns = ", ".join(_USAGE_COLUMNS)
cur = self._conn.cursor()
cur.execute(f"SELECT {columns} FROM llm_usage_records{where}", tuple(params)) # nosec B608 - columns and clauses are fixed.
cur.execute(f"SELECT {columns} FROM llm_usage_records{where}", tuple(params)) # nosec B608 - columns and clauses are fixed. # nosemgrep -- sqlalchemy-execute-raw-query FP: fixed columns and clause templates; all values are bound.
return [dict(zip(_USAGE_COLUMNS, values)) for values in cur.fetchall()]


Expand Down
28 changes: 26 additions & 2 deletions contextual_orchestrator/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def __init__(
@staticmethod
def _build_ssl_context(ca_bundle: str | None, verify_tls: bool) -> ssl.SSLContext:
if not verify_tls:
return ssl._create_unverified_context() # nosec B323 - explicit dev-only provider TLS opt-out.
return ssl._create_unverified_context() # nosec B323 - explicit dev-only provider TLS opt-out. # nosemgrep -- unverified-ssl-context: intentional, default-secure (verify_tls defaults True) dev-only opt-out for self-signed endpoints.
if ca_bundle:
if not os.path.isfile(ca_bundle):
raise ValueError(f"provider CA bundle does not exist: {ca_bundle}")
Expand Down Expand Up @@ -307,7 +307,7 @@ def _send(self, agent: ModelAgent, payload: dict[str, Any]) -> str:

def _open_provider(self, request: urllib.request.Request) -> Any:
"""Open a provider request built from a validated provider URL."""
return urllib.request.urlopen( # nosec B310 - request URL comes from _provider_url after provider validation.
return urllib.request.urlopen( # nosec B310 - request URL comes from _provider_url after provider validation. # nosemgrep -- dynamic-urllib-use: URL is built by _provider_url after scheme/host validation; egress to loopback/private/reserved is blocked.
request,
timeout=self.timeout,
context=self._ssl_context,
Expand Down Expand Up @@ -8517,6 +8517,30 @@ def chat_completion_response(
}



def text_completion_response(
result: dict[str, Any],
model: str = "contextual-orchestrator",
usage: dict[str, int] | None = None,
) -> dict[str, Any]: # pragma: no cover
"""Wrap orchestration output as OpenAI legacy ``text_completion`` (``/v1/completions``)."""
return {
"id": f"cmpl-{int(time.time() * 1000)}",
"object": "text_completion",
"created": int(time.time()),
"model": model,
"choices": [
{
"index": 0,
"text": result["answer"],
"logprobs": None,
"finish_reason": "stop",
}
],
"usage": usage or {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
}


_STREAM_CHUNK_SIZE = 32


Expand Down
102 changes: 102 additions & 0 deletions contextual_orchestrator/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
TaskOrchestrator,
chat_completion_chunks,
chat_completion_response,
text_completion_response,
redact_value,
sse_stream_body,
)
Expand All @@ -46,6 +47,11 @@
} | OPENAI_PASSTHROUGH_PARAM_KEYS
ALLOWED_BATCH_KEYS = {"requests", "attribution", "routing", "model"}
ALLOWED_EMBEDDINGS_BATCH_KEYS = {"model", "input", "inputs", "endpoint", "metadata", "attribution"}
ALLOWED_COMPLETIONS_KEYS = {
"model", "prompt", "stream", "echo", "suffix", "best_of", "logprobs",
"n", "max_tokens", "temperature", "top_p", "stop", "user", "seed",
"presence_penalty", "frequency_penalty", "logit_bias",
} | {"attribution", "routing"}
ALLOWED_MESSAGE_ROLES = {"system", "user", "assistant", "tool"}
ALLOWED_MODES = {"auto", "route", "conduct"}
ALLOWED_SIMULATE_KEYS = {"prompt", "mode", "include_orchestration_trace"}
Expand Down Expand Up @@ -177,6 +183,65 @@ def _reject_unknown_keys(body: dict[str, Any], allowed: set[str]) -> None:
raise RequestError(400, "unknown_fields", "request contains unsupported fields", {"fields": unknown})




def _validate_completion_prompt(prompt: Any) -> list[dict[str, str]]:
"""Legacy Completions ``prompt`` → single user message list."""
if isinstance(prompt, str):
if not prompt.strip():
raise RequestError(400, "invalid_prompt", "prompt must be a non-empty string or array")
return [{"role": "user", "content": prompt}]
if isinstance(prompt, list):
if not prompt:
raise RequestError(400, "invalid_prompt", "prompt must be a non-empty string or array")
parts: list[str] = []
for item in prompt:
if not isinstance(item, str):
raise RequestError(400, "invalid_prompt", "prompt array items must be strings")
parts.append(item)
joined = "\n".join(parts)
if not joined.strip():
raise RequestError(400, "invalid_prompt", "prompt must be a non-empty string or array")
return [{"role": "user", "content": joined}]
raise RequestError(400, "invalid_prompt", "prompt must be a non-empty string or array")


def _validate_completions_echo_best_of_suffix(body: dict[str, Any]) -> None:
"""Type-check legacy OpenAI Completions ``echo``, ``best_of``, and ``suffix``.

- ``echo``: boolean when present
- ``suffix``: string when present
- ``best_of``: integer >= 1; when ``n`` is set, best_of must be >= n;
incompatible with ``stream=true``
"""
if "echo" in body and not isinstance(body.get("echo"), bool):
raise RequestError(400, "invalid_echo", "echo must be a boolean")
if "suffix" in body:
suffix = body.get("suffix")
if suffix is not None and not isinstance(suffix, str):
raise RequestError(400, "invalid_suffix", "suffix must be a string or null")
if "best_of" not in body:
return
best_of = body.get("best_of")
if not isinstance(best_of, int) or isinstance(best_of, bool) or best_of < 1:
raise RequestError(400, "invalid_best_of", "best_of must be a positive integer")
if body.get("stream") is True:
raise RequestError(
400,
"invalid_best_of",
"best_of is not supported when stream is true",
)
if "n" in body:
n_value = body.get("n")
if isinstance(n_value, int) and not isinstance(n_value, bool) and best_of < n_value:
raise RequestError(
400,
"invalid_best_of",
"best_of must be greater than or equal to n",
{"best_of": best_of, "n": n_value},
)


def _validate_mode(mode: Any) -> str:
if not isinstance(mode, str) or mode not in ALLOWED_MODES:
raise RequestError(400, "invalid_mode", "mode must be auto, route, or conduct")
Expand Down Expand Up @@ -711,6 +776,43 @@ def do_POST(self) -> None: # noqa: N802
self._send(orchestrator.add_agent(segments[3], body), 201)
return

if path == "/v1/completions":
# Legacy OpenAI Completions: prompt → route → text_completion.
_reject_unknown_keys(body, ALLOWED_COMPLETIONS_KEYS)
_validate_completions_echo_best_of_suffix(body)
if body.get("stream") is True:
raise RequestError(
400,
"invalid_request",
"stream is not supported on /v1/completions; use /v1/chat/completions",
)
messages = _validate_completion_prompt(body.get("prompt"))
model_name = str(body.get("model", "contextual-orchestrator"))
attribution = _validate_attribution(body.get("attribution"))
routing = _validate_routing(body.get("routing"))
started_at = time.perf_counter()
result = self._run(lambda: coordinator.complete(
messages,
mode="route",
attribution=attribution,
hints=routing,
model_name=model_name,
workflow_run_id=f"run_{uuid.uuid4().hex}",
))
orchestrator.record_analytics_event(
"text_completion_requested",
{
"endpoint_path": "/v1/completions",
"actor_scope": "inference",
"status_code": 200,
"run_mode": "route",
"duration_ms": round((time.perf_counter() - started_at) * 1000, 2),
},
)
self._send(text_completion_response(
result, model=model_name, usage=result.get("usage"),
))
return
if path == "/v1/chat/completions":
_reject_unknown_keys(body, ALLOWED_CHAT_KEYS)
if PASSTHROUGH_TRIGGER_KEYS & set(body):
Expand Down
134 changes: 134 additions & 0 deletions tests/test_completions_echo_best_of_suffix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""Legacy Completions echo (bool), best_of (int), and suffix (str) validation."""

from __future__ import annotations

import json
import threading
import urllib.error
import urllib.request
from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from contextual_orchestrator import ModelAgent, TaskOrchestrator # noqa: E402
from contextual_orchestrator.server import ( # noqa: E402
RequestError,
SecurityConfig,
_validate_completions_echo_best_of_suffix,
build_server,
)

_TEST_AUTH_TOKEN = "cmpl_fields_token" # noqa: S105


def build() -> TaskOrchestrator:
return TaskOrchestrator(
[ModelAgent("general_agent", "mock-generalist", tags=("reasoning", "writing"))]
)


def test_validate_echo_best_of_suffix() -> None:
_validate_completions_echo_best_of_suffix({})
_validate_completions_echo_best_of_suffix({"echo": True, "suffix": "x", "best_of": 2, "n": 1})
_validate_completions_echo_best_of_suffix({"suffix": None})
try:
_validate_completions_echo_best_of_suffix({"echo": "yes"})
raise AssertionError("expected invalid_echo")
except RequestError as exc:
assert exc.code == "invalid_echo"
try:
_validate_completions_echo_best_of_suffix({"suffix": 12})
raise AssertionError("expected invalid_suffix")
except RequestError as exc:
assert exc.code == "invalid_suffix"
try:
_validate_completions_echo_best_of_suffix({"best_of": 0})
raise AssertionError("expected invalid_best_of")
except RequestError as exc:
assert exc.code == "invalid_best_of"
try:
_validate_completions_echo_best_of_suffix({"best_of": 1, "stream": True})
raise AssertionError("expected invalid_best_of stream")
except RequestError as exc:
assert exc.code == "invalid_best_of"
try:
_validate_completions_echo_best_of_suffix({"best_of": 1, "n": 3})
raise AssertionError("expected invalid_best_of n")
except RequestError as exc:
assert exc.code == "invalid_best_of"


def _post(port: int, payload: dict) -> tuple[int, dict]:
request = urllib.request.Request(
f"http://127.0.0.1:{port}/v1/completions",
data=json.dumps(payload).encode("utf-8"),
headers={
"content-type": "application/json",
"authorization": f"Bearer {_TEST_AUTH_TOKEN}",
"connection": "close",
},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=10) as response:
return response.status, json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
return exc.code, json.loads(exc.read().decode("utf-8"))


def test_http_rejects_bad_echo_and_best_of() -> None:
server = build_server(build(), port=0, security=SecurityConfig(auth_token=_TEST_AUTH_TOKEN))
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
port = server.server_address[1]
try:
status, body = _post(port, {"model": "mock-generalist", "prompt": "hi", "echo": 1})
assert status == 400, body
assert body["error"]["code"] == "invalid_echo"

status, body = _post(port, {"model": "mock-generalist", "prompt": "hi", "best_of": 0})
assert status == 400, body
assert body["error"]["code"] == "invalid_best_of"

status, body = _post(
port, {"model": "mock-generalist", "prompt": "hi", "best_of": 1, "n": 2}
)
assert status == 400, body
assert body["error"]["code"] == "invalid_best_of"
finally:
server.shutdown()
thread.join(timeout=5)


def test_http_accepts_valid_echo_suffix_best_of() -> None:
server = build_server(build(), port=0, security=SecurityConfig(auth_token=_TEST_AUTH_TOKEN))
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
port = server.server_address[1]
try:
status, body = _post(
port,
{
"model": "mock-generalist",
"prompt": "hello gateway",
"echo": False,
"suffix": "!",
"best_of": 2,
"n": 1,
},
)
assert status == 200, body
assert body["object"] == "text_completion"
assert isinstance(body["choices"][0]["text"], str)
assert body["choices"][0]["text"] # real mock answer non-empty
finally:
server.shutdown()
thread.join(timeout=5)


if __name__ == "__main__":
test_validate_echo_best_of_suffix()
test_http_rejects_bad_echo_and_best_of()
test_http_accepts_valid_echo_suffix_best_of()
print("ok")
Loading