| import re |
| from typing import Any, List, Optional, Sequence, Tuple, Union |
|
|
| from langchain_core._api import deprecated |
| from langchain_core.agents import AgentAction |
| from langchain_core.callbacks import BaseCallbackManager |
| from langchain_core.language_models import BaseLanguageModel |
| from langchain_core.prompts import BasePromptTemplate |
| from langchain_core.prompts.chat import ( |
| ChatPromptTemplate, |
| HumanMessagePromptTemplate, |
| SystemMessagePromptTemplate, |
| ) |
| from langchain_core.runnables import Runnable, RunnablePassthrough |
| from langchain_core.tools import BaseTool |
| from langchain_core.tools.render import ToolsRenderer |
| from pydantic import Field |
|
|
| from langchain.agents.agent import Agent, AgentOutputParser |
| from langchain.agents.format_scratchpad import format_log_to_str |
| from langchain.agents.output_parsers import JSONAgentOutputParser |
| from langchain.agents.structured_chat.output_parser import ( |
| StructuredChatOutputParserWithRetries, |
| ) |
| from langchain.agents.structured_chat.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX |
| from langchain.chains.llm import LLMChain |
| from langchain.tools.render import render_text_description_and_args |
|
|
| HUMAN_MESSAGE_TEMPLATE = "{input}\n\n{agent_scratchpad}" |
|
|
|
|
| @deprecated("0.1.0", alternative="create_structured_chat_agent", removal="1.0") |
| class StructuredChatAgent(Agent): |
| """Structured Chat Agent.""" |
|
|
| output_parser: AgentOutputParser = Field( |
| default_factory=StructuredChatOutputParserWithRetries |
| ) |
| """Output parser for the agent.""" |
|
|
| @property |
| def observation_prefix(self) -> str: |
| """Prefix to append the observation with.""" |
| return "Observation: " |
|
|
| @property |
| def llm_prefix(self) -> str: |
| """Prefix to append the llm call with.""" |
| return "Thought:" |
|
|
| def _construct_scratchpad( |
| self, intermediate_steps: List[Tuple[AgentAction, str]] |
| ) -> str: |
| agent_scratchpad = super()._construct_scratchpad(intermediate_steps) |
| if not isinstance(agent_scratchpad, str): |
| raise ValueError("agent_scratchpad should be of type string.") |
| if agent_scratchpad: |
| return ( |
| f"This was your previous work " |
| f"(but I haven't seen any of it! I only see what " |
| f"you return as final answer):\n{agent_scratchpad}" |
| ) |
| else: |
| return agent_scratchpad |
|
|
| @classmethod |
| def _validate_tools(cls, tools: Sequence[BaseTool]) -> None: |
| pass |
|
|
| @classmethod |
| def _get_default_output_parser( |
| cls, llm: Optional[BaseLanguageModel] = None, **kwargs: Any |
| ) -> AgentOutputParser: |
| return StructuredChatOutputParserWithRetries.from_llm(llm=llm) |
|
|
| @property |
| def _stop(self) -> List[str]: |
| return ["Observation:"] |
|
|
| @classmethod |
| def create_prompt( |
| cls, |
| tools: Sequence[BaseTool], |
| prefix: str = PREFIX, |
| suffix: str = SUFFIX, |
| human_message_template: str = HUMAN_MESSAGE_TEMPLATE, |
| format_instructions: str = FORMAT_INSTRUCTIONS, |
| input_variables: Optional[List[str]] = None, |
| memory_prompts: Optional[List[BasePromptTemplate]] = None, |
| ) -> BasePromptTemplate: |
| tool_strings = [] |
| for tool in tools: |
| args_schema = re.sub("}", "}}", re.sub("{", "{{", str(tool.args))) |
| tool_strings.append(f"{tool.name}: {tool.description}, args: {args_schema}") |
| formatted_tools = "\n".join(tool_strings) |
| tool_names = ", ".join([tool.name for tool in tools]) |
| format_instructions = format_instructions.format(tool_names=tool_names) |
| template = "\n\n".join([prefix, formatted_tools, format_instructions, suffix]) |
| if input_variables is None: |
| input_variables = ["input", "agent_scratchpad"] |
| _memory_prompts = memory_prompts or [] |
| messages = [ |
| SystemMessagePromptTemplate.from_template(template), |
| *_memory_prompts, |
| HumanMessagePromptTemplate.from_template(human_message_template), |
| ] |
| return ChatPromptTemplate(input_variables=input_variables, messages=messages) |
|
|
| @classmethod |
| def from_llm_and_tools( |
| cls, |
| llm: BaseLanguageModel, |
| tools: Sequence[BaseTool], |
| callback_manager: Optional[BaseCallbackManager] = None, |
| output_parser: Optional[AgentOutputParser] = None, |
| prefix: str = PREFIX, |
| suffix: str = SUFFIX, |
| human_message_template: str = HUMAN_MESSAGE_TEMPLATE, |
| format_instructions: str = FORMAT_INSTRUCTIONS, |
| input_variables: Optional[List[str]] = None, |
| memory_prompts: Optional[List[BasePromptTemplate]] = None, |
| **kwargs: Any, |
| ) -> Agent: |
| """Construct an agent from an LLM and tools.""" |
| cls._validate_tools(tools) |
| prompt = cls.create_prompt( |
| tools, |
| prefix=prefix, |
| suffix=suffix, |
| human_message_template=human_message_template, |
| format_instructions=format_instructions, |
| input_variables=input_variables, |
| memory_prompts=memory_prompts, |
| ) |
| llm_chain = LLMChain( |
| llm=llm, |
| prompt=prompt, |
| callback_manager=callback_manager, |
| ) |
| tool_names = [tool.name for tool in tools] |
| _output_parser = output_parser or cls._get_default_output_parser(llm=llm) |
| return cls( |
| llm_chain=llm_chain, |
| allowed_tools=tool_names, |
| output_parser=_output_parser, |
| **kwargs, |
| ) |
|
|
| @property |
| def _agent_type(self) -> str: |
| raise ValueError |
|
|
|
|
| def create_structured_chat_agent( |
| llm: BaseLanguageModel, |
| tools: Sequence[BaseTool], |
| prompt: ChatPromptTemplate, |
| tools_renderer: ToolsRenderer = render_text_description_and_args, |
| *, |
| stop_sequence: Union[bool, List[str]] = True, |
| ) -> Runnable: |
| """Create an agent aimed at supporting tools with multiple inputs. |
| |
| Args: |
| llm: LLM to use as the agent. |
| tools: Tools this agent has access to. |
| prompt: The prompt to use. See Prompt section below for more. |
| stop_sequence: bool or list of str. |
| If True, adds a stop token of "Observation:" 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. |
| tools_renderer: This controls how the tools are converted into a string and |
| then passed into the LLM. Default is `render_text_description`. |
| |
| 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. |
| |
| Examples: |
| |
| .. code-block:: python |
| |
| from langchain import hub |
| from langchain_community.chat_models import ChatOpenAI |
| from langchain.agents import AgentExecutor, create_structured_chat_agent |
| |
| prompt = hub.pull("hwchase17/structured-chat-agent") |
| model = ChatOpenAI() |
| tools = ... |
| |
| agent = create_structured_chat_agent(model, tools, prompt) |
| agent_executor = AgentExecutor(agent=agent, tools=tools) |
| |
| agent_executor.invoke({"input": "hi"}) |
| |
| # Using with chat history |
| from langchain_core.messages import AIMessage, HumanMessage |
| agent_executor.invoke( |
| { |
| "input": "what's my name?", |
| "chat_history": [ |
| HumanMessage(content="hi! my name is bob"), |
| AIMessage(content="Hello Bob! How can I assist you today?"), |
| ], |
| } |
| ) |
| |
| Prompt: |
| |
| The prompt must have input keys: |
| * `tools`: contains descriptions and arguments for each tool. |
| * `tool_names`: contains all tool names. |
| * `agent_scratchpad`: contains previous agent actions and tool outputs as a string. |
| |
| Here's an example: |
| |
| .. code-block:: python |
| |
| from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder |
| |
| system = '''Respond to the human as helpfully and accurately as possible. You have access to the following tools: |
| |
| {tools} |
| |
| Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input). |
| |
| Valid "action" values: "Final Answer" or {tool_names} |
| |
| Provide only ONE action per $JSON_BLOB, as shown: |
| |
| ``` |
| {{ |
| "action": $TOOL_NAME, |
| "action_input": $INPUT |
| }} |
| ``` |
| |
| Follow this format: |
| |
| Question: input question to answer |
| Thought: consider previous and subsequent steps |
| Action: |
| ``` |
| $JSON_BLOB |
| ``` |
| Observation: action result |
| ... (repeat Thought/Action/Observation N times) |
| Thought: I know what to respond |
| Action: |
| ``` |
| {{ |
| "action": "Final Answer", |
| "action_input": "Final response to human" |
| }} |
| |
| Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation''' |
| |
| human = '''{input} |
| |
| {agent_scratchpad} |
| |
| (reminder to respond in a JSON blob no matter what)''' |
| |
| prompt = ChatPromptTemplate.from_messages( |
| [ |
| ("system", system), |
| MessagesPlaceholder("chat_history", optional=True), |
| ("human", human), |
| ] |
| ) |
| """ |
| missing_vars = {"tools", "tool_names", "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)), |
| tool_names=", ".join([t.name for t in tools]), |
| ) |
| if stop_sequence: |
| stop = ["\nObservation"] 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_log_to_str(x["intermediate_steps"]), |
| ) |
| | prompt |
| | llm_with_stop |
| | JSONAgentOutputParser() |
| ) |
| return agent |
|
|