mirror of
https://github.com/Wx-2025/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 06:55:51 +00:00
release: v2.2.2 [2026-05-27 11:10:55]
### 新功能 - **Function Call 填表模式**:在填表设置中新增独立开关,启用后支持通过 OpenAI 兼容接口(DeepSeek / OpenRouter / 各类中转等)直接返回结构化操作列表,绕过 `<Amily2Edit>` 文本解析路径,填表更稳定 - 遇到不支持 `tool_choice` 的接口时自动降级重试 - 对思考模型注入强制调用指令,防止绕过工具直接输出文本 - 全部走 ST 后端代理,修复 CSP 拦截直连外部 URL 的问题 - **主界面新增提示词链编辑器入口**,同时调换了记忆管理与角色世界书的按钮位置 - **规则中心**新增"自动排除用户楼层"选项 ### 修复 - 提示词链按钮点击无响应(改为事件委托方式绑定) - 拖拽组件微抖误触发(加 5px 移动阈值过滤) - 填表检查窗若干问题修复;翰林院(批量回填)修复;防抖逻辑落地 - 角色世界书入口添加使用警告弹窗(强制 10 秒倒计时),提示该功能长期未维护 - ApiProfile `fakeStream` 字段保存丢失问题 - 正文优化默认改为关闭状态 - NGMS / NCCS API 配置槽位标签修正(NGMS→总结,NCCS→填表) - API Profile 面板选择逻辑统一重构,修复多处旧字段覆盖新配置的问题 - 世界书控制参数兼容性修复(排除递归、插入位置、扫描深度等,适配 ST 1.17.0+)
This commit is contained in:
108
core/api.js
108
core/api.js
@@ -949,3 +949,111 @@ export async function checkAndFixWithAPI(latestMessage, previousMessages) {
|
||||
const { processOptimization } = await import('./summarizer.js');
|
||||
return await processOptimization(latestMessage, previousMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 OpenAI Function Call 调用 AI,返回 tool_calls[0].function.arguments 字符串。
|
||||
* 仅支持 openai / openai_test 接口(Google / ST preset / backend 不在标准 tool_calls 格式下工作)。
|
||||
*
|
||||
* @param {Array} messages
|
||||
* @param {Object} tool - OpenAI tools 定义对象(单个,含 type/function 字段)
|
||||
* @param {Object} options - 同 callAI 的 options,支持 slot / customParams 等
|
||||
* @returns {Promise<string|null>} arguments JSON 字符串,失败返回 null
|
||||
*/
|
||||
export async function callAIForTools(messages, tool, options = {}) {
|
||||
const apiSettings = await getApiSettings(options.slot || 'main');
|
||||
|
||||
const finalOptions = {
|
||||
maxTokens: apiSettings.maxTokens,
|
||||
temperature: apiSettings.temperature,
|
||||
model: apiSettings.model,
|
||||
apiUrl: apiSettings.apiUrl,
|
||||
apiKey: apiSettings.apiKey,
|
||||
apiProvider: apiSettings.apiProvider,
|
||||
customParams: { ...(apiSettings.customParams ?? {}), ...(options.customParams ?? {}) },
|
||||
...options,
|
||||
};
|
||||
|
||||
const FC_SUPPORTED_PROVIDERS = new Set(['openai', 'openai_test', 'custom_oai', 'openrouter', 'deepseek', 'xai']);
|
||||
if (!FC_SUPPORTED_PROVIDERS.has(finalOptions.apiProvider)) {
|
||||
console.warn(`[Amily2-外交部] Function Call 不支持当前接口类型: ${finalOptions.apiProvider}`);
|
||||
toastr.warning(`当前 API 接口类型(${finalOptions.apiProvider})不支持 Function Call。`, 'Function Call');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!finalOptions.apiUrl || !finalOptions.model) {
|
||||
console.warn('[Amily2-外交部] API URL 或模型未配置,无法调用 Function Call AI');
|
||||
toastr.error('API URL 或模型未配置。', 'Amily2-外交部');
|
||||
return null;
|
||||
}
|
||||
|
||||
const buildFCBody = (withToolChoice, overrideMessages) => ({
|
||||
chat_completion_source: 'openai',
|
||||
reverse_proxy: finalOptions.apiUrl,
|
||||
proxy_password: finalOptions.apiKey,
|
||||
model: finalOptions.model,
|
||||
messages: overrideMessages ?? messages,
|
||||
max_tokens: finalOptions.maxTokens || 30000,
|
||||
temperature: finalOptions.temperature ?? 1,
|
||||
stream: false,
|
||||
...(finalOptions.customParams || {}),
|
||||
tools: [tool],
|
||||
...(withToolChoice ? { tool_choice: { type: 'function', function: { name: tool.function.name } } } : {}),
|
||||
});
|
||||
|
||||
const doFCRequest = async (withToolChoice, overrideMessages) => {
|
||||
const response = await fetch('/api/backends/chat-completions/generate', {
|
||||
method: 'POST',
|
||||
headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(buildFCBody(withToolChoice, overrideMessages)),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Function Call 请求失败: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
// ST 代理在上游报错时仍返回 HTTP 200,错误信息在 body 里
|
||||
if (data?.error) {
|
||||
throw new Error(`Function Call 请求失败: ${JSON.stringify(data.error)}`);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
try {
|
||||
console.groupCollapsed(`[Amily2号-Function Call] ${new Date().toLocaleTimeString()}`);
|
||||
console.log('【工具】:', tool.function?.name, '【模型】:', finalOptions.model);
|
||||
console.log('【消息】:', messages);
|
||||
console.groupEnd();
|
||||
|
||||
let data;
|
||||
try {
|
||||
// 走 ST 后端代理,避免浏览器 CSP 拦截直连外部 URL
|
||||
data = await doFCRequest(true);
|
||||
} catch (firstError) {
|
||||
// 首次失败(含 ST 代理吞掉错误码场景)无条件去掉 tool_choice 重试一次
|
||||
// 思考模式模型支持 tools 但不支持强制 tool_choice,追加强制指令防止模型直接输出文本
|
||||
console.warn('[Amily2-外交部] 首次 FC 请求失败,去掉 tool_choice 重试…', firstError.message);
|
||||
const retryMessages = [
|
||||
...messages,
|
||||
{ role: 'user', content: `你必须通过调用 \`${tool.function.name}\` 函数来返回结果,禁止直接输出文本内容。` },
|
||||
];
|
||||
data = await doFCRequest(false, retryMessages);
|
||||
}
|
||||
|
||||
const toolCalls = data?.choices?.[0]?.message?.tool_calls;
|
||||
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
||||
console.warn('[Amily2-外交部] Function Call 响应中无 tool_calls,finish_reason:', data?.choices?.[0]?.finish_reason);
|
||||
return null;
|
||||
}
|
||||
|
||||
const argsString = toolCalls[0]?.function?.arguments;
|
||||
console.groupCollapsed('[Amily2号-Function Call 响应]');
|
||||
console.log(argsString);
|
||||
console.groupEnd();
|
||||
return argsString ?? null;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Amily2-外交部] Function Call 调用失败:', error);
|
||||
toastr.error(`Function Call 调用失败: ${error.message}`, 'Amily2-外交部');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user