Skip to content

Commit 842bd34

Browse files
jsonbaileyclaude
andcommitted
fix: Replace fan-out with handoff routing and fix OpenAI message ordering
- Add _coalesce_tool_messages_for_openai to fix parallel fan-out causing OpenAI 400 errors when sibling branch tool_calls have no ToolMessages - Add LDMetricsCallbackHandler for per-node token/tool/latency tracking - Add build_structured_tools with async callable support via inspect - Replace fan-out static edges with LLM-driven handoff tools (Command(goto=)) - Switch WorkflowState.messages to add_messages reducer - Add parallel_tool_calls=False when handoff tools present - Remove _coalesce_tool_messages_for_openai (no longer needed with handoffs) - Demote diagnostic logs to debug; exclude handoff tools from LD tracking - Consolidate token extraction to use get_ai_usage_from_response - Fix TestBuildTools to import build_structured_tools; cover async callables Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 53fd95e commit 842bd34

7 files changed

Lines changed: 1600 additions & 92 deletions

File tree

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

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import inspect
12
from typing import Any, Dict, List, Optional, Union
23

34
from langchain_core.language_models.chat_models import BaseChatModel
@@ -156,9 +157,10 @@ def build_structured_tools(ai_config: AIConfigKind, tool_registry: ToolRegistry)
156157
"""
157158
Build a list of LangChain StructuredTool instances from LD tool definitions and a registry.
158159
159-
Tools found in the registry are wrapped as StructuredTool with the name and description
160-
from the LD config. Built-in provider tools and tools missing from the registry are
161-
skipped with a warning.
160+
Tools found in the registry are wrapped as StructuredTool using the LD config key as the
161+
tool name so the model's tool calls match ToolNode lookup. Async callables use ``coroutine=``
162+
so LangGraph invokes them correctly. Built-in provider tools and tools missing from the
163+
registry are skipped with a warning.
162164
163165
:param ai_config: The LaunchDarkly AI configuration
164166
:param tool_registry: Registry mapping tool names to callable implementations
@@ -171,14 +173,17 @@ def build_structured_tools(ai_config: AIConfigKind, tool_registry: ToolRegistry)
171173
parameters = dict(model_dict.get('parameters') or {})
172174
tool_definitions = parameters.pop('tools', []) or []
173175

174-
return [
175-
StructuredTool.from_function(
176-
func=tool_registry[name],
177-
name=name,
178-
description=td.get('description', ''),
179-
)
180-
for name, td in _iter_valid_tools(tool_definitions, tool_registry)
181-
]
176+
tools = []
177+
for name, td in _iter_valid_tools(tool_definitions, tool_registry):
178+
fn = tool_registry[name]
179+
raw_desc = td.get('description') if isinstance(td.get('description'), str) else ''
180+
description = raw_desc.strip() or (getattr(fn, '__doc__', None) or '').strip() or f'Tool {name}'
181+
if inspect.iscoroutinefunction(fn):
182+
tool = StructuredTool.from_function(coroutine=fn, name=name, description=description)
183+
else:
184+
tool = StructuredTool.from_function(fn, name=name, description=description)
185+
tools.append(tool)
186+
return tools
182187

183188

184189
def get_ai_usage_from_response(response: Any) -> Optional[TokenUsage]:
@@ -234,6 +239,20 @@ def get_tool_calls_from_response(response: Any) -> List[str]:
234239
return names
235240

236241

242+
def extract_last_message_content(messages: List[Any]) -> str:
243+
"""
244+
Extract the string content of the last message in a list.
245+
246+
:param messages: List of LangChain message objects
247+
:return: String content of the last message, or empty string if none or content is not a str
248+
"""
249+
if messages:
250+
last = messages[-1]
251+
if hasattr(last, 'content') and isinstance(last.content, str):
252+
return last.content
253+
return ''
254+
255+
237256
def sum_token_usage_from_messages(messages: List[Any]) -> Optional[TokenUsage]:
238257
"""
239258
Sum token usage across LangChain messages using get_ai_usage_from_response per message.

0 commit comments

Comments
 (0)