mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-13 12:43:42 +08:00
feat: 使用langchain实现插件功能
This commit is contained in:
193
app/api/langchain/tool/agent/route.ts
Normal file
193
app/api/langchain/tool/agent/route.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSideConfig } from "@/app/config/server";
|
||||
import { auth } from "../../../auth";
|
||||
|
||||
import { ChatOpenAI } from "langchain/chat_models/openai";
|
||||
import { BaseCallbackHandler } from "langchain/callbacks";
|
||||
|
||||
import {
|
||||
DynamicTool,
|
||||
RequestsGetTool,
|
||||
RequestsPostTool,
|
||||
} from "langchain/tools";
|
||||
import { SerpAPI } from "langchain/tools";
|
||||
import { Calculator } from "langchain/tools/calculator";
|
||||
import { AIMessage, HumanMessage, SystemMessage } from "langchain/schema";
|
||||
import { BufferMemory, ChatMessageHistory } from "langchain/memory";
|
||||
import { initializeAgentExecutorWithOptions } from "langchain/agents";
|
||||
|
||||
const serverConfig = getServerSideConfig();
|
||||
|
||||
interface RequestMessage {
|
||||
role: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface RequestBody {
|
||||
messages: RequestMessage[];
|
||||
model: string;
|
||||
stream?: boolean;
|
||||
temperature: number;
|
||||
presence_penalty?: number;
|
||||
frequency_penalty?: number;
|
||||
top_p?: number;
|
||||
}
|
||||
|
||||
class ResponseBody {
|
||||
message!: string;
|
||||
isToolMessage: boolean = false;
|
||||
toolName?: string;
|
||||
}
|
||||
|
||||
interface ToolInput {
|
||||
input: string;
|
||||
}
|
||||
|
||||
async function handle(req: NextRequest) {
|
||||
if (req.method === "OPTIONS") {
|
||||
return NextResponse.json({ body: "OK" }, { status: 200 });
|
||||
}
|
||||
try {
|
||||
const encoder = new TextEncoder();
|
||||
const transformStream = new TransformStream();
|
||||
const writer = transformStream.writable.getWriter();
|
||||
const reqBody: RequestBody = await req.json();
|
||||
|
||||
const handler = BaseCallbackHandler.fromMethods({
|
||||
async handleLLMNewToken(token: string) {
|
||||
if (token) {
|
||||
// console.log("token", token);
|
||||
var response = new ResponseBody();
|
||||
response.message = token;
|
||||
await writer.ready;
|
||||
await writer.write(
|
||||
encoder.encode(`data: ${JSON.stringify(response)}\n\n`),
|
||||
);
|
||||
}
|
||||
},
|
||||
// async handleChainError(err, runId, parentRunId, tags) {
|
||||
// console.log("writer error");
|
||||
// await writer.ready;
|
||||
// await writer.abort(err);
|
||||
// },
|
||||
async handleChainEnd(outputs, runId, parentRunId, tags) {
|
||||
// console.log("writer close");
|
||||
await writer.ready;
|
||||
await writer.close();
|
||||
},
|
||||
async handleLLMEnd() {
|
||||
// console.log("writer close");
|
||||
// await writer.ready;
|
||||
// await writer.close();
|
||||
},
|
||||
async handleLLMError(e: Error) {
|
||||
console.log("writer error");
|
||||
await writer.ready;
|
||||
await writer.abort(e);
|
||||
},
|
||||
handleLLMStart(llm, _prompts: string[]) {
|
||||
// console.log("handleLLMStart: I'm the second handler!!", { llm });
|
||||
},
|
||||
handleChainStart(chain) {
|
||||
// console.log("handleChainStart: I'm the second handler!!", { chain });
|
||||
},
|
||||
async handleAgentAction(action) {
|
||||
console.log(
|
||||
"agent (llm)",
|
||||
`tool: ${action.tool} toolInput: ${action.toolInput}`,
|
||||
{ action },
|
||||
);
|
||||
var response = new ResponseBody();
|
||||
response.isToolMessage = true;
|
||||
let toolInput = <ToolInput>(<unknown>action.toolInput);
|
||||
response.message = toolInput.input;
|
||||
response.toolName = action.tool;
|
||||
await writer.ready;
|
||||
await writer.write(
|
||||
encoder.encode(`data: ${JSON.stringify(response)}\n\n`),
|
||||
);
|
||||
},
|
||||
handleToolStart(tool, input) {
|
||||
console.log("handleToolStart", { tool, input });
|
||||
},
|
||||
});
|
||||
|
||||
const tools = [
|
||||
new RequestsGetTool(),
|
||||
new RequestsPostTool(),
|
||||
new SerpAPI(process.env.SERPAPI_API_KEY, {
|
||||
location: "Austin,Texas,United States",
|
||||
hl: "en",
|
||||
gl: "us",
|
||||
}),
|
||||
// new DynamicTool({
|
||||
// name: ddg.name,
|
||||
// description: ddg.description,
|
||||
// func: async (input: string) => ddg.call(input),
|
||||
// }),
|
||||
new Calculator(),
|
||||
];
|
||||
|
||||
const pastMessages = new Array();
|
||||
|
||||
reqBody.messages
|
||||
.slice(0, reqBody.messages.length - 1)
|
||||
.forEach((message) => {
|
||||
if (message.role === "system")
|
||||
pastMessages.push(new SystemMessage(message.content));
|
||||
if (message.role === "user")
|
||||
pastMessages.push(new HumanMessage(message.content));
|
||||
if (message.role === "assistant")
|
||||
pastMessages.push(new AIMessage(message.content));
|
||||
});
|
||||
|
||||
// console.log("mesage", { pastMessages })
|
||||
|
||||
const memory = new BufferMemory({
|
||||
memoryKey: "chat_history",
|
||||
returnMessages: true,
|
||||
inputKey: "input",
|
||||
outputKey: "output",
|
||||
chatHistory: new ChatMessageHistory(pastMessages),
|
||||
});
|
||||
const llm = new ChatOpenAI({
|
||||
modelName: reqBody.model,
|
||||
openAIApiKey: serverConfig.apiKey,
|
||||
temperature: reqBody.temperature,
|
||||
streaming: reqBody.stream,
|
||||
topP: reqBody.top_p,
|
||||
presencePenalty: reqBody.presence_penalty,
|
||||
frequencyPenalty: reqBody.frequency_penalty,
|
||||
});
|
||||
|
||||
const executor = await initializeAgentExecutorWithOptions(tools, llm, {
|
||||
agentType: "openai-functions",
|
||||
returnIntermediateSteps: true,
|
||||
maxIterations: 3,
|
||||
memory: memory,
|
||||
});
|
||||
executor
|
||||
.call(
|
||||
{
|
||||
input: reqBody.messages.slice(-1)[0].content,
|
||||
},
|
||||
[handler],
|
||||
)
|
||||
.catch((e: Error) => console.error(e));
|
||||
|
||||
console.log("returning response");
|
||||
return new Response(transformStream.readable, {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
} catch (e) {
|
||||
return new Response(JSON.stringify({ error: (e as any).message }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const GET = handle;
|
||||
export const POST = handle;
|
||||
|
||||
export const runtime = "edge";
|
||||
@@ -13,7 +13,6 @@ export type ChatModel = ModelType;
|
||||
export interface RequestMessage {
|
||||
role: MessageRole;
|
||||
content: string;
|
||||
toolPrompt?: string;
|
||||
}
|
||||
|
||||
export interface LLMConfig {
|
||||
@@ -28,7 +27,7 @@ export interface LLMConfig {
|
||||
export interface ChatOptions {
|
||||
messages: RequestMessage[];
|
||||
config: LLMConfig;
|
||||
|
||||
onToolUpdate?: (toolName: string, toolInput: string) => void;
|
||||
onUpdate?: (message: string, chunk: string) => void;
|
||||
onFinish: (message: string) => void;
|
||||
onError?: (err: Error) => void;
|
||||
@@ -47,6 +46,7 @@ export interface LLMModel {
|
||||
|
||||
export abstract class LLMApi {
|
||||
abstract chat(options: ChatOptions): Promise<void>;
|
||||
abstract toolAgentChat(options: ChatOptions): Promise<void>;
|
||||
abstract usage(): Promise<LLMUsage>;
|
||||
abstract models(): Promise<LLMModel[]>;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,13 @@ export interface OpenAIListModelResponse {
|
||||
}>;
|
||||
}
|
||||
|
||||
interface LangChainAgentResponse {
|
||||
message: string;
|
||||
isToolMessage: boolean;
|
||||
toolName?: string;
|
||||
toolInput?: object;
|
||||
}
|
||||
|
||||
export class ChatGPTApi implements LLMApi {
|
||||
private disableListModels = true;
|
||||
|
||||
@@ -47,7 +54,7 @@ export class ChatGPTApi implements LLMApi {
|
||||
async chat(options: ChatOptions) {
|
||||
const messages = options.messages.map((v) => ({
|
||||
role: v.role,
|
||||
content: v.toolPrompt ?? v.content,
|
||||
content: v.content,
|
||||
}));
|
||||
|
||||
const modelConfig = {
|
||||
@@ -182,6 +189,151 @@ export class ChatGPTApi implements LLMApi {
|
||||
options.onError?.(e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async toolAgentChat(options: ChatOptions) {
|
||||
const messages = options.messages.map((v) => ({
|
||||
role: v.role,
|
||||
content: v.content,
|
||||
}));
|
||||
|
||||
const modelConfig = {
|
||||
...useAppConfig.getState().modelConfig,
|
||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
||||
...{
|
||||
model: options.config.model,
|
||||
},
|
||||
};
|
||||
|
||||
const requestPayload = {
|
||||
messages,
|
||||
stream: options.config.stream,
|
||||
model: modelConfig.model,
|
||||
temperature: modelConfig.temperature,
|
||||
presence_penalty: modelConfig.presence_penalty,
|
||||
frequency_penalty: modelConfig.frequency_penalty,
|
||||
top_p: modelConfig.top_p,
|
||||
};
|
||||
|
||||
console.log("[Request] openai payload: ", requestPayload);
|
||||
|
||||
const shouldStream = true;
|
||||
const controller = new AbortController();
|
||||
options.onController?.(controller);
|
||||
|
||||
try {
|
||||
const path = "/api/langchain/tool/agent";
|
||||
const chatPayload = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(requestPayload),
|
||||
signal: controller.signal,
|
||||
headers: getHeaders(),
|
||||
};
|
||||
|
||||
// make a fetch request
|
||||
const requestTimeoutId = setTimeout(
|
||||
() => controller.abort(),
|
||||
REQUEST_TIMEOUT_MS,
|
||||
);
|
||||
console.log("shouldStream", shouldStream);
|
||||
|
||||
if (shouldStream) {
|
||||
let responseText = "";
|
||||
let finished = false;
|
||||
|
||||
const finish = () => {
|
||||
if (!finished) {
|
||||
options.onFinish(responseText);
|
||||
finished = true;
|
||||
}
|
||||
};
|
||||
|
||||
controller.signal.onabort = finish;
|
||||
|
||||
fetchEventSource(path, {
|
||||
...chatPayload,
|
||||
async onopen(res) {
|
||||
clearTimeout(requestTimeoutId);
|
||||
const contentType = res.headers.get("content-type");
|
||||
console.log(
|
||||
"[OpenAI] request response content type: ",
|
||||
contentType,
|
||||
);
|
||||
|
||||
if (contentType?.startsWith("text/plain")) {
|
||||
responseText = await res.clone().text();
|
||||
return finish();
|
||||
}
|
||||
|
||||
if (
|
||||
!res.ok ||
|
||||
!res.headers
|
||||
.get("content-type")
|
||||
?.startsWith(EventStreamContentType) ||
|
||||
res.status !== 200
|
||||
) {
|
||||
const responseTexts = [responseText];
|
||||
let extraInfo = await res.clone().text();
|
||||
console.warn(`extraInfo: ${extraInfo}`);
|
||||
// try {
|
||||
// const resJson = await res.clone().json();
|
||||
// extraInfo = prettyObject(resJson);
|
||||
// } catch { }
|
||||
|
||||
if (res.status === 401) {
|
||||
responseTexts.push(Locale.Error.Unauthorized);
|
||||
}
|
||||
|
||||
if (extraInfo) {
|
||||
responseTexts.push(extraInfo);
|
||||
}
|
||||
|
||||
responseText = responseTexts.join("\n\n");
|
||||
|
||||
return finish();
|
||||
}
|
||||
},
|
||||
onmessage(msg) {
|
||||
if (msg.data === "[DONE]" || finished) {
|
||||
return finish();
|
||||
}
|
||||
let response: LangChainAgentResponse = JSON.parse(msg.data);
|
||||
try {
|
||||
if (response && !response.isToolMessage) {
|
||||
responseText += response.message;
|
||||
options.onUpdate?.(
|
||||
responseText,
|
||||
JSON.stringify(response.toolInput),
|
||||
);
|
||||
} else {
|
||||
options.onToolUpdate?.(response.toolName!, response.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Request] parse error", response, msg);
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
finish();
|
||||
},
|
||||
onerror(e) {
|
||||
options.onError?.(e);
|
||||
throw e;
|
||||
},
|
||||
openWhenHidden: true,
|
||||
});
|
||||
} else {
|
||||
const res = await fetch(path, chatPayload);
|
||||
clearTimeout(requestTimeoutId);
|
||||
|
||||
const resJson = await res.json();
|
||||
const message = this.extractMessage(resJson);
|
||||
options.onFinish(message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("[Request] failed to make a chat reqeust", e);
|
||||
options.onError?.(e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async usage() {
|
||||
const formatDate = (d: Date) =>
|
||||
`${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d
|
||||
|
||||
@@ -34,8 +34,6 @@ import AutoIcon from "../icons/auto.svg";
|
||||
import BottomIcon from "../icons/bottom.svg";
|
||||
import StopIcon from "../icons/pause.svg";
|
||||
import RobotIcon from "../icons/robot.svg";
|
||||
import SearchCloseIcon from "../icons/search_close.svg";
|
||||
import SearchOpenIcon from "../icons/search_open.svg";
|
||||
import CheckmarkIcon from "../icons/checkmark.svg";
|
||||
|
||||
import {
|
||||
@@ -404,11 +402,11 @@ export function ChatActions(props: {
|
||||
const navigate = useNavigate();
|
||||
const chatStore = useChatStore();
|
||||
|
||||
// switch web search
|
||||
const webSearch = chatStore.currentSession().webSearch;
|
||||
function switchWebSearch() {
|
||||
// switch tools
|
||||
const useTools = chatStore.currentSession().useTools;
|
||||
function switchUseTools() {
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.webSearch = !session.webSearch;
|
||||
session.useTools = !session.useTools;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -513,15 +511,15 @@ export function ChatActions(props: {
|
||||
icon={<RobotIcon />}
|
||||
/>
|
||||
|
||||
<ChatAction
|
||||
onClick={switchWebSearch}
|
||||
{/* <ChatAction
|
||||
onClick={switchUseTools}
|
||||
text={
|
||||
webSearch
|
||||
? Locale.Chat.InputActions.CloseWebSearch
|
||||
: Locale.Chat.InputActions.OpenWebSearch
|
||||
useTools
|
||||
? Locale.Chat.InputActions.CloseTools
|
||||
: Locale.Chat.InputActions.OpenTools
|
||||
}
|
||||
icon={webSearch ? <SearchOpenIcon /> : <SearchCloseIcon />}
|
||||
/>
|
||||
icon={useTools ? <SearchOpenIcon /> : <SearchCloseIcon />}
|
||||
/> */}
|
||||
|
||||
{showModelSelector && (
|
||||
<Selector
|
||||
@@ -1149,20 +1147,27 @@ export function Chat() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={styles["chat-message-tools-status"]}>
|
||||
<div className={styles["chat-message-tools-name"]}>
|
||||
<CheckmarkIcon
|
||||
className={styles["chat-message-checkmark"]}
|
||||
/>
|
||||
web search:
|
||||
<code className={styles["chat-message-tools-details"]}>
|
||||
xxxxxxxxxxxxxxxx
|
||||
</code>
|
||||
{session.useTools &&
|
||||
!isUser &&
|
||||
message.toolMessages &&
|
||||
message.toolMessages.map((tool, index) => (
|
||||
<div
|
||||
className={styles["chat-message-tools-status"]}
|
||||
key={index}
|
||||
>
|
||||
<div className={styles["chat-message-tools-name"]}>
|
||||
<CheckmarkIcon
|
||||
className={styles["chat-message-checkmark"]}
|
||||
/>
|
||||
{tool.toolName}:
|
||||
<code
|
||||
className={styles["chat-message-tools-details"]}
|
||||
>
|
||||
{tool.toolInput}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{showTyping && (
|
||||
<div className={styles["chat-message-status"]}>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683102619308" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7254" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M533.333333 42.666667C262.346667 42.666667 42.666667 262.346667 42.666667 533.333333s219.68 490.666667 490.666666 490.666667 490.666667-219.68 490.666667-490.666667S804.32 42.666667 533.333333 42.666667z m342.086667 760.253333c-29.266667-16-61.566667-29.633333-96.166667-40.853333 18.713333-63.493333 29.413333-134 31.153334-207.4h170.42a445.853333 445.853333 0 0 1-94.413334 254.446666q-5.4-3.113333-10.993333-6.193333z m-695.166667 6.193333A445.853333 445.853333 0 0 1 85.84 554.666667h170.42c1.74 73.373333 12.44 143.906667 31.153333 207.4-34.6 11.22-66.9 24.886667-96.166666 40.853333q-5.593333 3.08-10.993334 6.193333z m10.993334-545.366666c29.266667 15.966667 61.566667 29.633333 96.166666 40.853333-18.713333 63.493333-29.413333 134-31.153333 207.4H85.84a445.866667 445.866667 0 0 1 94.413333-254.446667q5.4 3.113333 10.993334 6.193334zM554.666667 341.073333c64.34-1.526667 126.5-9.94 183.64-24.606666 17.6 59.6 27.706667 126.106667 29.426666 195.533333H554.666667z m0-42.666666V87.226667c52.386667 9.293333 101.793333 52.666667 140.96 124.453333a502.986667 502.986667 0 0 1 29.153333 64.24c-52.853333 13.313333-110.4 21-170.113333 22.48z m-42.666667-211.18V298.4c-59.713333-1.48-117.26-9.166667-170.113333-22.48a502.986667 502.986667 0 0 1 29.153333-64.24C410.206667 139.886667 459.613333 96.52 512 87.226667z m0 253.846666V512H298.933333c1.72-69.426667 11.826667-135.933333 29.426667-195.533333 57.14 14.666667 119.3 23.08 183.64 24.606666zM298.933333 554.666667H512v170.926666c-64.34 1.526667-126.5 9.94-183.64 24.606667-17.6-59.6-27.693333-126.106667-29.426667-195.533333zM512 768.266667v211.173333c-52.386667-9.293333-101.793333-52.666667-140.96-124.453333a502.986667 502.986667 0 0 1-29.153333-64.24c52.853333-13.313333 110.4-21 170.113333-22.48z m42.666667 211.173333V768.266667c59.713333 1.48 117.26 9.166667 170.113333 22.48a502.986667 502.986667 0 0 1-29.153333 64.24c-39.166667 71.793333-88.573333 115.16-140.96 124.453333z m0-253.846667V554.666667h213.066666c-1.72 69.426667-11.826667 135.933333-29.426666 195.533333-57.14-14.666667-119.3-23.08-183.64-24.606667zM810.406667 512c-1.74-73.373333-12.406667-143.906667-31.153334-207.4 34.6-11.22 66.9-24.886667 96.166667-40.853333q5.586667-3.053333 10.993333-6.193334A445.866667 445.866667 0 0 1 980.826667 512zM858 224.626667c-1 0.553333-2 1.113333-3 1.666666-27.053333 14.753333-56.98 27.413333-89.1 37.826667a546.88 546.88 0 0 0-32.806667-72.873333c-18.113333-33.206667-38.526667-61.333333-60.926666-84A448.106667 448.106667 0 0 1 858 224.626667z m-463.473333-117.333334c-22.4 22.666667-42.813333 50.78-60.926667 84a546 546 0 0 0-32.806667 72.873334c-32.126667-10.46-62.06-23.12-89.113333-37.873334-1.013333-0.553333-2-1.113333-3-1.666666a448.106667 448.106667 0 0 1 185.833333-117.366667zM208.666667 842.04c1-0.553333 2-1.113333 3-1.666667 27.053333-14.753333 56.98-27.413333 89.1-37.826666a546 546 0 0 0 32.806666 72.873333c18.113333 33.206667 38.526667 61.333333 60.926667 84A448.106667 448.106667 0 0 1 208.666667 842.04z m463.473333 117.333333c22.4-22.666667 42.813333-50.78 60.926667-84a546.88 546.88 0 0 0 32.806666-72.873333c32.12 10.413333 62.046667 23.073333 89.1 37.826667 1.013333 0.553333 2 1.113333 3 1.666666a448.106667 448.106667 0 0 1-185.82 117.413334z" fill="#353436" p-id="7255"></path></svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683102597441" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7012" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M191.246667 263.746667c28.546667 15.573333 60 28.96 93.606666 40-17.2 64.12-27.02 134.82-28.62 208.226666H43.133333a488.26 488.26 0 0 1 101.233334-277.766666 422.393333 422.393333 0 0 0 46.88 29.54zM512 554.666667H298.913333c1.566667 69.333333 10.78 136 26.853334 196.206666 57.846667-15.08 120.92-23.733333 186.233333-25.28z m-320.753333 248.253333c28.546667-15.573333 60-28.96 93.606666-40-17.2-64.12-27.02-134.82-28.62-208.226667H43.133333a488.26 488.26 0 0 0 101.233334 277.793334 422.393333 422.393333 0 0 1 46.88-29.566667zM554.666667 66v232.4c61.16-1.513333 120.053333-9.54 173.953333-23.453333a549.766667 549.766667 0 0 0-33.18-78.666667C656.246667 121.126667 606.906667 75.74 554.666667 66z m-42.666667 446V341.073333c-65.333333-1.546667-128.386667-10.2-186.233333-25.28C309.693333 376 300.48 442.666667 298.913333 512z m228.9-196.206667C683.053333 330.873333 620 339.526667 554.666667 341.073333V512h213.086666c-1.566667-69.333333-10.78-136-26.853333-196.206667zM512 1000.666667v-232.4c-61.16 1.513333-120.053333 9.54-173.953333 23.453333a549.766667 549.766667 0 0 0 33.18 78.7C410.42 945.54 459.76 990.926667 512 1000.666667zM371.226667 196.246667a549.766667 549.766667 0 0 0-33.18 78.7c53.9 13.913333 112.793333 21.94 173.953333 23.453333V66c-52.24 9.74-101.58 55.126667-140.773333 130.246667z m504.193333 67.5c-28.546667 15.573333-60 28.96-93.606667 40 17.2 64.12 27.02 134.82 28.62 208.226666h213.1a488.26 488.26 0 0 0-101.233333-277.793333 422.393333 422.393333 0 0 1-46.88 29.566667z m-663.74-37.453334c26 14.193333 54.7 26.44 85.433333 36.626667 10.293333-30.866667 22.426667-59.833333 36.286667-86.406667 24.866667-47.666667 54.086667-85.3 86.853333-111.846666Q428.666667 57.833333 437.28 52.073333c-104.086667 20.666667-196.313333 74.233333-265.38 149.426667a387.226667 387.226667 0 0 0 39.78 24.793333zM420.253333 1002c-32.766667-26.56-62-64.193333-86.853333-111.86-13.86-26.566667-26-55.54-36.286667-86.406667-30.733333 10.186667-59.413333 22.433333-85.433333 36.626667a387.226667 387.226667 0 0 0-39.78 24.793333c69.066667 75.193333 161.293333 128.766667 265.38 149.426667q-8.613333-5.746667-17.026667-12.58z m434.733334-161.64c-26-14.193333-54.666667-26.44-85.433334-36.626667-10.293333 30.866667-22.426667 59.84-36.286666 86.406667-24.866667 47.666667-54.086667 85.333333-86.853334 111.86q-8.42 6.82-17.026666 12.58c104.086667-20.666667 196.313333-74.233333 265.38-149.426667a387.226667 387.226667 0 0 0-39.78-24.78z m-159.546667 30.046667a549.766667 549.766667 0 0 0 33.18-78.7c-53.9-13.913333-112.793333-21.94-173.953333-23.453334V1000.666667c52.24-9.74 101.58-55.126667 140.773333-130.246667zM554.666667 554.666667v170.926666c65.333333 1.546667 128.386667 10.2 186.233333 25.28 16.073333-60.233333 25.286667-126.84 26.853333-196.206666zM646.413333 64.666667c32.766667 26.56 62 64.193333 86.853334 111.86 13.86 26.573333 26 55.54 36.286666 86.406666 30.733333-10.186667 59.413333-22.433333 85.433334-36.626666a387.226667 387.226667 0 0 0 39.78-24.793334c-69.066667-75.206667-161.293333-128.78-265.38-149.44Q638 57.833333 646.413333 64.666667z m275.886667 767.806666A488.26 488.26 0 0 0 1023.533333 554.666667h-213.1c-1.6 73.406667-11.42 144.106667-28.62 208.226666 33.633333 11.066667 65.06 24.453333 93.606667 40a422.393333 422.393333 0 0 1 46.88 29.566667z" fill="#353436" p-id="7013"></path></svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -57,8 +57,8 @@ const cn = {
|
||||
Masks: "所有面具",
|
||||
Clear: "清除聊天",
|
||||
Settings: "对话设置",
|
||||
OpenWebSearch: "开启联网",
|
||||
CloseWebSearch: "关闭联网",
|
||||
OpenTools: "开启插件",
|
||||
CloseTools: "关闭插件",
|
||||
},
|
||||
Rename: "重命名对话",
|
||||
Typing: "正在输入…",
|
||||
|
||||
@@ -59,8 +59,8 @@ const en: LocaleType = {
|
||||
Masks: "Masks",
|
||||
Clear: "Clear Context",
|
||||
Settings: "Settings",
|
||||
OpenWebSearch: "Enable Web Search",
|
||||
CloseWebSearch: "Disable Web Search",
|
||||
OpenTools: "Enable Plugins",
|
||||
CloseTools: "Disable Plugins",
|
||||
},
|
||||
Rename: "Rename Chat",
|
||||
Typing: "Typing…",
|
||||
|
||||
@@ -18,22 +18,27 @@ import { prettyObject } from "../utils/format";
|
||||
import { estimateTokenLength } from "../utils/token";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface ChatToolMessage {
|
||||
toolName: string;
|
||||
toolInput?: string;
|
||||
}
|
||||
|
||||
export type ChatMessage = RequestMessage & {
|
||||
date: string;
|
||||
toolMessages: ChatToolMessage[];
|
||||
streaming?: boolean;
|
||||
isError?: boolean;
|
||||
id: string;
|
||||
model?: ModelType;
|
||||
toolPrompt?: string;
|
||||
};
|
||||
|
||||
export function createMessage(override: Partial<ChatMessage>): ChatMessage {
|
||||
return {
|
||||
id: nanoid(),
|
||||
date: new Date().toLocaleString(),
|
||||
toolMessages: new Array<ChatToolMessage>(),
|
||||
role: "user",
|
||||
content: "",
|
||||
toolPrompt: undefined,
|
||||
...override,
|
||||
};
|
||||
}
|
||||
@@ -56,7 +61,7 @@ export interface ChatSession {
|
||||
clearContextIndex?: number;
|
||||
|
||||
mask: Mask;
|
||||
webSearch: boolean;
|
||||
useTools: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
|
||||
@@ -80,7 +85,7 @@ function createEmptySession(): ChatSession {
|
||||
lastSummarizeIndex: 0,
|
||||
|
||||
mask: createEmptyMask(),
|
||||
webSearch: false,
|
||||
useTools: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -308,91 +313,126 @@ export const useChatStore = create<ChatStore>()(
|
||||
content,
|
||||
};
|
||||
session.messages.push(savedUserMessage);
|
||||
});
|
||||
|
||||
if (session.webSearch) {
|
||||
const query = encodeURIComponent(content);
|
||||
let searchResult = await api.searchTool.call(query);
|
||||
console.log("[Tools] ", searchResult);
|
||||
const webSearchPrompt = `
|
||||
Using the provided web search results, write a comprehensive reply to the given query.
|
||||
If the provided search results refer to multiple subjects with the same name, write separate answers for each subject.
|
||||
Make sure to cite results using \`[[number](URL)]\` notation after the reference.
|
||||
|
||||
Web search json results:
|
||||
"""
|
||||
${JSON.stringify(searchResult)}
|
||||
"""
|
||||
|
||||
Current date:
|
||||
"""
|
||||
${new Date().toISOString()}
|
||||
"""
|
||||
|
||||
Query:
|
||||
"""
|
||||
${content}
|
||||
"""
|
||||
|
||||
Reply in ${getLang()} and markdown.`;
|
||||
userMessage.toolPrompt = webSearchPrompt;
|
||||
}
|
||||
// save user's and bot's message
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages.push(botMessage);
|
||||
});
|
||||
|
||||
// make request
|
||||
api.llm.chat({
|
||||
messages: sendMessages,
|
||||
config: { ...modelConfig, stream: true },
|
||||
onUpdate(message) {
|
||||
botMessage.streaming = true;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
}
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
},
|
||||
onFinish(message) {
|
||||
botMessage.streaming = false;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
get().onNewMessage(botMessage);
|
||||
}
|
||||
ChatControllerPool.remove(session.id, botMessage.id);
|
||||
},
|
||||
onError(error) {
|
||||
const isAborted = error.message.includes("aborted");
|
||||
botMessage.content =
|
||||
"\n\n" +
|
||||
prettyObject({
|
||||
error: true,
|
||||
message: error.message,
|
||||
if (session.useTools && modelConfig.model.endsWith("0613")) {
|
||||
console.log("[ToolAgent] start");
|
||||
api.llm.toolAgentChat({
|
||||
messages: sendMessages,
|
||||
config: { ...modelConfig, stream: true },
|
||||
onUpdate(message) {
|
||||
botMessage.streaming = true;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
}
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
botMessage.streaming = false;
|
||||
userMessage.isError = !isAborted;
|
||||
botMessage.isError = !isAborted;
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
ChatControllerPool.remove(
|
||||
session.id,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
},
|
||||
onToolUpdate(toolName, toolInput) {
|
||||
botMessage.streaming = true;
|
||||
if (toolName && toolInput) {
|
||||
botMessage.toolMessages.push({
|
||||
toolName,
|
||||
toolInput,
|
||||
});
|
||||
}
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
},
|
||||
onFinish(message) {
|
||||
botMessage.streaming = false;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
get().onNewMessage(botMessage);
|
||||
}
|
||||
ChatControllerPool.remove(session.id, botMessage.id);
|
||||
},
|
||||
onError(error) {
|
||||
const isAborted = error.message.includes("aborted");
|
||||
botMessage.content =
|
||||
"\n\n" +
|
||||
prettyObject({
|
||||
error: true,
|
||||
message: error.message,
|
||||
});
|
||||
botMessage.streaming = false;
|
||||
userMessage.isError = !isAborted;
|
||||
botMessage.isError = !isAborted;
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
ChatControllerPool.remove(
|
||||
session.id,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
|
||||
console.error("[Chat] failed ", error);
|
||||
},
|
||||
onController(controller) {
|
||||
// collect controller for stop/retry
|
||||
ChatControllerPool.addController(
|
||||
session.id,
|
||||
botMessage.id ?? messageIndex,
|
||||
controller,
|
||||
);
|
||||
},
|
||||
});
|
||||
console.error("[Chat] failed ", error);
|
||||
},
|
||||
onController(controller) {
|
||||
// collect controller for stop/retry
|
||||
ChatControllerPool.addController(
|
||||
session.id,
|
||||
botMessage.id ?? messageIndex,
|
||||
controller,
|
||||
);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// make request
|
||||
api.llm.chat({
|
||||
messages: sendMessages,
|
||||
config: { ...modelConfig, stream: true },
|
||||
onUpdate(message) {
|
||||
botMessage.streaming = true;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
}
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
},
|
||||
onFinish(message) {
|
||||
botMessage.streaming = false;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
get().onNewMessage(botMessage);
|
||||
}
|
||||
ChatControllerPool.remove(session.id, botMessage.id);
|
||||
},
|
||||
onError(error) {
|
||||
const isAborted = error.message.includes("aborted");
|
||||
botMessage.content =
|
||||
"\n\n" +
|
||||
prettyObject({
|
||||
error: true,
|
||||
message: error.message,
|
||||
});
|
||||
botMessage.streaming = false;
|
||||
userMessage.isError = !isAborted;
|
||||
botMessage.isError = !isAborted;
|
||||
get().updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
ChatControllerPool.remove(
|
||||
session.id,
|
||||
botMessage.id ?? messageIndex,
|
||||
);
|
||||
|
||||
console.error("[Chat] failed ", error);
|
||||
},
|
||||
onController(controller) {
|
||||
// collect controller for stop/retry
|
||||
ChatControllerPool.addController(
|
||||
session.id,
|
||||
botMessage.id ?? messageIndex,
|
||||
controller,
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getMemoryPrompt() {
|
||||
@@ -532,7 +572,7 @@ Reply in ${getLang()} and markdown.`;
|
||||
api.llm.chat({
|
||||
messages: topicMessages,
|
||||
config: {
|
||||
model: "gpt-3.5-turbo",
|
||||
model: "gpt-3.5-turbo-0613",
|
||||
},
|
||||
onFinish(message) {
|
||||
get().updateCurrentSession(
|
||||
|
||||
@@ -38,7 +38,7 @@ export const DEFAULT_CONFIG = {
|
||||
models: DEFAULT_MODELS as any as LLMModel[],
|
||||
|
||||
modelConfig: {
|
||||
model: "gpt-3.5-turbo" as ModelType,
|
||||
model: "gpt-3.5-turbo-0613" as ModelType,
|
||||
temperature: 0.5,
|
||||
top_p: 1,
|
||||
max_tokens: 2000,
|
||||
|
||||
Reference in New Issue
Block a user