From a5bc7355483a99b376d056f382a261987248af8b Mon Sep 17 00:00:00 2001 From: Hk-Gosuto Date: Mon, 31 Jul 2023 18:40:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/langchain-tools/duckduckgo.ts | 5 +- app/api/langchain-tools/http_get.ts | 70 +++++++++++++++++++++++++++ app/api/langchain/tool/agent/route.ts | 55 ++++++++++++++------- app/client/api.ts | 3 -- app/client/platforms/openai.ts | 19 +++----- app/client/tools/ddg_search.ts | 15 ------ 6 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 app/api/langchain-tools/http_get.ts delete mode 100644 app/client/tools/ddg_search.ts diff --git a/app/api/langchain-tools/duckduckgo.ts b/app/api/langchain-tools/duckduckgo.ts index 4febe5d29..518f2ea89 100644 --- a/app/api/langchain-tools/duckduckgo.ts +++ b/app/api/langchain-tools/duckduckgo.ts @@ -18,10 +18,7 @@ export class DuckDuckGo extends Tool { const results = searchResults.results .slice(0, this.maxResults) - .map( - ({ title, description, url }) => - `title:${title}\ncontent:${htmlToText(description)}\nurl:${url}`, - ) + .map(({ title, description, url }) => htmlToText(description)) .join("\n\n"); return results; diff --git a/app/api/langchain-tools/http_get.ts b/app/api/langchain-tools/http_get.ts new file mode 100644 index 000000000..b4b5a9383 --- /dev/null +++ b/app/api/langchain-tools/http_get.ts @@ -0,0 +1,70 @@ +import { htmlToText } from "html-to-text"; +import { Tool } from "langchain/tools"; + +export interface Headers { + [key: string]: string; +} + +export interface RequestTool { + headers: Headers; + maxOutputLength?: number; + timeout: number; +} + +export class HttpGetTool extends Tool implements RequestTool { + name = "http_get"; + + maxOutputLength = Infinity; + + timeout = 10000; + + constructor( + public headers: Headers = {}, + { maxOutputLength }: { maxOutputLength?: number } = {}, + { timeout }: { timeout?: number } = {}, + ) { + super(...arguments); + + this.maxOutputLength = maxOutputLength ?? this.maxOutputLength; + this.timeout = timeout ?? this.timeout; + } + + /** @ignore */ + async _call(input: string) { + try { + const res = await this.fetchWithTimeout( + input, + { + headers: this.headers, + }, + this.timeout, + ); + let text = await res.text(); + text = htmlToText(text); + text = text.slice(0, this.maxOutputLength); + console.log(text); + return text; + } catch (error) { + console.error(error); + return (error as Error).toString(); + } + } + + async fetchWithTimeout( + resource: RequestInfo | URL, + options = {}, + timeout: number = 30000, + ) { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + const response = await fetch(resource, { + ...options, + signal: controller.signal, + }); + clearTimeout(id); + return response; + } + + description = `A portal to the internet. Use this when you need to get specific content from a website. + Input should be a url string (i.e. "https://www.google.com"). The output will be the text response of the GET request.`; +} diff --git a/app/api/langchain/tool/agent/route.ts b/app/api/langchain/tool/agent/route.ts index 38eb3a0f6..5c9457a3b 100644 --- a/app/api/langchain/tool/agent/route.ts +++ b/app/api/langchain/tool/agent/route.ts @@ -17,6 +17,7 @@ import { initializeAgentExecutorWithOptions } from "langchain/agents"; import { SerpAPI } from "langchain/tools"; import { Calculator } from "langchain/tools/calculator"; import { DuckDuckGo } from "@/app/api/langchain-tools/duckduckgo"; +import { HttpGetTool } from "@/app/api/langchain-tools/http_get"; const serverConfig = getServerSideConfig(); @@ -36,6 +37,7 @@ interface RequestBody { } class ResponseBody { + isSuccess: boolean = true; message!: string; isToolMessage: boolean = false; toolName?: string; @@ -73,11 +75,17 @@ async function handle(req: NextRequest) { ); } }, - // async handleChainError(err, runId, parentRunId, tags) { - // console.log("writer error"); - // await writer.ready; - // await writer.abort(err); - // }, + async handleChainError(err, runId, parentRunId, tags) { + console.log(err, "writer error"); + var response = new ResponseBody(); + response.isSuccess = false; + response.message = err; + await writer.ready; + await writer.write( + encoder.encode(`data: ${JSON.stringify(response)}\n\n`), + ); + await writer.close(); + }, async handleChainEnd(outputs, runId, parentRunId, tags) { await writer.ready; await writer.close(); @@ -87,9 +95,15 @@ async function handle(req: NextRequest) { // await writer.close(); }, async handleLLMError(e: Error) { - console.log("writer error"); + console.log(e, "writer error"); + var response = new ResponseBody(); + response.isSuccess = false; + response.message = e.message; await writer.ready; - await writer.abort(e); + await writer.write( + encoder.encode(`data: ${JSON.stringify(response)}\n\n`), + ); + await writer.close(); }, handleLLMStart(llm, _prompts: string[]) { // console.log("handleLLMStart: I'm the second handler!!", { llm }); @@ -115,6 +129,14 @@ async function handle(req: NextRequest) { ); } catch (ex) { console.error("[handleAgentAction]", ex); + var response = new ResponseBody(); + response.isSuccess = false; + response.message = (ex as Error).message; + await writer.ready; + await writer.write( + encoder.encode(`data: ${JSON.stringify(response)}\n\n`), + ); + await writer.close(); } }, handleToolStart(tool, input) { @@ -134,8 +156,9 @@ async function handle(req: NextRequest) { const tools = [ searchTool, - new RequestsGetTool(), - new RequestsPostTool(), + new HttpGetTool(), + // new RequestsGetTool(), + // new RequestsPostTool(), new Calculator(), ]; @@ -175,14 +198,12 @@ async function handle(req: NextRequest) { maxIterations: 3, memory: memory, }); - executor - .call( - { - input: reqBody.messages.slice(-1)[0].content, - }, - [handler], - ) - .catch((e: Error) => console.error(e)); + executor.call( + { + input: reqBody.messages.slice(-1)[0].content, + }, + [handler], + ); console.log("returning response"); return new Response(transformStream.readable, { diff --git a/app/client/api.ts b/app/client/api.ts index be4e062c2..fde18a242 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -2,7 +2,6 @@ import { getClientConfig } from "../config/client"; import { ACCESS_CODE_PREFIX } from "../constant"; import { ChatMessage, ModelType, useAccessStore } from "../store"; import { ChatGPTApi } from "./platforms/openai"; -import { DuckDuckGoSearch } from "./tools/ddg_search"; export const ROLES = ["system", "user", "assistant"] as const; export type MessageRole = (typeof ROLES)[number]; @@ -80,11 +79,9 @@ export abstract class ToolApi { export class ClientApi { public llm: LLMApi; - public searchTool: ToolApi; constructor() { this.llm = new ChatGPTApi(); - this.searchTool = new DuckDuckGoSearch(); } config() {} diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 9b5fbbbdd..870e1955a 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -23,13 +23,6 @@ export interface OpenAIListModelResponse { }>; } -interface LangChainAgentResponse { - message: string; - isToolMessage: boolean; - toolName?: string; - toolInput?: object; -} - export class ChatGPTApi implements LLMApi { private disableListModels = true; @@ -165,6 +158,7 @@ export class ChatGPTApi implements LLMApi { } } catch (e) { console.error("[Request] parse error", text, msg); + throw e; } }, onclose() { @@ -296,14 +290,15 @@ export class ChatGPTApi implements LLMApi { if (msg.data === "[DONE]" || finished) { return finish(); } - let response: LangChainAgentResponse = JSON.parse(msg.data); + let response = JSON.parse(msg.data); + if (!response.isSuccess) { + console.error("[Request]", response, msg); + throw Error(response.message); + } try { if (response && !response.isToolMessage) { responseText += response.message; - options.onUpdate?.( - responseText, - JSON.stringify(response.toolInput), - ); + options.onUpdate?.(responseText, response.message); } else { options.onToolUpdate?.(response.toolName!, response.message); } diff --git a/app/client/tools/ddg_search.ts b/app/client/tools/ddg_search.ts deleted file mode 100644 index c091dd124..000000000 --- a/app/client/tools/ddg_search.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ToolApi, getHeaders } from "../api"; - -export class DuckDuckGoSearch implements ToolApi { - name = "duckduckgo_search"; - description = - "A wrapper around DuckDuckGo Search.Useful for when you need to answer questions about current events.Input should be a search query."; - - async call(input: string): Promise { - const res = await fetch(`/api/tools/ddg?query=${input}`, { - method: "GET", - headers: getHeaders(), - }); - return await res.json(); - } -}