LLM
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
import OpenAI from "openai";
|
||||
import storage from 'node-persist'
|
||||
import { sendMsg } from "../lib/qq";
|
||||
import prompt from './prompt.txt'
|
||||
|
||||
await storage.init();
|
||||
storage.clear();
|
||||
|
||||
const client = new OpenAI({
|
||||
baseURL: process.env.OPENAI_BASE_URL,
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
// logLevel: "debug"
|
||||
})
|
||||
|
||||
const tools = [{
|
||||
type: "function" as const,
|
||||
name: "send_msg",
|
||||
description: "Send a message to the user. Always use this to respond to the user.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
text: { type: "string", description: "The message content sent to the user through QQ." }
|
||||
},
|
||||
required: ["text"],
|
||||
additionalProperties: false
|
||||
},
|
||||
strict: true
|
||||
}]
|
||||
|
||||
/**
|
||||
*
|
||||
* @param input 提问
|
||||
* @param target_id 用户 QQ 号
|
||||
*/
|
||||
export async function chat(input: string, target_id: string) {
|
||||
const chatHistoryKey = `chat_history_${target_id}`;
|
||||
let chatHistory: OpenAI.Responses.ResponseInput = await storage.getItem(chatHistoryKey) || [];
|
||||
|
||||
// 添加新输入到对话历史
|
||||
chatHistory.push({ role: "user", content: input });
|
||||
|
||||
// 保存更新后的对话历史
|
||||
console.log(`[LLM] 使用对话, 历史:`, chatHistory);
|
||||
|
||||
await storage.setItem(chatHistoryKey, chatHistory);
|
||||
|
||||
const response = await client.responses.create({
|
||||
model: process.env.CHAT_MODEL || "gpt-5-nano",
|
||||
instructions: prompt,
|
||||
reasoning: { effort: 'minimal' },
|
||||
input: chatHistory,
|
||||
tools
|
||||
});
|
||||
|
||||
await storage.setItem(chatHistoryKey, chatHistory);
|
||||
|
||||
// 继续调用工具,直到没有工具调用为止
|
||||
|
||||
await toolUseCycle(response.output);
|
||||
|
||||
async function toolUseCycle(outputArr: OpenAI.Responses.ResponseOutputItem[]) {
|
||||
chatHistory.push(...outputArr);
|
||||
const functionCalls = (outputArr ?? []).filter(item => item.type === 'function_call');
|
||||
console.log("进入 toolUseCycle, with functionCalls", functionCalls.length, "个");
|
||||
console.log(JSON.stringify(chatHistory, null, 2));
|
||||
|
||||
if (functionCalls.length == 0) {
|
||||
let lastMessage = outputArr.at(-1);
|
||||
if (!lastMessage) return
|
||||
if (lastMessage.type != 'message') return
|
||||
if (lastMessage.role != 'assistant') return
|
||||
|
||||
const msg = lastMessage.content.map(c => c.type == 'output_text' ? c.text : '').join('');
|
||||
if (msg.trim().length > 0) {
|
||||
// 结束,发送最后的消息
|
||||
sendMsg(msg, target_id);
|
||||
}
|
||||
return
|
||||
}
|
||||
for (const item of functionCalls ?? []) {
|
||||
if (item.name === "send_msg") {
|
||||
console.log(item.arguments);
|
||||
|
||||
const { text } = JSON.parse(item.arguments);
|
||||
sendMsg(text, target_id);
|
||||
|
||||
chatHistory.push({ type: "function_call_output", call_id: item.call_id, output: "OK" });
|
||||
}
|
||||
}
|
||||
await storage.setItem(chatHistoryKey, chatHistory);
|
||||
|
||||
const response = await client.responses.create({
|
||||
model: process.env.CHAT_MODEL || "gpt-5-nano",
|
||||
instructions: prompt,
|
||||
reasoning: { effort: 'minimal' },
|
||||
input: chatHistory,
|
||||
tools
|
||||
});
|
||||
|
||||
toolUseCycle(response.output);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetChat(target_id: string) {
|
||||
const chatHistoryKey = `chat_history_${target_id}`;
|
||||
await storage.removeItem(chatHistoryKey);
|
||||
sendMsg("已为你重置对话历史。", target_id);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Role
|
||||
You are a “humorous, gentle, yet professional” Chinese chat and knowledge assistant working in a QQ-like instant messaging environment.
|
||||
|
||||
# Goals
|
||||
1) Engage in natural casual chat;
|
||||
2) Provide accurate answers;
|
||||
3) Explain complex topics clearly over multiple turns.
|
||||
|
||||
# Style
|
||||
- Friendly but not cheesy; witty but not snarky; professional but not stiff.
|
||||
- Use short sentences and bullet points; send 1–3 sentences per message.
|
||||
- Use one emoji appropriately; avoid excessive emoji or long paragraphs.
|
||||
|
||||
# Tool Rules (Important)
|
||||
- **Never** output text directly in the assistant channel. All user-visible content **must** be sent via `tools.send_msg`.
|
||||
- Each message must not exceed 50 characters. If it’s longer, split it into multiple `tools.send_msg` messages to simulate natural chat flow.
|
||||
- Keep lists/code snippets short; if long, split them into multiple messages.
|
||||
|
||||
# Clarification
|
||||
- If a user’s question is **unclear / ambiguous / missing details**, **immediately ask for clarification** and provide **specific options**.
|
||||
- After asking, briefly explain **why** that information is needed.
|
||||
Reference in New Issue
Block a user