11from typing import Any , Dict , List , Optional , Union
22
33from langchain_core .language_models .chat_models import BaseChatModel
4- from langchain_core .messages import AIMessage , BaseMessage , HumanMessage , SystemMessage
4+ from langchain_core .messages import AIMessage , HumanMessage , SystemMessage
55from ldai import LDMessage , log
66from ldai .models import AIConfigKind
77from ldai .providers import ToolRegistry
@@ -51,18 +51,12 @@ def convert_messages_to_langchain(
5151 return result
5252
5353
54- def create_langchain_model (ai_config : AIConfigKind , tool_registry : Optional [ ToolRegistry ] = None ) -> BaseChatModel :
54+ def create_langchain_model (ai_config : AIConfigKind ) -> BaseChatModel :
5555 """
5656 Create a LangChain BaseChatModel from a LaunchDarkly AI configuration.
5757
58- If the config includes tool definitions and a tool_registry is provided, tools found
59- in the registry are bound to the model. Tools not found in the registry are skipped
60- with a warning. Built-in provider tools (e.g. code_interpreter) are not supported
61- via LangChain's bind_tools abstraction and are skipped with a warning.
62-
6358 :param ai_config: The LaunchDarkly AI configuration
64- :param tool_registry: Optional registry mapping tool names to callable implementations
65- :return: A configured LangChain BaseChatModel, with tools bound if applicable
59+ :return: A configured LangChain BaseChatModel
6660 """
6761 from langchain .chat_models import init_chat_model
6862
@@ -73,112 +67,51 @@ def create_langchain_model(ai_config: AIConfigKind, tool_registry: Optional[Tool
7367 model_name = model_dict .get ('name' , '' )
7468 provider = provider_dict .get ('name' , '' )
7569 parameters = dict (model_dict .get ('parameters' ) or {})
76- tool_definitions = parameters .pop ('tools' , []) or []
70+ parameters .pop ('tools' , None )
7771 mapped_provider = map_provider (provider )
7872
7973 # Bedrock requires the foundation provider (e.g. Bedrock:Anthropic) passed in
8074 # parameters separately from model_provider, which is used for LangChain routing.
8175 if mapped_provider == 'bedrock_converse' and 'provider' not in parameters :
8276 parameters ['provider' ] = provider .removeprefix ('bedrock:' )
8377
84- model = init_chat_model (
78+ return init_chat_model (
8579 model_name ,
8680 model_provider = mapped_provider ,
8781 ** parameters ,
8882 )
8983
90- if tool_definitions and tool_registry is not None :
91- bindable = _resolve_tools_for_langchain (tool_definitions , tool_registry )
92- if bindable :
93- model = model .bind_tools (bindable )
94-
95- return model
96-
97-
98- def _iter_valid_tools (
99- tool_definitions : List [Dict [str , Any ]],
100- tool_registry : ToolRegistry ,
101- ) -> List [tuple ]:
102- """
103- Filter LD tool definitions against a registry, returning (name, td) pairs for each
104- valid function tool that has a callable implementation. Built-in provider tools and
105- tools missing from the registry are skipped with a warning.
106- """
107- valid = []
108- for td in tool_definitions :
109- if not isinstance (td , dict ):
110- continue
111-
112- tool_type = td .get ('type' )
113- if tool_type and tool_type != 'function' :
114- log .warning (
115- f"Built-in tool '{ tool_type } ' is not reliably supported via LangChain and will be skipped. "
116- "Use a provider-specific runner to use built-in provider tools."
117- )
118- continue
119-
120- name = td .get ('name' )
121- if not name :
122- continue
123-
124- if name not in tool_registry :
125- log .warning (f"Tool '{ name } ' is defined in the AI config but was not found in the tool registry; skipping." )
126- continue
127-
128- valid .append ((name , td ))
129-
130- return valid
131-
13284
133- def _resolve_tools_for_langchain (
134- tool_definitions : List [Dict [str , Any ]],
135- tool_registry : ToolRegistry ,
136- ) -> List [Dict [str , Any ]]:
137- """
138- Match LD tool definitions against a registry, returning function-calling tool dicts
139- for tools that have a callable implementation. Built-in provider tools and tools
140- missing from the registry are skipped with a warning.
141- """
142- return [
143- {
144- 'type' : 'function' ,
145- 'function' : {
146- 'name' : name ,
147- 'description' : td .get ('description' , '' ),
148- 'parameters' : td .get ('parameters' , {'type' : 'object' , 'properties' : {}}),
149- },
150- }
151- for name , td in _iter_valid_tools (tool_definitions , tool_registry )
152- ]
153-
154-
155- def build_structured_tools (ai_config : AIConfigKind , tool_registry : ToolRegistry ) -> List [Any ]:
85+ def build_tools (ai_config : AIConfigKind , tool_registry : ToolRegistry ) -> List [Any ]:
15686 """
157- Build a list of LangChain StructuredTool instances from LD tool definitions and a registry .
87+ Return callables from the registry for each tool defined in the AI config .
15888
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 .
89+ Tools not found in the registry are skipped with a warning. The returned
90+ callables can be passed directly to bind_tools or langchain.agents.create_agent.
91+ Functions should have type-annotated parameters so LangChain can infer the schema .
16292
16393 :param ai_config: The LaunchDarkly AI configuration
16494 :param tool_registry: Registry mapping tool names to callable implementations
165- :return: List of StructuredTool instances ready to pass to langchain.agents. create_agent
95+ :return: List of callables ready to pass to bind_tools or create_agent
16696 """
167- from langchain_core .tools import StructuredTool
168-
16997 config_dict = ai_config .to_dict ()
17098 model_dict = config_dict .get ('model' ) or {}
17199 parameters = dict (model_dict .get ('parameters' ) or {})
172100 tool_definitions = parameters .pop ('tools' , []) or []
173101
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- ]
102+ tools = []
103+ for td in tool_definitions :
104+ if not isinstance (td , dict ):
105+ continue
106+ name = td .get ('name' )
107+ if not name :
108+ continue
109+ fn = tool_registry .get (name )
110+ if fn is None :
111+ log .warning (f"Tool '{ name } ' is defined in the AI config but was not found in the tool registry; skipping." )
112+ continue
113+ tools .append (fn )
114+ return tools
182115
183116
184117def get_ai_usage_from_response (response : Any ) -> Optional [TokenUsage ]:
@@ -234,6 +167,20 @@ def get_tool_calls_from_response(response: Any) -> List[str]:
234167 return names
235168
236169
170+ def extract_last_message_content (messages : List [Any ]) -> str :
171+ """
172+ Extract the string content of the last message in a list.
173+
174+ :param messages: List of LangChain message objects
175+ :return: String content of the last message, or empty string if none or content is not a str
176+ """
177+ if messages :
178+ last = messages [- 1 ]
179+ if hasattr (last , 'content' ) and isinstance (last .content , str ):
180+ return last .content
181+ return ''
182+
183+
237184def sum_token_usage_from_messages (messages : List [Any ]) -> Optional [TokenUsage ]:
238185 """
239186 Sum token usage across LangChain messages using get_ai_usage_from_response per message.
0 commit comments