Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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: 5 additions & 3 deletions contextual_orchestrator/cost_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ 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: python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query -- ph is a DB-API placeholder, value is parameterized below.
f"SELECT 1 FROM cost_attribution_dimensions WHERE dimension_name = {ph}", # nosec B608 - ph is a DB-API placeholder.
(name,),
)
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: python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query -- columns are fixed _USAGE_COLUMNS, values are parameterized below.
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,9 @@ 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( # nosemgrep: python.sqlalchemy.security.sqlalchemy-execute-raw-query.sqlalchemy-execute-raw-query -- columns/clauses are fixed, values are parameterized.
f"SELECT {columns} FROM llm_usage_records{where}", tuple(params) # nosec B608 - columns and clauses are fixed.
)
return [dict(zip(_USAGE_COLUMNS, values)) for values in cur.fetchall()]


Expand Down
8 changes: 7 additions & 1 deletion contextual_orchestrator/cost_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,17 @@ def complete(
hints: Optional[Dict[str, Any]] = None,
model_name: str = "contextual-orchestrator",
workflow_run_id: Optional[str] = None,
reasoning_effort: Optional[str] = None,
) -> Dict[str, Any]:
"""Route a request (sync or batch) and record its usage + cost.

Sync requests run the orchestrator immediately and return the completion
augmented with ``channel``, ``routing_reason``, ``usage``, and the
``usage_record_id``. Batch requests are dispatched to the batch backend
and return a job envelope; their cost is recorded on retrieval.
``reasoning_effort`` only applies to the sync path today — ``BatchRequest``
has no reasoning_effort field, so a request routed to the batch channel
drops the hint rather than partially threading it through pg-llm-batch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A caller who set reasoning_effort=high plus a batch hint gets HTTP 202 and default provider effort.

Documenting the drop in this docstring is not wire honesty. Put reasoning_effort: {requested, status: dropped, reason} on the 202 envelope so the buyer can see the knob was discarded. Do not silently succeed.

"""
routing_hints = hints if isinstance(hints, RoutingHints) else RoutingHints.from_mapping(hints)
prompt_tokens_estimate = self.token_counter.count_messages(messages, model_name)
Expand All @@ -145,7 +149,9 @@ def complete(
"request_count": job.request_count,
}

result = self.orchestrator.run(messages, mode=mode, workflow_run_id=workflow_run_id)
result = self.orchestrator.run(
messages, mode=mode, workflow_run_id=workflow_run_id, reasoning_effort=reasoning_effort
)
record = self._record_completion(
messages=messages,
answer=result.get("answer", ""),
Expand Down
Loading
Loading