fix: 修复问题

This commit is contained in:
Hk-Gosuto 2023-07-31 18:40:46 +08:00
parent e3d3eed74b
commit a5bc735548
6 changed files with 116 additions and 51 deletions

View File

@ -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;

View File

@ -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.`;
}

View File

@ -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, {

View File

@ -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() {}

View File

@ -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);
}

View File

@ -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<string> {
const res = await fetch(`/api/tools/ddg?query=${input}`, {
method: "GET",
headers: getHeaders(),
});
return await res.json();
}
}