diff --git a/libs/langchain/langchain/agents/agent_toolkits/openapi/planner.py b/libs/langchain/langchain/agents/agent_toolkits/openapi/planner.py index 6e2ab4483d..759891d030 100644 --- a/libs/langchain/langchain/agents/agent_toolkits/openapi/planner.py +++ b/libs/langchain/langchain/agents/agent_toolkits/openapi/planner.py @@ -19,10 +19,12 @@ from langchain.agents.agent_toolkits.openapi.planner_prompt import ( PARSING_GET_PROMPT, PARSING_PATCH_PROMPT, PARSING_POST_PROMPT, + PARSING_PUT_PROMPT, REQUESTS_DELETE_TOOL_DESCRIPTION, REQUESTS_GET_TOOL_DESCRIPTION, REQUESTS_PATCH_TOOL_DESCRIPTION, REQUESTS_POST_TOOL_DESCRIPTION, + REQUESTS_PUT_TOOL_DESCRIPTION, ) from langchain.agents.agent_toolkits.openapi.spec import ReducedOpenAPISpec from langchain.agents.mrkl.base import ZeroShotAgent @@ -151,6 +153,35 @@ class RequestsPatchToolWithParsing(BaseRequestsTool, BaseTool): raise NotImplementedError() +class RequestsPutToolWithParsing(BaseRequestsTool, BaseTool): + """Requests PUT tool with LLM-instructed extraction of truncated responses.""" + + name: str = "requests_put" + """Tool name.""" + description = REQUESTS_PUT_TOOL_DESCRIPTION + """Tool description.""" + response_length: Optional[int] = MAX_RESPONSE_LENGTH + """Maximum length of the response to be returned.""" + llm_chain: LLMChain = Field( + default_factory=_get_default_llm_chain_factory(PARSING_PUT_PROMPT) + ) + """LLMChain used to extract the response.""" + + def _run(self, text: str) -> str: + try: + data = json.loads(text) + except json.JSONDecodeError as e: + raise e + response = self.requests_wrapper.put(data["url"], data["data"]) + response = response[: self.response_length] + return self.llm_chain.predict( + response=response, instructions=data["output_instructions"] + ).strip() + + async def _arun(self, text: str) -> str: + raise NotImplementedError() + + class RequestsDeleteToolWithParsing(BaseRequestsTool, BaseTool): """A tool that sends a DELETE request and parses the response.""" diff --git a/libs/langchain/langchain/agents/agent_toolkits/openapi/planner_prompt.py b/libs/langchain/langchain/agents/agent_toolkits/openapi/planner_prompt.py index 9d1688d853..c7263e68be 100644 --- a/libs/langchain/langchain/agents/agent_toolkits/openapi/planner_prompt.py +++ b/libs/langchain/langchain/agents/agent_toolkits/openapi/planner_prompt.py @@ -24,6 +24,7 @@ GET /user to get information about the current user GET /products/search search across products POST /users/{{id}}/cart to add products to a user's cart PATCH /users/{{id}}/cart to update a user's cart +PUT /users/{{id}}/coupon to apply idempotent coupon to a user's cart DELETE /users/{{id}}/cart to delete a user's cart User query: tell me a joke @@ -39,6 +40,10 @@ Plan: 1. GET /products with a query param to search for lamps 2. GET /user to find the user's id 3. PATCH /users/{{id}}/cart to add a lamp to the user's cart +User query: I want to add a coupon to my cart +Plan: 1. GET /user to find the user's id +2. PUT /users/{{id}}/coupon to apply the coupon + User query: I want to delete my cart Plan: 1. GET /user to find the user's id 2. DELETE required. Did user specify DELETE or previously authorize? Yes, proceed. @@ -195,6 +200,23 @@ Output:""", input_variables=["response", "instructions"], ) +REQUESTS_PUT_TOOL_DESCRIPTION = """Use this when you want to PUT to a website. +Input to the tool should be a json string with 3 keys: "url", "data", and "output_instructions". +The value of "url" should be a string. +The value of "data" should be a dictionary of key-value pairs you want to PUT to the url. +The value of "output_instructions" should be instructions on what information to extract from the response, for example the id(s) for a resource(s) that the PUT request creates. +Always use double quotes for strings in the json string.""" + +PARSING_PUT_PROMPT = PromptTemplate( + template="""Here is an API response:\n\n{response}\n\n==== +Your task is to extract some information according to these instructions: {instructions} +When working with API objects, you should usually use ids over names. Do not return any ids or names that are not in the response. +If the response indicates an error, you should instead output a summary of the error. + +Output:""", + input_variables=["response", "instructions"], +) + REQUESTS_DELETE_TOOL_DESCRIPTION = """ONLY USE THIS TOOL WHEN THE USER HAS SPECIFICALLY REQUESTED TO DELETE CONTENT FROM A WEBSITE. Input to the tool should be a json string with 2 keys: "url", and "output_instructions". The value of "url" should be a string. diff --git a/libs/langchain/langchain/agents/agent_toolkits/openapi/spec.py b/libs/langchain/langchain/agents/agent_toolkits/openapi/spec.py index 35b104c4a3..8f5699390c 100644 --- a/libs/langchain/langchain/agents/agent_toolkits/openapi/spec.py +++ b/libs/langchain/langchain/agents/agent_toolkits/openapi/spec.py @@ -31,12 +31,12 @@ def reduce_openapi_spec(spec: dict, dereference: bool = True) -> ReducedOpenAPIS I was hoping https://openapi.tools/ would have some useful bits to this end, but doesn't seem so. """ - # 1. Consider only get, post, patch, delete endpoints. + # 1. Consider only get, post, patch, put, delete endpoints. endpoints = [ (f"{operation_name.upper()} {route}", docs.get("description"), docs) for route, operation in spec["paths"].items() for operation_name, docs in operation.items() - if operation_name in ["get", "post", "patch", "delete"] + if operation_name in ["get", "post", "patch", "put", "delete"] ] # 2. Replace any refs so that complete docs are retrieved.