From 93d902d77686eff655f7a41851ce85cfecf930f7 Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 11:27:40 +0200 Subject: [PATCH 1/8] Proof of Concept: Calculator Tool --- src/routes/[conversationId]/+page.svelte | 159 +++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/routes/[conversationId]/+page.svelte b/src/routes/[conversationId]/+page.svelte index 9423e4b..df9ebf5 100644 --- a/src/routes/[conversationId]/+page.svelte +++ b/src/routes/[conversationId]/+page.svelte @@ -41,6 +41,154 @@ $: conversationTitle = $currentConversation?.title || 'New conversation'; + async function chooseTool(message){ + const tools = { + calculator: { + explanation: 'Solves complicated calculations with precision.', + instructions: ` +# calculator +## execute +Performs the calculation using javascript syntax and outputs the result. + +\`\`\` +execute +\`\`\` + +Example: +\`\`\` +execute 1 + 1 +\`\`\` +Output: +\`\`\` +2 +\`\`\``, + exec: async (toolSpec) => eval(toolSpec.arguments[0]), + outputContext: 'This is the output of the calculation.' + }, + 'encyclopedia': { + explanation: 'Provides information about a topic.', + instructions: ` +# encyclopedia +## query +Queries the encyclopedia for a topic. It produces up to 5 results. Its output is in JSON. During output processing, in-line reference the result you are using with a Footnote. + +Example: +\`\`\` +query Constantine III +\`\`\` + +Output: +\`\`\` +[ +{id: 789, similarity: 0.9, attributes: {"text": "Constantine III (Latin: Flavius Claudius Constantinus; died shortly before 18 September 411) was a common Roman soldier who was declared emperor in Roman Britain in 407 and established himself in Gaul. He was recognised as co-emperor of the Roman Empire from 409 until 411.", "title": "Constantine III (Western Roman emperor)", "url": "https://en.wikipedia.org/wiki/Constantine_III_(Western_Roman_emperor)"}} +] +\`\`\``, + outputContext: 'Answer the question using only the relevant entries in the context. Do not make a judgement on the quality of the results. Cite your sources by providing the urls for all used results.', + exec: async (toolSpec) => console.log(toolSpec) + }, + 'more-context-tool': { + explanation: 'Select this tool when you need clarification. A tool that will make the user provide you with more context in the current conversation.', + }, + 'noop': { + explanation: 'A tool that signals that there is no need to use a tool.', + }, + 'no-applicable-tool': { + explanation: 'A tool that signals that you don\'t have a good tool to resolve the query.', + } + } + + + const basePrompt = ` +You are "ToolSelector". The following is a list of tools and a description of their utility that are available. You will receive a query from a user. The query is part of a larger conversation that you don't have direct access to. Your responsibility is to select one of the tools to use to resolve the user's query. + +You output everything as JSON formatted like the this: + +\`\`\`json +{ + // Think step by step and provide a reasoning for what you think would be the best tool to use. + reason: "", + // Your actual tool selection. + toolName: "" +} +\`\`\` + +You output nothing else. Not even any reasoning. + +--- + +` + + const toolPrompt = Object.keys(tools).map(key => `# ${key}\n${tools[key].explanation}\n`).join('\n\n') + const fullPrompt = basePrompt + toolPrompt; + + const response = await $currentBackend.sendMessage([ + { + role: 'system', + content: fullPrompt + }, + message + ]) + let match = null; + try { + match = JSON.parse(response.content); + }catch (e) { + console.warn("Could not parse response from tool selector.", response); + } + if(match){ + const selectedTool = match.toolName; + if(selectedTool in tools){ + const tool = tools[selectedTool]; + const toolFn = async () => { + const toolUsagePrompt = ` +You are an AI assistant called "ToolBot". ToolBot is an intelligent chatbot that is designed to use the tool it is provided. ToolBot understands the given tool descriptions and APIs. First, ToolBot thinks step by step and provide a reasoning for what you think would be the method to use. +Only then provide your actual answer formatted as JSON like this: + +\`\`\`json +{ + // Think step by step and provide a reasoning for what you think would be the best method to use. + reason: "", + // Your selected method + method: "", + // Your selected method's arguments + arguments: ["", ""] +} +\`\`\` + +--- + +` + + const toolResponse = await $currentBackend.sendMessage([ + { + role: 'system', + content: toolUsagePrompt + tool.instructions + }, + message + ]) + + const matches = toolResponse.content.matchAll(/```(?:json)?((?:\s|.)+?)```/gm); + for (let match of matches) { + try { + const toolSpec = JSON.parse(match[1]) + const toolOutput = await tool.exec(toolSpec) + + return { + role: 'system', + content: 'Context: \n'+ toolOutput + '\n\n' + tool.outputContext + } + }catch (e) { + console.warn("Could not parse response from tool.", toolResponse); + } + } + } + toolFn.toolName = selectedTool; + return toolFn; + }else{ + console.warn("Selected tool not in list of tools.", response); + } + } + } + async function sendMessageToChat() { waiting = true; let message: Message; @@ -50,9 +198,20 @@ content: inputText }; + await addMessage(message, { backend: 'human', model: 'egg' }, forkMessageId); forkMessageId = $currentConversation?.lastMessageId; inputText = ''; + + const tool = await chooseTool(message); + if(tool){ + const toolOutputMessage = await tool(); + if(toolOutputMessage){ + await addMessage(toolOutputMessage, { backend: tool.toolName, model: 'tool' }, forkMessageId); + } + } + forkMessageId = $currentConversation?.lastMessageId; + if(!autoSend) { waiting = false; return; From 7057770417b87f3e853ea7c5871fd5eac29d7d7c Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 12:25:49 +0200 Subject: [PATCH 2/8] Tool implementation initial refactoring --- src/lib/backend/types.ts | 1 + src/lib/tools/index.ts | 161 +++++++++++++++++++++++ src/lib/tools/types.ts | 30 +++++ src/routes/[conversationId]/+page.svelte | 120 ++--------------- 4 files changed, 204 insertions(+), 108 deletions(-) create mode 100644 src/lib/tools/index.ts create mode 100644 src/lib/tools/types.ts diff --git a/src/lib/backend/types.ts b/src/lib/backend/types.ts index 1ece0a4..44105fa 100644 --- a/src/lib/backend/types.ts +++ b/src/lib/backend/types.ts @@ -1,6 +1,7 @@ export interface Message { role: string; content: string; + name?: string; } export interface Backend { diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts new file mode 100644 index 0000000..9a9e7cc --- /dev/null +++ b/src/lib/tools/index.ts @@ -0,0 +1,161 @@ +import type {Message} from "$lib/backend/types"; +import type {MethodSpec, ToolCall, ToolSpec} from "$lib/tools/types"; + + + +function toolOutput(toolName: string, content: string, prefix?: string, suffix?: string, role = 'system'): Message { + return { + role: role, + content: (prefix ? prefix : `${toolName} Tool Output: \n\n`)+content+(suffix ? suffix : '') + } +} + +function calculatorTool(): ToolSpec { + const toolName = 'calculator' + const explanation = 'Solves complicated calculations with precision.' + const methods: MethodSpec[] = [ + { + name: 'execute', + arguments: [{ + name: 'calculation', + doc: 'The calculation to perform.' + }], + explanation: 'Performs the calculation using javascript syntax and outputs the result.', + examples: [ + { + input: 'execute 1 + 1', + output: '2' + }, + { + input: 'execute 1 + 1 * 2', + output: '3' + } + ], + exec: async (toolCall: ToolCall) => { + return toolOutput( + toolName, + eval(toolCall.arguments[0]), + undefined, + "\n\nThis is the output of the calculation." + ) + } + } + ] + + return { + name: toolName, + explanation: explanation, + methods: methods + } +} + +function encyclopediaTool(): ToolSpec { + const toolName = 'encyclopedia' + const explanation = 'Provides information about a topic.' + const methods: MethodSpec[] = [ + { + name: 'query', + arguments: [{ + name: 'topic', + doc: 'The topic you want to query for more information.' + }], + explanation: 'Queries the encyclopedia for a topic. It produces up to 5 results. Its output is in JSON.', + examples: [ + { + input: 'query "Constantine III"', + output: '[\n{id: 789, similarity: 0.9, attributes: {"text": "Constantine III (Latin: Flavius Claudius Constantinus; died shortly before 18 September 411) was a common Roman soldier who was declared emperor in Roman Britain in 407 and established himself in Gaul. He was recognised as co-emperor of the Roman Empire from 409 until 411.", "title": "Constantine III (Western Roman emperor)", "url": "https://en.wikipedia.org/wiki/Constantine_III_(Western_Roman_emperor)"}}\n]' + }, + { + input: 'query "Illuminati"', + output: `[\n{id: 123, similarity: 0.9, attributes: {"text": "The Illuminati (plural of Latin illuminatus, 'enlightened') is a name given to several groups, both real and fictitious. Historically, the name usually refers to the Bavarian Illuminati, an Enlightenment-era secret society founded on 1 May 1776 in Bavaria, today part of Germany. The society's goals were to oppose superstition, obscurantism, religious influence over public life, and abuses of state power.", "url": "https://en.wikipedia.org/wiki/Illuminati"}}\n]` + } + ], + exec: async (toolCall: ToolCall) => { + return toolOutput( + toolName, + eval(toolCall.arguments[0]), + undefined, + "\n\nAnswer the question using only the relevant entries in the context. Do not make a judgement on the quality of the results. Cite your sources by providing the urls for all used results." + ) + } + } + ] + + return { + name: toolName, + explanation: explanation, + methods: methods + } +} + + + +function codeBlock(content: string, type?: string): string { + return '```' + (type ? type : '') + '\n' + content + '\n```' +} + +export function renderToolInstructions(tool: ToolSpec): string { + const basePrompt = `You are an AI assistant called "ToolBot". +ToolBot is an intelligent chatbot that is designed to use the tool it is provided. +ToolBot understands the given tool descriptions and APIs. +First, ToolBot thinks step by step and provide a reasoning for what you think would be the method to use. +Only then provide your actual answer formatted as JSON like this: +` + codeBlock( +`{ + // Think step by step and provide a reasoning for what you think would be the best method to use. + reason: "", + // Your selected method + method: "", + // Your selected method's arguments + arguments: ["", ""] +}`, 'json') + '\n\n' + + const instructions = tool.methods?.map((method: MethodSpec) => { + const header = `## ${method.name} ${method.arguments.map(it => '<'+it.name+'>').join(' ')}\n` + const explanation = method.explanation + '\n' + 'Arguments: \n' + method.arguments.map(it => '* ' + it.name + ': ' + it.doc).join('\n') + '\n\n' + const examples = 'Examples: \n' + method.examples.map(it => `* \`${method.name} ${it.input}\` => \n${codeBlock(it.output)}\n\n`).join('\n') + '\n\n' + + return `${header}${explanation}${method.examples ? examples : ''}` + }).join('\n\n') + + return basePrompt + instructions +} + +export function renderToolSelectionInstructions(tools: ToolSpec[]): string { + const basePrompt = ` +You are "ToolSelector". The following is a list of tools and a description of their utility that are available. You will receive a query from a user. The query is part of a larger conversation that you don't have direct access to. Your responsibility is to select one of the tools to use to resolve the user's query. + +You output everything as JSON formatted like the this: +` + codeBlock(` +{ + // Think step by step and provide a reasoning for what you think would be the best tool to use. + reason: "", + // Your actual tool selection. + toolName: "" +} +`, 'json') + ` +You output nothing else. Not even any reasoning. + +--- + +` + const toolPrompt = tools.map(tool => `# ${tool.name}\n${tool.explanation}\n`).join('\n\n') + + return basePrompt + toolPrompt +} + +export const tools: ToolSpec[] = [ + { + name: 'more-context-tool', + explanation: 'Select this tool when you need clarification. A tool that will make the user provide you with more context in the current conversation.', + }, + { + name: 'noop', + explanation: 'A tool that signals that there is no need to use a tool.', + }, + { + name: 'no-applicable-tool', + explanation: 'A tool that signals that you don\'t have a good tool to resolve the query.', + }, + calculatorTool() +] diff --git a/src/lib/tools/types.ts b/src/lib/tools/types.ts new file mode 100644 index 0000000..dad8166 --- /dev/null +++ b/src/lib/tools/types.ts @@ -0,0 +1,30 @@ +import type {Message} from "$lib/backend/types"; + +export interface ExampleSpec { + input: string, + output: string +} + +export interface ArgumentSpec { + name: string, + doc: string +} + +export interface ToolCall { + method: string, + arguments: string[] +} + +export interface MethodSpec { + name: string, + arguments: ArgumentSpec[], + explanation: string, + examples: ExampleSpec[], + exec?: (toolSpec: ToolCall) => Promise +} + +export interface ToolSpec { + name: string, + explanation: string, + methods?: MethodSpec[] +} diff --git a/src/routes/[conversationId]/+page.svelte b/src/routes/[conversationId]/+page.svelte index df9ebf5..9ac81c2 100644 --- a/src/routes/[conversationId]/+page.svelte +++ b/src/routes/[conversationId]/+page.svelte @@ -29,6 +29,8 @@ import type { Message } from '$lib/backend/types'; import { prompt, confirm } from "$lib/components/dialogs"; + import {renderToolInstructions, renderToolSelectionInstructions, tools} from "$lib/tools"; + import type {MethodSpec, ToolCall, ToolSpec} from "$lib/tools/types"; let inputText = ''; @@ -42,89 +44,10 @@ $: conversationTitle = $currentConversation?.title || 'New conversation'; async function chooseTool(message){ - const tools = { - calculator: { - explanation: 'Solves complicated calculations with precision.', - instructions: ` -# calculator -## execute -Performs the calculation using javascript syntax and outputs the result. - -\`\`\` -execute -\`\`\` - -Example: -\`\`\` -execute 1 + 1 -\`\`\` -Output: -\`\`\` -2 -\`\`\``, - exec: async (toolSpec) => eval(toolSpec.arguments[0]), - outputContext: 'This is the output of the calculation.' - }, - 'encyclopedia': { - explanation: 'Provides information about a topic.', - instructions: ` -# encyclopedia -## query -Queries the encyclopedia for a topic. It produces up to 5 results. Its output is in JSON. During output processing, in-line reference the result you are using with a Footnote. - -Example: -\`\`\` -query Constantine III -\`\`\` - -Output: -\`\`\` -[ -{id: 789, similarity: 0.9, attributes: {"text": "Constantine III (Latin: Flavius Claudius Constantinus; died shortly before 18 September 411) was a common Roman soldier who was declared emperor in Roman Britain in 407 and established himself in Gaul. He was recognised as co-emperor of the Roman Empire from 409 until 411.", "title": "Constantine III (Western Roman emperor)", "url": "https://en.wikipedia.org/wiki/Constantine_III_(Western_Roman_emperor)"}} -] -\`\`\``, - outputContext: 'Answer the question using only the relevant entries in the context. Do not make a judgement on the quality of the results. Cite your sources by providing the urls for all used results.', - exec: async (toolSpec) => console.log(toolSpec) - }, - 'more-context-tool': { - explanation: 'Select this tool when you need clarification. A tool that will make the user provide you with more context in the current conversation.', - }, - 'noop': { - explanation: 'A tool that signals that there is no need to use a tool.', - }, - 'no-applicable-tool': { - explanation: 'A tool that signals that you don\'t have a good tool to resolve the query.', - } - } - - - const basePrompt = ` -You are "ToolSelector". The following is a list of tools and a description of their utility that are available. You will receive a query from a user. The query is part of a larger conversation that you don't have direct access to. Your responsibility is to select one of the tools to use to resolve the user's query. - -You output everything as JSON formatted like the this: - -\`\`\`json -{ - // Think step by step and provide a reasoning for what you think would be the best tool to use. - reason: "", - // Your actual tool selection. - toolName: "" -} -\`\`\` - -You output nothing else. Not even any reasoning. - ---- - -` - - const toolPrompt = Object.keys(tools).map(key => `# ${key}\n${tools[key].explanation}\n`).join('\n\n') - const fullPrompt = basePrompt + toolPrompt; - const response = await $currentBackend.sendMessage([ { role: 'system', - content: fullPrompt + content: renderToolSelectionInstructions(tools) }, message ]) @@ -136,32 +59,13 @@ You output nothing else. Not even any reasoning. } if(match){ const selectedTool = match.toolName; - if(selectedTool in tools){ - const tool = tools[selectedTool]; + const tool: ToolSpec = tools.find(tool => tool.name === selectedTool); + if(tool){ const toolFn = async () => { - const toolUsagePrompt = ` -You are an AI assistant called "ToolBot". ToolBot is an intelligent chatbot that is designed to use the tool it is provided. ToolBot understands the given tool descriptions and APIs. First, ToolBot thinks step by step and provide a reasoning for what you think would be the method to use. -Only then provide your actual answer formatted as JSON like this: - -\`\`\`json -{ - // Think step by step and provide a reasoning for what you think would be the best method to use. - reason: "", - // Your selected method - method: "", - // Your selected method's arguments - arguments: ["", ""] -} -\`\`\` - ---- - -` - const toolResponse = await $currentBackend.sendMessage([ { role: 'system', - content: toolUsagePrompt + tool.instructions + content: renderToolInstructions(tool) }, message ]) @@ -169,12 +73,12 @@ Only then provide your actual answer formatted as JSON like this: const matches = toolResponse.content.matchAll(/```(?:json)?((?:\s|.)+?)```/gm); for (let match of matches) { try { - const toolSpec = JSON.parse(match[1]) - const toolOutput = await tool.exec(toolSpec) - - return { - role: 'system', - content: 'Context: \n'+ toolOutput + '\n\n' + tool.outputContext + const toolCall: ToolCall = JSON.parse(match[1]) + const method = tool.methods?.find(it => it.name === toolCall.method) + if(method){ + return await method.exec(toolCall) + }else{ + console.warn("Selected method not in list of methods.", toolCall); } }catch (e) { console.warn("Could not parse response from tool.", toolResponse); From 27d6614fe9e30ff70edc69781948fb9e9e2b2cab Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 12:42:55 +0200 Subject: [PATCH 3/8] Initial Encyclopedia Tool --- src/lib/tools/index.ts | 32 +++++++++++++++++++++--- src/routes/[conversationId]/+page.svelte | 4 +++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts index 9a9e7cc..7a74cd6 100644 --- a/src/lib/tools/index.ts +++ b/src/lib/tools/index.ts @@ -49,7 +49,7 @@ function calculatorTool(): ToolSpec { } } -function encyclopediaTool(): ToolSpec { +function encyclopediaTool(baseURL: string, vectorSpaceId: string, token: string): ToolSpec { const toolName = 'encyclopedia' const explanation = 'Provides information about a topic.' const methods: MethodSpec[] = [ @@ -71,9 +71,34 @@ function encyclopediaTool(): ToolSpec { } ], exec: async (toolCall: ToolCall) => { + const query = toolCall.arguments[0] + + const data = new FormData(); + data.append('vector_space_id', vectorSpaceId) + data.append('modality', 'TEXT') + data.append('top_k', '5') + data.append('query', new File([new Blob([query])], '_')) + + const result = await fetch(`${baseURL}/api/v0/space/${vectorSpaceId}/lookup`, { + method: 'POST', + body: data, + headers: { + 'Authorization': 'Bearer ' + token + } + }) + const json = await result.json() + + const results = json.results.map(it => {return { + ...it, + url: `${it.attributes.url}#:~:text=${encodeURIComponent(it.attributes.text.substring(0, 25))}` + }}) + + + const output = codeBlock(JSON.stringify(results)) + return toolOutput( toolName, - eval(toolCall.arguments[0]), + output, undefined, "\n\nAnswer the question using only the relevant entries in the context. Do not make a judgement on the quality of the results. Cite your sources by providing the urls for all used results." ) @@ -157,5 +182,6 @@ export const tools: ToolSpec[] = [ name: 'no-applicable-tool', explanation: 'A tool that signals that you don\'t have a good tool to resolve the query.', }, - calculatorTool() + calculatorTool(), + encyclopediaTool('http://localhost:8080', '5', '64291804-d1d9-4e40-9f4f-9d145bc30882') ] diff --git a/src/routes/[conversationId]/+page.svelte b/src/routes/[conversationId]/+page.svelte index 9ac81c2..fbe339d 100644 --- a/src/routes/[conversationId]/+page.svelte +++ b/src/routes/[conversationId]/+page.svelte @@ -59,6 +59,8 @@ } if(match){ const selectedTool = match.toolName; + console.info("Selected Tool:", selectedTool) + const tool: ToolSpec = tools.find(tool => tool.name === selectedTool); if(tool){ const toolFn = async () => { @@ -74,6 +76,8 @@ for (let match of matches) { try { const toolCall: ToolCall = JSON.parse(match[1]) + console.info("Tool Call:", toolCall.method, toolCall.arguments) + const method = tool.methods?.find(it => it.name === toolCall.method) if(method){ return await method.exec(toolCall) From d250d6581d680ce918dc5c84ba6b58d508f1e677 Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 12:56:56 +0200 Subject: [PATCH 4/8] Fix example inputs --- src/lib/tools/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts index 7a74cd6..73ecdfc 100644 --- a/src/lib/tools/index.ts +++ b/src/lib/tools/index.ts @@ -23,11 +23,11 @@ function calculatorTool(): ToolSpec { explanation: 'Performs the calculation using javascript syntax and outputs the result.', examples: [ { - input: 'execute 1 + 1', + input: '1 + 1', output: '2' }, { - input: 'execute 1 + 1 * 2', + input: '1 + 1 * 2', output: '3' } ], @@ -62,11 +62,11 @@ function encyclopediaTool(baseURL: string, vectorSpaceId: string, token: string) explanation: 'Queries the encyclopedia for a topic. It produces up to 5 results. Its output is in JSON.', examples: [ { - input: 'query "Constantine III"', + input: '"Constantine III"', output: '[\n{id: 789, similarity: 0.9, attributes: {"text": "Constantine III (Latin: Flavius Claudius Constantinus; died shortly before 18 September 411) was a common Roman soldier who was declared emperor in Roman Britain in 407 and established himself in Gaul. He was recognised as co-emperor of the Roman Empire from 409 until 411.", "title": "Constantine III (Western Roman emperor)", "url": "https://en.wikipedia.org/wiki/Constantine_III_(Western_Roman_emperor)"}}\n]' }, { - input: 'query "Illuminati"', + input: '"Illuminati"', output: `[\n{id: 123, similarity: 0.9, attributes: {"text": "The Illuminati (plural of Latin illuminatus, 'enlightened') is a name given to several groups, both real and fictitious. Historically, the name usually refers to the Bavarian Illuminati, an Enlightenment-era secret society founded on 1 May 1776 in Bavaria, today part of Germany. The society's goals were to oppose superstition, obscurantism, religious influence over public life, and abuses of state power.", "url": "https://en.wikipedia.org/wiki/Illuminati"}}\n]` } ], From c07ffaca0d392fb8b4469bd89d3f1563ba792b9b Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 13:01:58 +0200 Subject: [PATCH 5/8] Drop direct text links, as they don't seem to be picked up. --- src/lib/tools/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts index 73ecdfc..a294b74 100644 --- a/src/lib/tools/index.ts +++ b/src/lib/tools/index.ts @@ -88,10 +88,7 @@ function encyclopediaTool(baseURL: string, vectorSpaceId: string, token: string) }) const json = await result.json() - const results = json.results.map(it => {return { - ...it, - url: `${it.attributes.url}#:~:text=${encodeURIComponent(it.attributes.text.substring(0, 25))}` - }}) + const results = json.results const output = codeBlock(JSON.stringify(results)) From 6f029b5c159457a5924cad10d461d8cec8dabc5c Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 16:31:37 +0200 Subject: [PATCH 6/8] Allow tool usage on regenerating answers and improve knowledge base tool --- src/lib/tools/index.ts | 15 +++-- src/routes/[conversationId]/+page.svelte | 83 ++++++++++++------------ 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts index a294b74..c5844bf 100644 --- a/src/lib/tools/index.ts +++ b/src/lib/tools/index.ts @@ -91,13 +91,20 @@ function encyclopediaTool(baseURL: string, vectorSpaceId: string, token: string) const results = json.results - const output = codeBlock(JSON.stringify(results)) + const output = codeBlock( + JSON.stringify(results, (k, v) => v.toFixed ? Number(v.toFixed(3)) : v + ), 'json'); return toolOutput( toolName, output, undefined, - "\n\nAnswer the question using only the relevant entries in the context. Do not make a judgement on the quality of the results. Cite your sources by providing the urls for all used results." + "\n\n---\n\n" + + "The JSON above is additional context information about the topic that is being discussed. \n"+ + "Answer the question using only the relevant entries in the context. " + + "Do not make a judgement on the quality of the results. " + + "Cite your sources by providing the urls for all used results." + + "You must answer in the same language as the original question, but you may use sources in different languages." ) } } @@ -130,7 +137,7 @@ Only then provide your actual answer formatted as JSON like this: method: "", // Your selected method's arguments arguments: ["", ""] -}`, 'json') + '\n\n' +}`, 'json') + '\n\n ToolBot always starts its output with `{\n"reason":"` and ends it with `]\n}`. \n\n' const instructions = tool.methods?.map((method: MethodSpec) => { const header = `## ${method.name} ${method.arguments.map(it => '<'+it.name+'>').join(' ')}\n` @@ -156,7 +163,7 @@ You output everything as JSON formatted like the this: toolName: "" } `, 'json') + ` -You output nothing else. Not even any reasoning. +ToolSelector always starts its output with \`{\n"reason":"\` and ends it with \`"\n}\`. \n\n --- diff --git a/src/routes/[conversationId]/+page.svelte b/src/routes/[conversationId]/+page.svelte index fbe339d..cd2d541 100644 --- a/src/routes/[conversationId]/+page.svelte +++ b/src/routes/[conversationId]/+page.svelte @@ -47,9 +47,8 @@ const response = await $currentBackend.sendMessage([ { role: 'system', - content: renderToolSelectionInstructions(tools) - }, - message + content: renderToolSelectionInstructions(tools) + "\n\n --- \n\n The User's query is: \n```" + message.content + "```" + } ]) let match = null; try { @@ -67,26 +66,22 @@ const toolResponse = await $currentBackend.sendMessage([ { role: 'system', - content: renderToolInstructions(tool) - }, - message + content: renderToolInstructions(tool)+ "\n\n --- \n\n The User's query is: ```" + message.content + "```" + } ]) - const matches = toolResponse.content.matchAll(/```(?:json)?((?:\s|.)+?)```/gm); - for (let match of matches) { - try { - const toolCall: ToolCall = JSON.parse(match[1]) - console.info("Tool Call:", toolCall.method, toolCall.arguments) - - const method = tool.methods?.find(it => it.name === toolCall.method) - if(method){ - return await method.exec(toolCall) - }else{ - console.warn("Selected method not in list of methods.", toolCall); - } - }catch (e) { - console.warn("Could not parse response from tool.", toolResponse); + try { + const toolCall: ToolCall = JSON.parse(toolResponse.content) + console.info("Tool Call:", toolCall.method, toolCall.arguments) + + const method = tool.methods?.find(it => it.name === toolCall.method) + if(method){ + return await method.exec(toolCall) + }else{ + console.warn("Selected method not in list of methods.", toolCall); } + }catch (e) { + console.warn("Could not parse response from tool.", toolResponse); } } toolFn.toolName = selectedTool; @@ -97,6 +92,23 @@ } } + function getHistory(){ + let history; + if (forkMessageId !== $currentConversation?.lastMessageId) { + const forkMessageIdx = $currentMessageThread.messages.findIndex( + (it) => it.self === forkMessageId + ); + history = $currentMessageThread.messages + .slice(0, forkMessageIdx + 1) + .map((msg) => $currentConversation?.messages[msg.self].message); + } else { + history = $currentMessageThread.messages.map( + (msg) => $currentConversation?.messages[msg.self].message + ); + } + return history; + } + async function sendMessageToChat() { waiting = true; let message: Message; @@ -111,7 +123,15 @@ forkMessageId = $currentConversation?.lastMessageId; inputText = ''; - const tool = await chooseTool(message); + if(!autoSend) { + waiting = false; + return; + } + } + + let history = getHistory(); + if(history.length > 0 && history[history.length - 1].role === 'user'){ + const tool = await chooseTool(history[history.length - 1]); if(tool){ const toolOutputMessage = await tool(); if(toolOutputMessage){ @@ -119,26 +139,7 @@ } } forkMessageId = $currentConversation?.lastMessageId; - - if(!autoSend) { - waiting = false; - return; - } - } - - - let history; - if (forkMessageId !== $currentConversation?.lastMessageId) { - const forkMessageIdx = $currentMessageThread.messages.findIndex( - (it) => it.self === forkMessageId - ); - history = $currentMessageThread.messages - .slice(0, forkMessageIdx + 1) - .map((msg) => $currentConversation?.messages[msg.self].message); - } else { - history = $currentMessageThread.messages.map( - (msg) => $currentConversation?.messages[msg.self].message - ); + history = getHistory(); } const source = { backend: $currentBackend.name, model: $currentBackend.model }; From 6f8c0cfcc549f296a41efab956b42ea5787178bf Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 16:43:07 +0200 Subject: [PATCH 7/8] Improve Knowledge Base Tool --- src/lib/tools/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts index c5844bf..cad9b71 100644 --- a/src/lib/tools/index.ts +++ b/src/lib/tools/index.ts @@ -103,8 +103,10 @@ function encyclopediaTool(baseURL: string, vectorSpaceId: string, token: string) "The JSON above is additional context information about the topic that is being discussed. \n"+ "Answer the question using only the relevant entries in the context. " + "Do not make a judgement on the quality of the results. " + - "Cite your sources by providing the urls for all used results." + + "You must use only the information provided in the context. If it doesn't answer the question say only 'I do not know.'" + + "You must cite your sources by providing the urls and similarity for every entry you used from the context." + "You must answer in the same language as the original question, but you may use sources in different languages." + ) } } From 7a96790415b5b92e00463406230f24f733f7e1db Mon Sep 17 00:00:00 2001 From: Paul Dubs Date: Wed, 3 May 2023 16:57:03 +0200 Subject: [PATCH 8/8] Improve Knowledge Base Tool --- src/lib/tools/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/tools/index.ts b/src/lib/tools/index.ts index cad9b71..1734d92 100644 --- a/src/lib/tools/index.ts +++ b/src/lib/tools/index.ts @@ -103,7 +103,7 @@ function encyclopediaTool(baseURL: string, vectorSpaceId: string, token: string) "The JSON above is additional context information about the topic that is being discussed. \n"+ "Answer the question using only the relevant entries in the context. " + "Do not make a judgement on the quality of the results. " + - "You must use only the information provided in the context. If it doesn't answer the question say only 'I do not know.'" + + "You must use only the information provided in the context. If it doesn't answer the question, you say that you don't know." + "You must cite your sources by providing the urls and similarity for every entry you used from the context." + "You must answer in the same language as the original question, but you may use sources in different languages." @@ -129,8 +129,8 @@ export function renderToolInstructions(tool: ToolSpec): string { const basePrompt = `You are an AI assistant called "ToolBot". ToolBot is an intelligent chatbot that is designed to use the tool it is provided. ToolBot understands the given tool descriptions and APIs. -First, ToolBot thinks step by step and provide a reasoning for what you think would be the method to use. -Only then provide your actual answer formatted as JSON like this: +Toolbot must choose a method and its arguments from the given tool. +ToolBot provides the chosen method and its step-by-step reasons formatted as JSON like this: ` + codeBlock( `{ // Think step by step and provide a reasoning for what you think would be the best method to use.