Skip to content

Commit 42fe82c

Browse files
jsonbaileyclaude
andcommitted
fix: eliminate fire-and-forget tasks and harden eval task tracking
- Redesign ManagedModel._track_judge_results to call evaluator.evaluate() internally and attach tracking via add_done_callback, returning the task so the reference is held by ModelResponse.evaluations — no GC risk - Warn instead of silently dropping eval tasks when the LangGraph ContextVar is unexpectedly unset in a node's execution context - Make AgentGraphDefinition.create_tracker a required parameter; all production and test call sites already supply it, and this matches the invariant that runners only execute on enabled (always-tracked) graphs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b65dd04 commit 42fe82c

4 files changed

Lines changed: 28 additions & 11 deletions

File tree

packages/ai-providers/server-ai-langchain/src/ldai_langchain/langgraph_agent_graph_runner.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,14 @@ async def invoke(state: WorkflowState) -> dict:
192192
response.content if hasattr(response, 'content') else str(response)
193193
)
194194
task = node_obj.get_config().evaluator.evaluate(input_text, output_text)
195-
_run_eval_tasks.get({}).setdefault(nk, []).append(task)
195+
run_tasks = _run_eval_tasks.get(None)
196+
if run_tasks is not None:
197+
run_tasks.setdefault(nk, []).append(task)
198+
else:
199+
log.warning(
200+
f"LangGraphAgentGraphRunner: eval task for node '{nk}' "
201+
"has no run context; judge results will not be tracked"
202+
)
196203

197204
return {'messages': [response]}
198205

packages/ai-providers/server-ai-langchain/tests/test_langgraph_callback_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ async def test_flush_with_no_graph_key_on_node_tracker():
433433
nodes=nodes,
434434
context=context,
435435
enabled=True,
436-
create_tracker=lambda: None,
436+
create_tracker=lambda: AIGraphTracker(mock_ld_client, 'v1', 'test-graph', 1, context),
437437
)
438438

439439
handler = LDMetricsCallbackHandler({'root-agent'}, {})

packages/sdk/server-ai/src/ldai/agent_graph/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def __init__(
5252
nodes: Dict[str, AgentGraphNode],
5353
context: Context,
5454
enabled: bool,
55-
create_tracker: Optional[Callable[[], AIGraphTracker]] = None,
55+
create_tracker: Callable[[], AIGraphTracker],
5656
):
5757
self._agent_graph = agent_graph
5858
self._context = context

packages/sdk/server-ai/src/ldai/managed_model.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,32 @@ async def invoke(self, prompt: str) -> ModelResponse:
4949
lambda result: result.metrics,
5050
)
5151

52-
evaluator = self._ai_config.evaluator
5352
input_text = '\r\n'.join(m.content for m in self._messages) if self._messages else ''
5453
output_text = response.message.content
55-
response.evaluations = evaluator.evaluate(input_text, output_text)
56-
self._track_judge_results(tracker, response.evaluations)
54+
response.evaluations = self._track_judge_results(tracker, input_text, output_text)
5755

5856
self._messages.append(response.message)
5957
return response
6058

61-
def _track_judge_results(self, tracker: LDAIConfigTracker, eval_task: asyncio.Task[List[JudgeResult]]) -> None:
62-
async def _run() -> None:
63-
results = await eval_task
64-
for r in results:
59+
def _track_judge_results(
60+
self,
61+
tracker: LDAIConfigTracker,
62+
input_text: str,
63+
output_text: str,
64+
) -> asyncio.Task[List[JudgeResult]]:
65+
eval_task = self._ai_config.evaluator.evaluate(input_text, output_text)
66+
67+
def _on_done(task: asyncio.Task) -> None:
68+
if task.cancelled():
69+
return
70+
if task.exception() is not None:
71+
return
72+
for r in task.result():
6573
if r.success:
6674
tracker.track_judge_result(r)
67-
asyncio.create_task(_run())
75+
76+
eval_task.add_done_callback(_on_done)
77+
return eval_task
6878

6979
def get_messages(self, include_config_messages: bool = False) -> List[LDMessage]:
7080
"""

0 commit comments

Comments
 (0)