support xml

This commit is contained in:
锦恢 2025-06-18 20:32:04 +08:00
parent c2a634118b
commit 2c13ce5e6d
7 changed files with 366 additions and 74 deletions

42
package-lock.json generated
View File

@ -2697,6 +2697,16 @@
"@types/node": "*"
}
},
"node_modules/@types/xml2js": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
@ -9245,6 +9255,12 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
@ -11261,6 +11277,28 @@
}
}
},
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@ -11452,7 +11490,8 @@
"uuid": "^11.1.0",
"vue": "^3.5.13",
"vue-i18n": "^11.1.0",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@babel/core": "^7.27.1",
@ -11466,6 +11505,7 @@
"@types/markdown-it": "^14.1.2",
"@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5",
"@types/xml2js": "^0.4.14",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/babel-plugin-jsx": "^1.4.0",
"@vue/devtools-core": "^7.7.6",

View File

@ -33,7 +33,8 @@
"uuid": "^11.1.0",
"vue": "^3.5.13",
"vue-i18n": "^11.1.0",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@babel/core": "^7.27.1",
@ -47,6 +48,7 @@
"@types/markdown-it": "^14.1.2",
"@types/node": "^22.14.0",
"@types/prismjs": "^1.26.5",
"@types/xml2js": "^0.4.14",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/babel-plugin-jsx": "^1.4.0",
"@vue/devtools-core": "^7.7.6",

View File

