2024-03-05 16:30:16 +00:00
|
|
|
import json
|
|
|
|
from typing import (
|
|
|
|
Any,
|
|
|
|
Dict,
|
|
|
|
List,
|
|
|
|
Union,
|
|
|
|
)
|
|
|
|
|
2024-04-04 20:22:48 +00:00
|
|
|
from langchain_core._api import deprecated
|
|
|
|
from langchain_core.pydantic_v1 import Field
|
2024-03-05 16:30:16 +00:00
|
|
|
|
|
|
|
from langchain_anthropic.chat_models import ChatAnthropic
|
|
|
|
|
|
|
|
SYSTEM_PROMPT_FORMAT = """In this environment you have access to a set of tools you can use to answer the user's question.
|
|
|
|
|
|
|
|
You may call them like this:
|
|
|
|
<function_calls>
|
|
|
|
<invoke>
|
|
|
|
<tool_name>$TOOL_NAME</tool_name>
|
|
|
|
<parameters>
|
|
|
|
<$PARAMETER_NAME>$PARAMETER_VALUE</$PARAMETER_NAME>
|
|
|
|
...
|
|
|
|
</parameters>
|
|
|
|
</invoke>
|
|
|
|
</function_calls>
|
|
|
|
|
|
|
|
Here are the tools available:
|
|
|
|
<tools>
|
|
|
|
{formatted_tools}
|
|
|
|
</tools>""" # noqa: E501
|
|
|
|
|
|
|
|
TOOL_FORMAT = """<tool_description>
|
|
|
|
<tool_name>{tool_name}</tool_name>
|
|
|
|
<description>{tool_description}</description>
|
|
|
|
<parameters>
|
|
|
|
{formatted_parameters}
|
|
|
|
</parameters>
|
|
|
|
</tool_description>"""
|
|
|
|
|
|
|
|
TOOL_PARAMETER_FORMAT = """<parameter>
|
|
|
|
<name>{parameter_name}</name>
|
|
|
|
<type>{parameter_type}</type>
|
|
|
|
<description>{parameter_description}</description>
|
|
|
|
</parameter>"""
|
|
|
|
|
|
|
|
|
2024-03-05 22:19:40 +00:00
|
|
|
def _get_type(parameter: Dict[str, Any]) -> str:
|
|
|
|
if "type" in parameter:
|
|
|
|
return parameter["type"]
|
|
|
|
if "anyOf" in parameter:
|
|
|
|
return json.dumps({"anyOf": parameter["anyOf"]})
|
|
|
|
if "allOf" in parameter:
|
|
|
|
return json.dumps({"allOf": parameter["allOf"]})
|
|
|
|
return json.dumps(parameter)
|
|
|
|
|
|
|
|
|
2024-03-05 16:30:16 +00:00
|
|
|
def get_system_message(tools: List[Dict]) -> str:
|
|
|
|
tools_data: List[Dict] = [
|
|
|
|
{
|
|
|
|
"tool_name": tool["name"],
|
|
|
|
"tool_description": tool["description"],
|
|
|
|
"formatted_parameters": "\n".join(
|
|
|
|
[
|
|
|
|
TOOL_PARAMETER_FORMAT.format(
|
|
|
|
parameter_name=name,
|
2024-03-05 22:19:40 +00:00
|
|
|
parameter_type=_get_type(parameter),
|
2024-03-05 16:30:16 +00:00
|
|
|
parameter_description=parameter.get("description"),
|
|
|
|
)
|
|
|
|
for name, parameter in tool["parameters"]["properties"].items()
|
|
|
|
]
|
|
|
|
),
|
|
|
|
}
|
|
|
|
for tool in tools
|
|
|
|
]
|
|
|
|
tools_formatted = "\n".join(
|
|
|
|
[
|
|
|
|
TOOL_FORMAT.format(
|
|
|
|
tool_name=tool["tool_name"],
|
|
|
|
tool_description=tool["tool_description"],
|
|
|
|
formatted_parameters=tool["formatted_parameters"],
|
|
|
|
)
|
|
|
|
for tool in tools_data
|
|
|
|
]
|
|
|
|
)
|
|
|
|
return SYSTEM_PROMPT_FORMAT.format(formatted_tools=tools_formatted)
|
|
|
|
|
|
|
|
|
|
|
|
def _xml_to_dict(t: Any) -> Union[str, Dict[str, Any]]:
|
|
|
|
# Base case: If the element has no children, return its text or an empty string.
|
|
|
|
if len(t) == 0:
|
|
|
|
return t.text or ""
|
|
|
|
|
|
|
|
# Recursive case: The element has children. Convert them into a dictionary.
|
|
|
|
d: Dict[str, Any] = {}
|
|
|
|
for child in t:
|
|
|
|
if child.tag not in d:
|
|
|
|
d[child.tag] = _xml_to_dict(child)
|
|
|
|
else:
|
|
|
|
# Handle multiple children with the same tag
|
|
|
|
if not isinstance(d[child.tag], list):
|
|
|
|
d[child.tag] = [d[child.tag]] # Convert existing entry into a list
|
|
|
|
d[child.tag].append(_xml_to_dict(child))
|
|
|
|
return d
|
|
|
|
|
|
|
|
|
2024-03-05 22:19:40 +00:00
|
|
|
def _xml_to_function_call(invoke: Any, tools: List[Dict]) -> Dict[str, Any]:
|
|
|
|
name = invoke.find("tool_name").text
|
|
|
|
arguments = _xml_to_dict(invoke.find("parameters"))
|
|
|
|
|
|
|
|
# make list elements in arguments actually lists
|
|
|
|
filtered_tools = [tool for tool in tools if tool["name"] == name]
|
|
|
|
if len(filtered_tools) > 0 and not isinstance(arguments, str):
|
|
|
|
tool = filtered_tools[0]
|
|
|
|
for key, value in arguments.items():
|
|
|
|
if key in tool["parameters"]["properties"]:
|
|
|
|
if "type" in tool["parameters"]["properties"][key]:
|
|
|
|
if tool["parameters"]["properties"][key][
|
|
|
|
"type"
|
|
|
|
] == "array" and not isinstance(value, list):
|
|
|
|
arguments[key] = [value]
|
|
|
|
if (
|
|
|
|
tool["parameters"]["properties"][key]["type"] != "object"
|
|
|
|
and isinstance(value, dict)
|
|
|
|
and len(value.keys()) == 1
|
|
|
|
):
|
|
|
|
arguments[key] = list(value.values())[0]
|
|
|
|
|
|
|
|
return {
|
|
|
|
"function": {
|
|
|
|
"name": name,
|
|
|
|
"arguments": json.dumps(arguments),
|
|
|
|
},
|
|
|
|
"type": "function",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _xml_to_tool_calls(elem: Any, tools: List[Dict]) -> List[Dict[str, Any]]:
|
2024-03-05 16:30:16 +00:00
|
|
|
"""
|
|
|
|
Convert an XML element and its children into a dictionary of dictionaries.
|
|
|
|
"""
|
|
|
|
invokes = elem.findall("invoke")
|
2024-03-05 22:19:40 +00:00
|
|
|
|
|
|
|
return [_xml_to_function_call(invoke, tools) for invoke in invokes]
|
2024-03-05 16:30:16 +00:00
|
|
|
|
|
|
|
|
2024-04-04 20:22:48 +00:00
|
|
|
@deprecated(
|
|
|
|
"0.1.5",
|
|
|
|
removal="0.2.0",
|
|
|
|
alternative="ChatAnthropic",
|
|
|
|
message=(
|
|
|
|
"Tool-calling is now officially supported by the Anthropic API so this "
|
|
|
|
"workaround is no longer needed."
|
|
|
|
),
|
|
|
|
)
|
2024-03-05 16:30:16 +00:00
|
|
|
class ChatAnthropicTools(ChatAnthropic):
|
|
|
|
"""Chat model for interacting with Anthropic functions."""
|
|
|
|
|
|
|
|
_xmllib: Any = Field(default=None)
|