diff --git a/chapter12.md b/chapter12.md index 8b13789..bc72312 100644 --- a/chapter12.md +++ b/chapter12.md @@ -1 +1,86 @@ +## Chapter 12: Creating Custom Tools +LangChain allows you to create your own custom tools and incorporate them into your agents. +``` +from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent +from langchain.prompts import StringPromptTemplate +from langchain import OpenAI, SerpAPIWrapper, LLMChain +from typing import List, Union +from langchain.schema import AgentAction, AgentFinish + + +class CustomPromptTemplate(StringPromptTemplate): + template: str + tools: List[Tool] + + def format(self, **kwargs) -> str: + intermediate_steps = kwargs.pop("intermediate_steps") + thoughts = "" + for action, observation in intermediate_steps: + thoughts += action.log + thoughts += f"\nObservation: {observation}\nThought: " + kwargs["agent_scratchpad"] = thoughts + kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools]) + kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools]) + return self.template.format(**kwargs) + + +def random_word(word: str) -> str: + return f"A random word related to {word} is: example" + + +search = SerpAPIWrapper() +tools = [ + Tool( + name = "Search", + func=search.run, + description="useful for when you need to answer questions about current events" + ), + Tool( + name = "RandomWord", + func=random_word, + description="useful for when you need a random word related to a given word" + ) +] + + +prompt = CustomPromptTemplate( + template="Answer the following questions as best you can. You have access to the following tools:\n\n{tools}\n\nUse the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\nBegin!\n\nQuestion: {input}\nThought:{agent_scratchpad}", + tools=tools, +) + + +class CustomOutputParser: + def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]: + if "Final Answer:" in llm_output: + return AgentFinish( + return_values={"output": llm_output.split("Final Answer:")[-1].strip()}, + log=llm_output, + ) + regex = r"Action: (.*?)\nAction Input: (.*)" + match = re.search(regex, llm_output, re.DOTALL) + if not match: + raise ValueError(f"Could not parse LLM output: `{llm_output}`") + action = match.group(1).strip() + action_input = match.group(2).strip() + return AgentAction(tool=action, tool_input=action_input, log=llm_output) + +output_parser = CustomOutputParser() + +# LLM +llm = OpenAI(temperature=0) +llm_chain = LLMChain(llm=llm, prompt=prompt) + + +tool_names = [tool.name for tool in tools] +agent = LLMSingleActionAgent( + llm_chain=llm_chain, + output_parser=output_parser, + stop=["\nObservation:"], + allowed_tools=tool_names +) + + +agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True) +agent_executor.run("What's a random word related to 'programming'?") +```