@ -1,4 +1,4 @@
import type { ToolCallContent, ToolItem } from "@/hook/type";
import type { InputSchema, ToolCallContent, ToolItem } from "@/hook/type";
import { type Ref, ref } from "vue";
import type { OpenAI } from 'openai';
@ -16,6 +16,7 @@ export enum MessageState {
Success = 'success',
ParseJsonError = 'parse json error',
NoToolFunction = 'no tool function',
InvalidXml = 'invalid xml',
}
export interface IExtraInfo {
@ -23,6 +24,7 @@ export interface IExtraInfo {
state: MessageState,
serverName: string,
usage?: ChatCompletionChunk['usage'];
enableXmlWrapper: boolean;
[key: string]: any;
}
@ -52,7 +54,7 @@ export interface EnableToolItem {
name: string;
description: string;
enabled: boolean;
inputSchema: any;
inputSchema: InputSchema;
}
export interface ChatSetting {
@ -108,7 +110,7 @@ export interface IToolRenderMessage {
export type IRenderMessage = ICommonRenderMessage | IToolRenderMessage;
export function getToolSchema(enableTools: EnableToolItem[]) {
export function getToolSchema(enableTools: EnableToolItem[]): any[] {
const toolsSchema = [];
for (let i = 0; i < enableTools.length; i++) {
const enableTool = enableTools[i];

View File

@ -16,11 +16,9 @@
<div>
<span>{{ t('tool-manage') }}</span>
<el-tooltip :content="t('enable-xml-wrapper')" placement="top" effect="light">
<span class="xml-tag" :class="{
<span class="xml-tag" :class="{
'active': tabStorage.settings.enableXmlWrapper
}"
@click="tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper"
>xml</span>
}" @click="tabStorage.settings.enableXmlWrapper = !tabStorage.settings.enableXmlWrapper">xml</span>
</el-tooltip>
</div>
</template>
@ -57,7 +55,7 @@ import { useI18n } from 'vue-i18n';
import { type ChatStorage, type EnableToolItem, getToolSchema } from '../chat';
import { markdownToHtml } from '@/components/main-panel/chat/markdown/markdown';
import { mcpClientAdapter } from '@/views/connect/core';
import { toolSchemaToPromptDescription } from '../../core/prompt';
import { toolSchemaToPromptDescription } from '../../core/xml-wrapper';
const { t } = useI18n();
@ -66,37 +64,34 @@ const tabStorage = inject('tabStorage') as ChatStorage;
const showToolsDialog = ref(false);
const availableToolsNum = computed(() => {
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
return tabStorage.settings.enableTools.filter(tool => tool.enabled).length;
});
// toggleTools
const toggleTools = () => {
showToolsDialog.value = true;
showToolsDialog.value = true;
};
const activeToolsSchemaHTML = computed(() => {
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml(
"```json\n" + jsonString + "\n```"
);
const activeToolsSchemaHTML = computed(() => {
const toolsSchema = getToolSchema(tabStorage.settings.enableTools);
const jsonString = JSON.stringify(toolsSchema, null, 2);
return markdownToHtml(
"```json\n" + jsonString + "\n```"
);
});
const activeToolsXmlPrompt = computed(() => {
if (tabStorage.settings.enableXmlWrapper) {
const prompt = toolSchemaToPromptDescription(tabStorage.settings.enableTools);
return markdownToHtml(
"```markdown\n" + prompt + "\n```"
);
} else {
return '';
}
const activeToolsXmlPrompt = computed(() => {
const prompt = toolSchemaToPromptDescription(tabStorage.settings.enableTools);
return markdownToHtml(
"```markdown\n" + prompt + "\n```"
);
});
// -
const enableAllTools = () => {
tabStorage.settings.enableTools.forEach(tool => {
tabStorage.settings.enableTools.forEach(tool => {
tool.enabled = true;
});
};
@ -151,7 +146,6 @@ onMounted(async () => {
</script>
<style scoped>
.xml-tag {
margin-left: 10px;
border-radius: .5em;
@ -169,5 +163,4 @@ onMounted(async () => {
opacity: 1;
transition: var(--animation-3s);
}
</style>

View File

@ -2,7 +2,14 @@ import type { ToolCallContent, ToolCallResponse } from "@/hook/type";
import { MessageState, type ToolCall } from "../chat-box/chat";
import { mcpClientAdapter } from "@/views/connect/core";
import type { BasicLlmDescription } from "@/views/setting/llm";
import { redLog } from "@/views/setting/util";
import type OpenAI from "openai";
export interface TaskLoopChatOption {
id?: string
proxyServer?: string
enableXmlWrapper?: boolean
}
export type ChatCompletionCreateParamsBase = OpenAI.Chat.Completions.ChatCompletionCreateParams & TaskLoopChatOption;
export interface ToolCallResult {
state: MessageState;
@ -60,7 +67,7 @@ function deserializeToolCallResponse(toolArgs: string) {
}
}
function handleToolResponse(toolResponse: ToolCallResponse) {
export function handleToolResponse(toolResponse: ToolCallResponse) {
if (typeof toolResponse === 'string') {
return {
@ -98,7 +105,14 @@ function parseErrorObject(error: any): string {
}
}
function grokIndexAdapter(toolCall: ToolCall, callId2Index: Map<string, number>): IToolCallIndex {
/**
* @description ID映射为索引
* @param toolCall
* @param callId2Index ID到索引的映射表
* @returns
*/
export function idAsIndexAdapter(toolCall: ToolCall, callId2Index: Map<string, number>): IToolCallIndex {
// grok 采用 id 作为 index需要将 id 映射到 zero-based 的 index
if (!toolCall.id) {
return 0;
@ -109,24 +123,42 @@ function grokIndexAdapter(toolCall: ToolCall, callId2Index: Map<string, number>)
return callId2Index.get(toolCall.id)!;
}
function geminiIndexAdapter(toolCall: ToolCall): IToolCallIndex {
/**
* @description
* @param toolCall
* @returns 0
*/
export function singleCallIndexAdapter(toolCall: ToolCall): IToolCallIndex {
// TODO: 等待后续支持
return 0;
}
function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
/**
* @description
* @param toolCall
* @returns
*/
export function defaultIndexAdapter(toolCall: ToolCall): IToolCallIndex {
return toolCall.index || 0;
}
export function getToolCallIndexAdapter(llm: BasicLlmDescription) {
export function getToolCallIndexAdapter(llm: BasicLlmDescription, chatData: ChatCompletionCreateParamsBase) {
// 如果是 xml 模式,那么 index adapter 必须是 idAsIndexAdapter
if (chatData.enableXmlWrapper) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
if (llm.userModel.startsWith('gemini')) {
return geminiIndexAdapter;
return singleCallIndexAdapter;
}
if (llm.userModel.startsWith('grok')) {
const callId2Index = new Map<string, number>();
return (toolCall: ToolCall) => grokIndexAdapter(toolCall, callId2Index);
return (toolCall: ToolCall) => idAsIndexAdapter(toolCall, callId2Index);
}
return defaultIndexAdapter;

View File

@ -1,6 +1,6 @@
/* eslint-disable */
import { ref, type Ref } from "vue";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState } from "../chat-box/chat";
import { type ToolCall, type ChatStorage, getToolSchema, MessageState, type ChatMessage } from "../chat-box/chat";
import { useMessageBridge, MessageBridge, createMessageBridge } from "@/api/message-bridge";
import type { OpenAI } from 'openai';
import { llmManager, llms, type BasicLlmDescription } from "@/views/setting/llm";
@ -13,6 +13,7 @@ import { mcpSetting } from "@/hook/mcp";
import { mcpClientAdapter } from "@/views/connect/core";
import type { ToolItem } from "@/hook/type";
import chalk from 'chalk';
import { getXmlWrapperPrompt, getToolCallFromXmlString, getXmlsFromString, handleXmlWrapperToolcall, toNormaliseToolcall, getXmlResultPrompt } from "./xml-wrapper";
export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk;
export interface TaskLoopChatOption {
@ -90,14 +91,26 @@ export class TaskLoop {
this.bridge = useMessageBridge();
}
private handleChunkDeltaContent(chunk: ChatCompletionChunk) {
/**
* @description streaming content
* xml toocall
* @param chunk
* @param chatData
*/
private handleChunkDeltaContent(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
this.streamingContent.value += content;
}
}
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
/**
* @description streaming chunk tool_calls
* @param chunk
* @param chatData
* @param toolcallIndexAdapter
*/
private handleChunkDeltaToolCalls(chunk: ChatCompletionChunk, chatData: ChatCompletionCreateParamsBase, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) {
const toolCall = chunk.choices[0]?.delta?.tool_calls?.[0];
if (toolCall) {
@ -154,12 +167,12 @@ export class TaskLoop {
// 处理增量的 content 和 tool_calls
if (chatData.enableXmlWrapper) {
this.handleChunkDeltaContent(chunk);
this.handleChunkDeltaContent(chunk, chatData);
// no tool call in enableXmlWrapper
this.handleChunkUsage(chunk);
} else {
this.handleChunkDeltaContent(chunk);
this.handleChunkDeltaToolCalls(chunk, toolcallIndexAdapter);
this.handleChunkDeltaContent(chunk, chatData);
this.handleChunkDeltaToolCalls(chunk, chatData, toolcallIndexAdapter);
this.handleChunkUsage(chunk);
}
@ -218,24 +231,35 @@ export class TaskLoop {
const model = this.getLlmConfig().userModel;
const temperature = tabStorage.settings.temperature;
const tools = getToolSchema(tabStorage.settings.enableTools);
const parallelToolCalls = tabStorage.settings.parallelToolCalls;
const proxyServer = mcpSetting.proxyServer || '';
// 如果是 xml 模式,则 tools 为空
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
const tools = enableXmlWrapper ? []: getToolSchema(tabStorage.settings.enableTools);
const userMessages = [];
// 尝试获取 system prompt在 api 模式下systemPrompt 就是目标提词
// 但是在 UI 模式下systemPrompt 只是一个 index需要从后端数据库中获取真实 prompt
if (tabStorage.settings.systemPrompt) {
const prompt = getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
userMessages.push({
role: 'system',
content: prompt
});
let prompt = '';
// 如果存在系统提示词,则从数据库中获取对应的数据
if (tabStorage.settings.systemPrompt) {
prompt += getSystemPrompt(tabStorage.settings.systemPrompt) || tabStorage.settings.systemPrompt;
}
// 如果是 xml 模式,则在开头注入 xml
if (enableXmlWrapper) {
prompt += getXmlWrapperPrompt(tabStorage.settings.enableTools, tabStorage);
}
userMessages.push({
role: 'system',
content: prompt
});
// 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息
const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength);
userMessages.push(...loadMessages);
@ -253,7 +277,7 @@ export class TaskLoop {
parallelToolCalls,
messages: userMessages,
proxyServer,
enableXmlWrapper
enableXmlWrapper,
} as ChatCompletionCreateParamsBase;
return chatData;
@ -474,6 +498,7 @@ export class TaskLoop {
// 等待连接完成
await this.nodejsStatus.connectionFut;
}
const enableXmlWrapper = tabStorage.settings.enableXmlWrapper;
// 添加目前的消息
tabStorage.messages.push({
@ -482,7 +507,8 @@ export class TaskLoop {
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown'
serverName: this.getLlmConfig().id || 'unknown',
enableXmlWrapper
}
});
@ -511,7 +537,7 @@ export class TaskLoop {
this.currentChatId = chatData.id!;
const llm = this.getLlmConfig();
const toolcallIndexAdapter = getToolCallIndexAdapter(llm);
const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData);
// 发送请求
const doConverationResult = await this.doConversation(chatData, toolcallIndexAdapter);
@ -526,7 +552,8 @@ export class TaskLoop {
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown'
serverName: this.getLlmConfig().id || 'unknown',
enableXmlWrapper
}
});
@ -563,7 +590,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: undefined
usage: undefined,
enableXmlWrapper
}
});
break;
@ -578,7 +606,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
} else if (toolCallResult.state === MessageState.ToolCall) {
@ -592,7 +621,8 @@ export class TaskLoop {
created: Date.now(),
state: toolCallResult.state,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
}
@ -606,10 +636,96 @@ export class TaskLoop {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage
usage: this.completionUsage,
enableXmlWrapper
}
});
break;
// 如果 xml 模型,需要检查内部是否含有有效的 xml 进行调用
if (tabStorage.settings.enableXmlWrapper) {
const xmls = getXmlsFromString(this.streamingContent.value);
if (xmls.length === 0) {
// 没有 xml 了,说明对话结束
break;
}
// 使用 user 作为身份来承载 xml 调用的结果
// 并且在 extra 内存储结构化信息
const fakeUserMessage = {
role: 'user',
content: '',
extraInfo: {
created: Date.now(),
state: MessageState.Success,
serverName: this.getLlmConfig().id || 'unknown',
usage: this.completionUsage,
enableXmlWrapper,
}
} as ChatMessage;
// 有 xml 了,需要检查 xml 内部是否有有效的 xml 进行调用
for (const xml of xmls) {
const toolcall = await getToolCallFromXmlString(xml);
if (!toolcall) {
continue;
}
// toolcall 事件
// 此处使用的是 xml 使用的 toolcall为了保持一致性需要转换成 openai 标准下的 toolcall
const normaliseToolcall = toNormaliseToolcall(toolcall, toolcallIndexAdapter);
this.consumeToolCalls(normaliseToolcall);
// 调用 XML 调用,其实可以考虑后续把这个循环改成 Promise.race
const toolCallResult = await handleXmlWrapperToolcall(toolcall);
// toolcalled 事件
// 因为是交付给后续进行统一消费的,所以此处的输出满足 openai 接口规范
this.consumeToolCalleds(toolCallResult);
// XML 模式下只存在 assistant 和 user 这两个角色,因此,以 user 为身份来存储
if (toolCallResult.state === MessageState.InvalidXml) {
// 如果是因为解析 XML 错误,则重新开始
tabStorage.messages.pop();
jsonParseErrorRetryCount ++;
redLog('解析 XML 错误 ' + normaliseToolcall?.function?.arguments);
// 如果因为 XML 错误而失败太多,就只能中断了
if (jsonParseErrorRetryCount >= (this.taskOptions.maxJsonParseRetry || 3)) {
const prompt = getXmlResultPrompt(toolcall.callId, `解析 XML 错误,无法继续调用工具 (累计错误次数 ${this.taskOptions.maxJsonParseRetry})`);
fakeUserMessage.content += prompt;
break;
}
} else if (toolCallResult.state === MessageState.Success) {
// TODO: xml 目前只支持 text 类型的回复
const toolCallResultString = toolCallResult.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
} else if (toolCallResult.state === MessageState.ToolCall) {
// TODO: xml 目前只支持 text 类型的回复
const toolCallResultString = toolCallResult.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
fakeUserMessage.content += getXmlResultPrompt(toolcall.callId, toolCallResultString);
}
}
tabStorage.messages.push(fakeUserMessage);
} else {
// 普通对话直接结束
break;
}
} else {
// 一些提示

View File

@ -1,7 +1,21 @@
import type { ToolItem } from "@/hook/type";
import { parseString } from 'xml2js';
import { MessageState, type ToolCall } from '../chat-box/chat';
import { mcpClientAdapter } from '@/views/connect/core';
import { handleToolResponse, type IToolCallIndex, type ToolCallResult } from './handle-tool-calls';
import type { ChatStorage, EnableToolItem } from "../chat-box/chat";
export function toolSchemaToPromptDescription(tools: ToolItem[]) {
export interface XmlToolCall {
server: string;
name: string;
callId: string;
parameters: Record<string, string>;
}
export function toolSchemaToPromptDescription(enableTools: EnableToolItem[]) {
let prompt = '';
const tools = enableTools.filter(tool => tool.enabled);
// 无参数的工具
const noParamTools = tools.filter(tool =>
@ -38,15 +52,32 @@ export function toolSchemaToPromptDescription(tools: ToolItem[]) {
return prompt;
}
export function getXmlWrapperPrompt(tools: ToolItem[]) {
export function getXmlWrapperPrompt(tools: EnableToolItem[], tabStorage: ChatStorage) {
const toolPrompt = toolSchemaToPromptDescription(tools);
const requests = [
`ALWAYS analyze what function calls would be appropriate for the task`,
`ALWAYS format your function call usage EXACTLY as specified in the schema`,
`NEVER skip required parameters in function calls`,
`NEVER invent functions that arent available to you`,
`ALWAYS wait for function call execution results before continuing`,
`After invoking a function, wait for the output in <function_results> tag and then continue with your response`,
`NEVER mock or form <function_results> on your own, it will be provided to you after the execution`,
];
if (!tabStorage.settings.parallelToolCalls) {
requests.push(`NEVER invoke multiple functions in a single response`);
}
const requestString = requests.map((text, index) => {
return `${index + 1}. ${text}`;
}).join('\n');
return `
[Start Fresh Session from here]
<SYSTEM>
You are SuperAssistant with the capabilities of invoke functions and make the best use of it during your assistance, a knowledgeable assistant focused on answering questions and providing information on any topics.
You are OpenMCP Assistant with the capabilities of invoke functions and make the best use of it during your assistance, a knowledgeable assistant focused on answering questions and providing information on any topics.
In this environment you have access to a set of tools you can use to answer the user's question.
You have access to a set of functions you can use to answer the user's question. You do NOT currently have the ability to inspect files or interact with external resources, except by invoking the below functions.
@ -94,15 +125,7 @@ You can invoke one or more functions by writing a "<function_calls>" block like
String and scalar parameters should be specified as is, while lists and objects should use JSON format. Note that spaces for string values are not stripped. The output is not expected to be valid XML and is parsed with regular expressions.
When a user makes a request:
1. ALWAYS analyze what function calls would be appropriate for the task
2. ALWAYS format your function call usage EXACTLY as specified in the schema
3. NEVER skip required parameters in function calls
4. NEVER invent functions that arent available to you
5. ALWAYS wait for function call execution results before continuing
6. After invoking a function, wait for the output in <function_results> tag and then continue with your response
7. NEVER invoke multiple functions in a single response
8. NEVER mock or form <function_results> on your own, it will be provided to you after the execution
${requestString}
Answer the user's request using the relevant tool(s), if they are available. Check that all the required parameters for each tool call are provided or can reasonably be inferred from context. IF there are no relevant tools or there are missing values for required parameters, ask the user to supply these values; otherwise proceed with the tool calls. If the user provides a specific value for a parameter (for example provided in quotes), make sure to use that value EXACTLY. DO NOT make up values for or ask about optional parameters. Carefully analyze descriptive terms in the request as they may indicate required parameter values that should be included even if not explicitly quoted.
@ -148,6 +171,90 @@ User Interaction Starts here:
}
export function getXmlWrapperPromptCn() {
export function getXmlResultPrompt(callId: string, result: string) {
return `
\`\`\`xml
<function_results>
<result call_id="${callId}">
${result}
</result>
</function_results>
\`\`\`
`.trim() + '\n\n';
}
export function getXmlsFromString(content: string) {
const matches = content.matchAll(/```xml\n([\s\S]*?)\n```/g);
return Array.from(matches).map(match => match[1].trim());
}
export async function getToolCallFromXmlString(xmlString: string): Promise<XmlToolCall | null> {
try {
const result = await new Promise<any>((resolve, reject) => {
parseString(xmlString, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
if (!result?.function_calls?.invoke) {
return null;
}
const invoke = result.function_calls.invoke[0].$;
const parameters: Record<string, any> = {};
if (result.function_calls.invoke[0].parameter) {
result.function_calls.invoke[0].parameter.forEach((param: any) => {
const name = param.$.name as string;
parameters[name] = param._;
});
}
// name 可能是 neo4j-mcp.executeReadOnlyCypherQuery
return {
server: '',
name: invoke.name,
callId: invoke.call_id,
parameters
};
} catch (error) {
console.error('Failed to parse function calls:', error);
return null;
}
}
export function toNormaliseToolcall(xmlToolcall: XmlToolCall, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex): ToolCall {
const toolcall = {
id: xmlToolcall.callId,
index: -1,
type: 'function',
function: {
name: xmlToolcall.name,
arguments: JSON.stringify(xmlToolcall.parameters)
}
} as ToolCall;
toolcall.index = toolcallIndexAdapter(toolcall);
return toolcall;
}
export async function handleXmlWrapperToolcall(toolcall: XmlToolCall): Promise<ToolCallResult> {
if (!toolcall) {
return {
content: [{
type: 'error',
text: 'invalid xml'
}],
state: MessageState.InvalidXml
}
}
// 进行调用,根据结果返回不同的值
console.log(toolcall);
const toolResponse = await mcpClientAdapter.callTool(toolcall.name, toolcall.parameters);
return handleToolResponse(toolResponse);
}