from collections.abc import Callable, Sequence from typing import ( Any, Literal, ) from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.language_models import BaseChatModel, LanguageModelInput from langchain_core.messages import ( AIMessage, BaseMessage, ToolCall, ) from langchain_core.outputs import ChatGeneration, ChatResult from langchain_core.runnables import Runnable, RunnableLambda from langchain_core.tools import BaseTool from pydantic import BaseModel from langgraph.prebuilt.chat_agent_executor import StructuredResponse class FakeToolCallingModel(BaseChatModel): tool_calls: list[list[ToolCall]] | None = None structured_response: StructuredResponse | None = None index: int = 0 tool_style: Literal["openai", "anthropic"] = "openai" def _generate( self, messages: list[BaseMessage], stop: list[str] | None = None, run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> ChatResult: """Top Level call""" messages_string = "-".join([m.content for m in messages]) tool_calls = ( self.tool_calls[self.index % len(self.tool_calls)] if self.tool_calls else [] ) message = AIMessage( content=messages_string, id=str(self.index), tool_calls=tool_calls.copy() ) self.index += 1 return ChatResult(generations=[ChatGeneration(message=message)]) @property def _llm_type(self) -> str: return "fake-tool-call-model" def with_structured_output( self, schema: type[BaseModel] ) -> Runnable[LanguageModelInput, StructuredResponse]: if self.structured_response is None: raise ValueError("Structured response is not set") return RunnableLambda(lambda x: self.structured_response) def bind_tools( self, tools: Sequence[dict[str, Any] | type[BaseModel] | Callable | BaseTool], **kwargs: Any, ) -> Runnable[LanguageModelInput, BaseMessage]: if len(tools) == 0: raise ValueError("Must provide at least one tool") tool_dicts = [] for tool in tools: if isinstance(tool, dict): tool_dicts.append(tool) continue if not isinstance(tool, BaseTool): raise TypeError( "Only BaseTool and dict is supported by FakeToolCallingModel.bind_tools" ) # NOTE: this is a simplified tool spec for testing purposes only if self.tool_style == "openai": tool_dicts.append( { "type": "function", "function": { "name": tool.name, }, } ) elif self.tool_style == "anthropic": tool_dicts.append( { "name": tool.name, } ) return self.bind(tools=tool_dicts)