fix(agent): pass execution context when resolving model context window (#2858)

RunnerContextBuilder called ModelManager.get_model_by_uuid(model_uuid)
without the required tenant context. The TypeError was swallowed at debug
level, so runtime.metadata.model_context_window_tokens was always missing.
Thread the run's ExecutionContext through build_context_from_event.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
This commit is contained in:
RockChinQ
2026-11-04 01:13:39 +08:00
committed by GitHub
co-authored by Claude Opus 5.5 multica-agent
parent 63cb2bc460
commit c62f79f9d7
4 changed files with 29 additions and 8 deletions
@@ -6,6 +6,7 @@ import uuid
import time
import typing
from ...api.http.context import ExecutionContext
from ...core import app
from .descriptor import RunnerDescriptor
from .persistent_state_store import get_persistent_state_store
@@ -191,7 +192,9 @@ class RunnerContextBuilder:
return bool({'invoke', 'stream', 'count_tokens'} & {str(operation) for operation in operations})
return model_resource.get('model_type') != 'rerank'
async def _build_model_context_window_tokens(self, resources: AgentResources) -> int | None:
async def _build_model_context_window_tokens(
self, execution_context: ExecutionContext, resources: AgentResources
) -> int | None:
model_mgr = getattr(self.ap, 'model_mgr', None)
if model_mgr is None:
return None
@@ -205,7 +208,7 @@ class RunnerContextBuilder:
continue
try:
model = await model_mgr.get_model_by_uuid(model_uuid)
model = await model_mgr.get_model_by_uuid(execution_context, model_uuid)
except Exception as exc:
logger = getattr(self.ap, 'logger', None)
if logger is not None:
@@ -220,6 +223,7 @@ class RunnerContextBuilder:
async def build_context_from_event(
self,
execution_context: ExecutionContext,
event: AgentEventEnvelope,
binding: AgentBinding,
descriptor: RunnerDescriptor,
@@ -231,6 +235,7 @@ class RunnerContextBuilder:
Does NOT inline full history by default.
Args:
execution_context: Trusted tenant context used to resolve models
event: Event envelope
binding: Agent binding
descriptor: Runner descriptor
@@ -360,7 +365,7 @@ class RunnerContextBuilder:
persistent_state_store = get_persistent_state_store(self.ap.persistence_mgr.get_db_engine())
state: AgentRunState = await persistent_state_store.build_snapshot_from_event(event, binding, descriptor)
model_context_window_tokens = await self._build_model_context_window_tokens(resources)
model_context_window_tokens = await self._build_model_context_window_tokens(execution_context, resources)
# Build runtime context
runtime: AgentRuntimeContext = {
@@ -171,6 +171,7 @@ class AgentRunOrchestrator:
pass
context = await self.context_builder.build_context_from_event(
execution_context=execution_context,
event=execution_event,
binding=binding,
descriptor=descriptor,
@@ -22,9 +22,17 @@ from langbot.pkg.agent.runner.context_builder import (
)
from langbot.pkg.agent.runner.descriptor import RunnerDescriptor
from langbot.pkg.agent.runner.host_models import AgentEventEnvelope, AgentBinding, BindingScope
from langbot.pkg.api.http.context import ExecutionContext
from langbot.pkg.core import app
EXECUTION_CONTEXT = ExecutionContext(
instance_uuid='test-instance',
workspace_uuid='test-workspace',
placement_generation=1,
)
class TestContextValidation:
"""Test that context builder output validates against SDK RunnerContext."""
@@ -131,6 +139,7 @@ class TestContextValidation:
# Build context
context_dict = await builder.build_context_from_event(
execution_context=EXECUTION_CONTEXT,
event=event,
binding=binding,
descriptor=descriptor,
@@ -199,6 +208,7 @@ class TestContextValidation:
)
mock_get_store.return_value = mock_store
context_dict = await builder.build_context_from_event(
execution_context=EXECUTION_CONTEXT,
event=event,
binding=self._make_binding(),
descriptor=self._make_descriptor(),
@@ -253,6 +263,7 @@ class TestContextValidation:
mock_get_store.return_value = mock_store
context_dict = await builder.build_context_from_event(
execution_context=EXECUTION_CONTEXT,
event=event,
binding=binding,
descriptor=descriptor,
@@ -260,7 +271,7 @@ class TestContextValidation:
)
assert context_dict['runtime']['metadata']['model_context_window_tokens'] == 128000
mock_app.model_mgr.get_model_by_uuid.assert_awaited_once_with('llm-model')
mock_app.model_mgr.get_model_by_uuid.assert_awaited_once_with(EXECUTION_CONTEXT, 'llm-model')
@pytest.mark.asyncio
async def test_model_context_window_uses_primary_llm_only(self):
@@ -289,8 +300,8 @@ class TestContextValidation:
},
]
assert await builder._build_model_context_window_tokens(resources) is None
mock_app.model_mgr.get_model_by_uuid.assert_awaited_once_with('primary-model')
assert await builder._build_model_context_window_tokens(EXECUTION_CONTEXT, resources) is None
mock_app.model_mgr.get_model_by_uuid.assert_awaited_once_with(EXECUTION_CONTEXT, 'primary-model')
@pytest.mark.asyncio
async def test_build_context_preserves_subject_data_for_non_message_events(self):
@@ -338,6 +349,7 @@ class TestContextValidation:
mock_get_store.return_value = mock_store
context_dict = await builder.build_context_from_event(
execution_context=EXECUTION_CONTEXT,
event=event,
binding=binding,
descriptor=descriptor,
@@ -378,6 +390,7 @@ class TestContextValidation:
mock_get_store.return_value = mock_store
context_dict = await builder.build_context_from_event(
execution_context=EXECUTION_CONTEXT,
event=event,
binding=binding,
descriptor=descriptor,
@@ -421,6 +434,7 @@ class TestContextValidation:
mock_get_store.return_value = mock_store
context_dict = await builder.build_context_from_event(
execution_context=EXECUTION_CONTEXT,
event=event,
binding=binding,
descriptor=descriptor,
@@ -459,6 +473,7 @@ class TestContextValidation:
mock_get_store.return_value = mock_store
context_dict = await builder.build_context_from_event(
execution_context=EXECUTION_CONTEXT,
event=event,
binding=binding,
descriptor=descriptor,
@@ -14,7 +14,7 @@ from langbot.pkg.agent.runner.query_entry_adapter import QueryEntryAdapter
from langbot.pkg.agent.runner.context_builder import RunnerContextBuilder
from langbot.pkg.agent.runner.host_models import AgentEventEnvelope
from tests.unit_tests.agent import test_event_first_protocol as event_fixtures
from tests.unit_tests.agent.test_context_validation import TestContextValidation as Helpers
from tests.unit_tests.agent.test_context_validation import EXECUTION_CONTEXT, TestContextValidation as Helpers
from tests.utils.legacy_plugin_contract import load_identity_helper, verified_plugin_root
@@ -35,7 +35,7 @@ async def build(event, runner, source='legacy-session'):
with patch('langbot.pkg.agent.runner.context_builder.get_persistent_state_store') as store:
store.return_value.build_snapshot_from_event = AsyncMock(return_value={})
context = await builder.build_context_from_event(
event, binding, helper._make_descriptor(), helper._make_resources()
EXECUTION_CONTEXT, event, binding, helper._make_descriptor(), helper._make_resources()
)
return context, binding