|
|
from typing import Any, List, Sequence, Tuple, Union |
|
|
|
|
|
from langchain_core._api import deprecated |
|
|
from langchain_core.agents import AgentAction, AgentFinish |
|
|
from langchain_core.callbacks import Callbacks |
|
|
from langchain_core.language_models import BaseLanguageModel |
|
|
from langchain_core.prompts.base import BasePromptTemplate |
|
|
from langchain_core.prompts.chat import AIMessagePromptTemplate, ChatPromptTemplate |
|
|
from langchain_core.runnables import Runnable, RunnablePassthrough |
|
|
from langchain_core.tools import BaseTool |
|
|
from langchain_core.tools.render import ToolsRenderer, render_text_description |
|
|
|
|
|
from langchain.agents.agent import BaseSingleActionAgent |
|
|
from langchain.agents.format_scratchpad import format_xml |
|
|
from langchain.agents.output_parsers import XMLAgentOutputParser |
|
|
from langchain.agents.xml.prompt import agent_instructions |
|
|
from langchain.chains.llm import LLMChain |
|
|
|
|
|
|
|
|
@deprecated("0.1.0", alternative="create_xml_agent", removal="1.0") |
|
|
class XMLAgent(BaseSingleActionAgent): |
|
|
"""Agent that uses XML tags. |
|
|
|
|
|
Args: |
|
|
tools: list of tools the agent can choose from |
|
|
llm_chain: The LLMChain to call to predict the next action |
|
|
|
|
|
Examples: |
|
|
|
|
|
.. code-block:: python |
|
|
|
|
|
from langchain.agents import XMLAgent |
|
|
from langchain |
|
|
|
|
|
tools = ... |
|
|
model = |
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
tools: List[BaseTool] |
|
|
"""List of tools this agent has access to.""" |
|
|
llm_chain: LLMChain |
|
|
"""Chain to use to predict action.""" |
|
|
|
|
|
@property |
|
|
def input_keys(self) -> List[str]: |
|
|
return ["input"] |
|
|
|
|
|
@staticmethod |
|
|
def get_default_prompt() -> ChatPromptTemplate: |
|
|
base_prompt = ChatPromptTemplate.from_template(agent_instructions) |
|
|
return base_prompt + AIMessagePromptTemplate.from_template( |
|
|
"{intermediate_steps}" |
|
|
) |
|
|
|
|
|
@staticmethod |
|
|
def get_default_output_parser() -> XMLAgentOutputParser: |
|
|
return XMLAgentOutputParser() |
|
|
|
|
|
def plan( |
|
|
self, |
|
|
intermediate_steps: List[Tuple[AgentAction, str]], |
|
|
callbacks: Callbacks = None, |
|
|
**kwargs: Any, |
|
|
) -> Union[AgentAction, AgentFinish]: |
|
|
log = "" |
|
|
for action, observation in intermediate_steps: |
|
|
log += ( |
|
|
f"<tool>{action.tool}</tool><tool_input>{action.tool_input}" |
|
|
f"</tool_input><observation>{observation}</observation>" |
|
|
) |
|
|
tools = "" |
|
|
for tool in self.tools: |
|
|
tools += f"{tool.name}: {tool.description}\n" |
|
|
inputs = { |
|
|
"intermediate_steps": log, |
|
|
"tools": tools, |
|
|
"question": kwargs["input"], |
|
|
"stop": ["</tool_input>", "</final_answer>"], |
|
|
} |
|
|
response = self.llm_chain(inputs, callbacks=callbacks) |
|
|
return response[self.llm_chain.output_key] |
|
|
|
|
|
async def aplan( |
|
|
self, |
|
|
intermediate_steps: List[Tuple[AgentAction, str]], |
|
|
callbacks: Callbacks = None, |
|
|
**kwargs: Any, |
|
|
) -> Union[AgentAction, AgentFinish]: |
|
|
log = "" |
|
|
for action, observation in intermediate_steps: |
|
|
log += ( |
|
|
f"<tool>{action.tool}</tool><tool_input>{action.tool_input}" |
|
|
f"</tool_input><observation>{observation}</observation>" |
|
|
) |
|
|
tools = "" |
|
|
for tool in self.tools: |
|
|
tools += f"{tool.name}: {tool.description}\n" |
|
|
inputs = { |
|
|
"intermediate_steps": log, |
|
|
"tools": tools, |
|
|
"question": kwargs["input"], |
|
|
"stop": ["</tool_input>", "</final_answer>"], |
|
|
} |
|
|
response = await self.llm_chain.acall(inputs, callbacks=callbacks) |
|
|
return response[self.llm_chain.output_key] |
|
|
|
|
|
|
|
|
def create_xml_agent( |
|
|
llm: BaseLanguageModel, |
|
|
tools: Sequence[BaseTool], |
|
|
prompt: BasePromptTemplate, |
|
|
tools_renderer: ToolsRenderer = render_text_description, |
|
|
*, |
|
|
stop_sequence: Union[bool, List[str]] = True, |
|
|
) -> Runnable: |
|
|
"""Create an agent that uses XML to format its logic. |
|
|
|
|
|
Args: |
|
|
llm: LLM to use as the agent. |
|
|
tools: Tools this agent has access to. |
|
|
prompt: The prompt to use, must have input keys |
|
|
`tools`: contains descriptions for each tool. |
|
|
`agent_scratchpad`: contains previous agent actions and tool outputs. |
|
|
tools_renderer: This controls how the tools are converted into a string and |
|
|
then passed into the LLM. Default is `render_text_description`. |
|
|
stop_sequence: bool or list of str. |
|
|
If True, adds a stop token of "</tool_input>" to avoid hallucinates. |
|
|
If False, does not add a stop token. |
|
|
If a list of str, uses the provided list as the stop tokens. |
|
|
|
|
|
Default is True. You may to set this to False if the LLM you are using |
|
|
does not support stop sequences. |
|
|
|
|
|
Returns: |
|
|
A Runnable sequence representing an agent. It takes as input all the same input |
|
|
variables as the prompt passed in does. It returns as output either an |
|
|
AgentAction or AgentFinish. |
|
|
|
|
|
Example: |
|
|
|
|
|
.. code-block:: python |
|
|
|
|
|
from langchain import hub |
|
|
from langchain_community.chat_models import ChatAnthropic |
|
|
from langchain.agents import AgentExecutor, create_xml_agent |
|
|
|
|
|
prompt = hub.pull("hwchase17/xml-agent-convo") |
|
|
model = ChatAnthropic(model="claude-3-haiku-20240307") |
|
|
tools = ... |
|
|
|
|
|
agent = create_xml_agent(model, tools, prompt) |
|
|
agent_executor = AgentExecutor(agent=agent, tools=tools) |
|
|
|
|
|
agent_executor.invoke({"input": "hi"}) |
|
|
|
|
|
# Use with chat history |
|
|
from langchain_core.messages import AIMessage, HumanMessage |
|
|
agent_executor.invoke( |
|
|
{ |
|
|
"input": "what's my name?", |
|
|
# Notice that chat_history is a string |
|
|
# since this prompt is aimed at LLMs, not chat models |
|
|
"chat_history": "Human: My name is Bob\\nAI: Hello Bob!", |
|
|
} |
|
|
) |
|
|
|
|
|
Prompt: |
|
|
|
|
|
The prompt must have input keys: |
|
|
* `tools`: contains descriptions for each tool. |
|
|
* `agent_scratchpad`: contains previous agent actions and tool outputs as an XML string. |
|
|
|
|
|
Here's an example: |
|
|
|
|
|
.. code-block:: python |
|
|
|
|
|
from langchain_core.prompts import PromptTemplate |
|
|
|
|
|
template = '''You are a helpful assistant. Help the user answer any questions. |
|
|
|
|
|
You have access to the following tools: |
|
|
|
|
|
{tools} |
|
|
|
|
|
In order to use a tool, you can use <tool></tool> and <tool_input></tool_input> tags. You will then get back a response in the form <observation></observation> |
|
|
For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond: |
|
|
|
|
|
<tool>search</tool><tool_input>weather in SF</tool_input> |
|
|
<observation>64 degrees</observation> |
|
|
|
|
|
When you are done, respond with a final answer between <final_answer></final_answer>. For example: |
|
|
|
|
|
<final_answer>The weather in SF is 64 degrees</final_answer> |
|
|
|
|
|
Begin! |
|
|
|
|
|
Previous Conversation: |
|
|
{chat_history} |
|
|
|
|
|
Question: {input} |
|
|
{agent_scratchpad}''' |
|
|
prompt = PromptTemplate.from_template(template) |
|
|
""" |
|
|
missing_vars = {"tools", "agent_scratchpad"}.difference( |
|
|
prompt.input_variables + list(prompt.partial_variables) |
|
|
) |
|
|
if missing_vars: |
|
|
raise ValueError(f"Prompt missing required variables: {missing_vars}") |
|
|
|
|
|
prompt = prompt.partial( |
|
|
tools=tools_renderer(list(tools)), |
|
|
) |
|
|
|
|
|
if stop_sequence: |
|
|
stop = ["</tool_input>"] if stop_sequence is True else stop_sequence |
|
|
llm_with_stop = llm.bind(stop=stop) |
|
|
else: |
|
|
llm_with_stop = llm |
|
|
|
|
|
agent = ( |
|
|
RunnablePassthrough.assign( |
|
|
agent_scratchpad=lambda x: format_xml(x["intermediate_steps"]), |
|
|
) |
|
|
| prompt |
|
|
| llm_with_stop |
|
|
| XMLAgentOutputParser() |
|
|
) |
|
|
return agent |
|
|
|