Add namespace metadata to deriver LLM calls

This commit is contained in:
adavyas 2026-06-05 14:12:20 -04:00
parent bb6dad9157
commit e88a59932a
6 changed files with 56 additions and 0 deletions

View File

@ -154,6 +154,7 @@ async def process_representation_tasks_batch(
enable_retry=True,
retry_attempts=3,
trace_name="minimal_deriver",
request_metadata={"namespace": settings.NAMESPACE},
telemetry=LLMTelemetryContext(
workspace_name=latest_message.workspace_name,
call_purpose=CallPurpose.DERIVER_REPRESENTATION.value,

View File

@ -75,6 +75,7 @@ async def honcho_llm_call(
trace_name: str | None = None,
iteration_callback: IterationCallback | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> HonchoLLMCallResponse[M]: ...
@ -105,6 +106,7 @@ async def honcho_llm_call(
trace_name: str | None = None,
iteration_callback: IterationCallback | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> HonchoLLMCallResponse[str]: ...
@ -135,6 +137,7 @@ async def honcho_llm_call(
trace_name: str | None = None,
iteration_callback: IterationCallback | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> AsyncIterator[HonchoLLMCallStreamChunk] | StreamingResponseWithMetadata: ...
@ -165,6 +168,7 @@ async def honcho_llm_call(
trace_name: str | None = None,
iteration_callback: IterationCallback | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> (
HonchoLLMCallResponse[Any]
| AsyncIterator[HonchoLLMCallStreamChunk]
@ -243,6 +247,7 @@ async def honcho_llm_call(
selected_config=plan.selected_config,
plan=plan,
telemetry=telemetry,
request_metadata=request_metadata,
)
return await honcho_llm_call_inner(
plan.provider,
@ -263,6 +268,7 @@ async def honcho_llm_call(
selected_config=plan.selected_config,
plan=plan,
telemetry=telemetry,
request_metadata=request_metadata,
)
decorated = _call_with_provider_selection
@ -373,6 +379,7 @@ async def honcho_llm_call(
plan=plan,
telemetry=telemetry,
messages=captured_messages,
request_metadata=request_metadata,
)
return await honcho_llm_call_inner(
plan.provider,
@ -394,6 +401,7 @@ async def honcho_llm_call(
plan=plan,
telemetry=telemetry,
messages=captured_messages,
request_metadata=request_metadata,
)
wrapped = _toolless_call

View File

@ -322,6 +322,8 @@ class OpenAIBackend:
):
if key in extra_params:
params[key] = extra_params[key]
if "metadata" in extra_params:
params["metadata"] = extra_params["metadata"]
return params
def _normalize_response(

View File

@ -210,6 +210,7 @@ async def honcho_llm_call_inner(
selected_config: ModelConfig | None = None,
plan: AttemptPlan | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> HonchoLLMCallResponse[M]: ...
@ -234,6 +235,7 @@ async def honcho_llm_call_inner(
selected_config: ModelConfig | None = None,
plan: AttemptPlan | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> HonchoLLMCallResponse[str]: ...
@ -258,6 +260,7 @@ async def honcho_llm_call_inner(
selected_config: ModelConfig | None = None,
plan: AttemptPlan | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> AsyncIterator[HonchoLLMCallStreamChunk]: ...
@ -281,6 +284,7 @@ async def honcho_llm_call_inner(
selected_config: ModelConfig | None = None,
plan: AttemptPlan | None = None,
telemetry: LLMTelemetryContext | None = None,
request_metadata: dict[str, str] | None = None,
) -> HonchoLLMCallResponse[Any] | AsyncIterator[HonchoLLMCallStreamChunk]:
"""One backend call. No retry, no fallback, no tool loop.
@ -318,6 +322,8 @@ async def honcho_llm_call_inner(
# knobs — they pass through extra_params. execute_completion merges
# build_config_extra_params(effective_config) on top for top_p/seed/etc.
call_extras: dict[str, Any] = {"json_mode": json_mode, "verbosity": verbosity}
if request_metadata is not None:
call_extras["metadata"] = request_metadata
if stream:
# Stream path: setup must run inside the awaited coroutine so it

View File

@ -64,6 +64,7 @@ class TestDeriverProcessing:
== expected_config.thinking_budget_tokens
)
assert kwargs["model_config"].stop_sequences == expected_config.stop_sequences
assert kwargs["request_metadata"] == {"namespace": settings.NAMESPACE}
assert "llm_settings" not in kwargs
async def test_process_representation_tasks_batch_passes_custom_instructions_into_prompt(

View File

@ -227,6 +227,44 @@ async def test_openai_backend_skips_extra_body_when_thinking_budget_zero() -> No
assert "extra_body" not in call
@pytest.mark.asyncio
async def test_openai_backend_passes_request_metadata() -> None:
client = Mock()
client.chat.completions.create = AsyncMock(
return_value=SimpleNamespace(
choices=[
SimpleNamespace(
finish_reason="stop",
message=SimpleNamespace(
content="ok",
tool_calls=[],
reasoning_details=[],
),
)
],
usage=SimpleNamespace(
prompt_tokens=10,
completion_tokens=5,
prompt_tokens_details=None,
),
)
)
backend = OpenAIBackend(client)
await backend.complete(
model="gpt-4.1",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=100,
extra_params={"metadata": {"namespace": "honcho"}},
)
await_args = client.chat.completions.create.await_args
if await_args is None:
raise AssertionError("Expected OpenAI create call")
call = await_args.kwargs
assert call["metadata"] == {"namespace": "honcho"}
@pytest.mark.asyncio
async def test_openai_backend_converts_anthropic_style_tools() -> None:
client = Mock()