ai: separate model and tool selection

This commit is contained in:
end-4
2025-07-27 22:33:25 +07:00
parent d3392000af
commit 3ac44d211f
10 changed files with 192 additions and 232 deletions
@@ -61,6 +61,7 @@ Singleton {
property JsonObject ai: JsonObject { property JsonObject ai: JsonObject {
property string systemPrompt: "## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Presentation\n- Use Markdown features in your response: \n - **Bold** text to **highlight keywords** in your response\n - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n\n## Tools\nMay or may not be available depending on the user's settings. If they're available, follow these guidelines:\n\n### Search\n- When user asks for information that might benefit from up-to-date information, use this to get search access\n\n### Shell configuration\n- Always fetch the config options to see the available keys before setting\n- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it\n" property string systemPrompt: "## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Presentation\n- Use Markdown features in your response: \n - **Bold** text to **highlight keywords** in your response\n - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n\n## Tools\nMay or may not be available depending on the user's settings. If they're available, follow these guidelines:\n\n### Search\n- When user asks for information that might benefit from up-to-date information, use this to get search access\n\n### Shell configuration\n- Always fetch the config options to see the available keys before setting\n- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it\n"
property string tool: "functions" // search, functions, or none
} }
property JsonObject appearance: JsonObject { property JsonObject appearance: JsonObject {
@@ -45,6 +45,22 @@ Item {
Ai.setModel(args[0]); Ai.setModel(args[0]);
} }
}, },
{
name: "tool",
description: Translation.tr("Set the tool to use for the model."),
execute: (args) => {
// console.log(args)
if (args.length == 0 || args[0] == "get") {
Ai.addMessage(Translation.tr("Usage: %1tool TOOL_NAME").arg(root.commandPrefix), Ai.interfaceRole);
} else {
const tool = args[0];
const switched = Ai.setTool(tool);
if (switched) {
Ai.addMessage(Translation.tr("Tool set to %1").arg(tool), Ai.interfaceRole);
}
}
}
},
{ {
name: "prompt", name: "prompt",
description: Translation.tr("Set the system prompt for the model."), description: Translation.tr("Set the system prompt for the model."),
@@ -73,7 +89,7 @@ Item {
execute: (args) => { execute: (args) => {
const joinedArgs = args.join(" ") const joinedArgs = args.join(" ")
if (joinedArgs.trim().length == 0) { if (joinedArgs.trim().length == 0) {
Ai.addMessage(`Usage: ${root.commandPrefix}save CHAT_NAME`, Ai.interfaceRole); Ai.addMessage(Translation.tr("Usage: %1save CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole);
return; return;
} }
Ai.saveChat(joinedArgs) Ai.saveChat(joinedArgs)
@@ -85,7 +101,7 @@ Item {
execute: (args) => { execute: (args) => {
const joinedArgs = args.join(" ") const joinedArgs = args.join(" ")
if (joinedArgs.trim().length == 0) { if (joinedArgs.trim().length == 0) {
Ai.addMessage(`Usage: ${root.commandPrefix}load CHAT_NAME`, Ai.interfaceRole); Ai.addMessage(Translation.tr("Usage: %1load CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole);
return; return;
} }
Ai.loadChat(joinedArgs) Ai.loadChat(joinedArgs)
@@ -606,8 +622,9 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
property var commandsShown: [ property var commandsShown: [
{ {
name: "model", name: "",
sendDirectly: false, sendDirectly: false,
dontAddSpace: true,
}, },
{ {
name: "clear", name: "clear",
@@ -615,45 +632,25 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
}, },
] ]
Item { ApiInputBoxIndicator { // Model indicator
implicitHeight: providerRowLayout.implicitHeight + 5 * 2 icon: "api"
implicitWidth: providerRowLayout.implicitWidth + 10 * 2
RowLayout {
id: providerRowLayout
anchors.centerIn: parent
MaterialSymbol {
text: "api"
iconSize: Appearance.font.pixelSize.large
}
StyledText {
id: providerName
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.m3colors.m3onSurface
elide: Text.ElideRight
text: Ai.getModel().name text: Ai.getModel().name
} tooltipText: Translation.tr("Current model: %1\nSet it with %2model MODEL")
}
StyledToolTip {
id: toolTip
extraVisibleCondition: false
alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered
content: Translation.tr("Current model: %1\nSet it with %2model MODEL")
.arg(Ai.getModel().name) .arg(Ai.getModel().name)
.arg(root.commandPrefix) .arg(root.commandPrefix)
} }
MouseArea { ApiInputBoxIndicator { // Tool indicator
id: mouseArea icon: "service_toolbox"
anchors.fill: parent text: Ai.currentTool.charAt(0).toUpperCase() + Ai.currentTool.slice(1)
hoverEnabled: true tooltipText: Translation.tr("Current tool: %1\nSet it with %2tool TOOL")
} .arg(Ai.currentTool)
.arg(root.commandPrefix)
} }
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
ButtonGroup { ButtonGroup { // Command buttons
padding: 0 padding: 0
Repeater { // Command buttons Repeater { // Command buttons
@@ -665,7 +662,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
if(modelData.sendDirectly) { if(modelData.sendDirectly) {
root.handleInput(commandRepresentation) root.handleInput(commandRepresentation)
} else { } else {
messageInputField.text = commandRepresentation + " " messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? "" : " ")
messageInputField.cursorPosition = messageInputField.text.length messageInputField.cursorPosition = messageInputField.text.length
messageInputField.forceActiveFocus() messageInputField.forceActiveFocus()
} }
@@ -492,42 +492,14 @@ Item {
}, },
] ]
Item { ApiInputBoxIndicator { // Tool indicator
implicitHeight: providerRowLayout.implicitHeight + 5 * 2 icon: "api"
implicitWidth: providerRowLayout.implicitWidth + 10 * 2
RowLayout {
id: providerRowLayout
anchors.centerIn: parent
MaterialSymbol {
text: "api"
iconSize: Appearance.font.pixelSize.large
}
StyledText {
id: providerName
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.m3colors.m3onSurface
text: Booru.providers[Booru.currentProvider].name text: Booru.providers[Booru.currentProvider].name
} tooltipText: Translation.tr("Current API endpoint: %1\nSet it with %2mode PROVIDER")
}
StyledToolTip {
id: toolTip
extraVisibleCondition: false
alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered
// content: Translation.tr("The current API used. Endpoint: ") + Booru.providers[Booru.currentProvider].url + Translation.tr("\nSet with /mode PROVIDER")
content: Translation.tr("Current API endpoint: %1\nSet it with %2mode PROVIDER")
.arg(Booru.providers[Booru.currentProvider].url) .arg(Booru.providers[Booru.currentProvider].url)
.arg(root.commandPrefix) .arg(root.commandPrefix)
} }
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
}
}
StyledText { StyledText {
font.pixelSize: Appearance.font.pixelSize.large font.pixelSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer1 color: Appearance.colors.colOnLayer1
@@ -1,6 +1,5 @@
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.services
import QtQuick import QtQuick
GroupButton { GroupButton {
@@ -0,0 +1,47 @@
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Layouts
Item { // Model indicator
id: root
property string icon: "api"
property string text: ""
property string tooltipText: ""
implicitHeight: rowLayout.implicitHeight + 5 * 2
implicitWidth: rowLayout.implicitWidth + 10 * 2
RowLayout {
id: rowLayout
anchors.centerIn: parent
MaterialSymbol {
text: root.icon
iconSize: Appearance.font.pixelSize.normal
}
StyledText {
id: providerName
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.m3colors.m3onSurface
elide: Text.ElideRight
text: root.text
}
}
Loader {
active: root.tooltipText?.length > 0
anchors.fill: parent
sourceComponent: MouseArea {
id: mouseArea
hoverEnabled: true
StyledToolTip {
id: toolTip
extraVisibleCondition: false
alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered
content: root.tooltipText
}
}
}
}
+34 -88
View File
@@ -62,8 +62,10 @@ Singleton {
// Gemini: https://ai.google.dev/gemini-api/docs/function-calling // Gemini: https://ai.google.dev/gemini-api/docs/function-calling
// OpenAI: https://platform.openai.com/docs/guides/function-calling // OpenAI: https://platform.openai.com/docs/guides/function-calling
property string currentTool: Config?.options.ai.tool ?? "search"
property var tools: { property var tools: {
"gemini": [{"functionDeclarations": [ "gemini": {
"functions": [{"functionDeclarations": [
{ {
"name": "switch_to_search_mode", "name": "switch_to_search_mode",
"description": "Search the web", "description": "Search the web",
@@ -105,7 +107,13 @@ Singleton {
} }
}, },
]}], ]}],
"openai": [ "search": [{
"google_search": {}
}],
"none": []
},
"openai": {
"functions": [
{ {
"type": "function", "type": "function",
"name": "get_shell_config", "name": "get_shell_config",
@@ -131,7 +139,10 @@ Singleton {
"additionalProperties": false "additionalProperties": false
} }
} }
] ],
"search": [],
"none": [],
}
} }
// Model properties: // Model properties:
@@ -145,13 +156,12 @@ Singleton {
// - key_get_link: Link to get an API key // - key_get_link: Link to get an API key
// - key_get_description: Description of pricing and how to get an API key // - key_get_description: Description of pricing and how to get an API key
// - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai". // - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai".
// - tools: List of tools that the model can use. Each tool is an object with the tool name as the key and an empty object as the value.
// - extraParams: Extra parameters to be passed to the model. This is a JSON object. // - extraParams: Extra parameters to be passed to the model. This is a JSON object.
property var models: { property var models: {
"gemini-2.0-flash-search": aiModelComponent.createObject(this, { "gemini-2.0-flash": aiModelComponent.createObject(this, {
"name": "Gemini 2.0 Flash (Search)", "name": "Gemini 2.0 Flash",
"icon": "google-gemini-symbolic", "icon": "google-gemini-symbolic",
"description": Translation.tr("Online | Google's model\nGives up-to-date information with search."), "description": Translation.tr("Online | Google's model\nFast, can perform searches for up-to-date information"),
"homepage": "https://aistudio.google.com", "homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
"model": "gemini-2.0-flash", "model": "gemini-2.0-flash",
@@ -160,28 +170,11 @@ Singleton {
"key_get_link": "https://aistudio.google.com/app/apikey", "key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"), "key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini", "api_format": "gemini",
"tools": [{
"google_search": {}
}]
}), }),
"gemini-2.0-flash-tools": aiModelComponent.createObject(this, { "gemini-2.5-flash": aiModelComponent.createObject(this, {
"name": "Gemini 2.0 Flash (Tools)", "name": "Gemini 2.5 Flash",
"icon": "google-gemini-symbolic", "icon": "google-gemini-symbolic",
"description": Translation.tr("Experimental | Online | Google's model\nCan do a little more but takes an extra turn to perform search"), "description": Translation.tr("Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers"),
"homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
"model": "gemini-2.0-flash",
"requires_key": true,
"key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini",
"tools": root.tools["gemini"],
}),
"gemini-2.5-flash-search": aiModelComponent.createObject(this, {
"name": "Gemini 2.5 Flash (Search)",
"icon": "google-gemini-symbolic",
"description": Translation.tr("Online | Google's model\nGives up-to-date information with search."),
"homepage": "https://aistudio.google.com", "homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent",
"model": "gemini-2.5-flash", "model": "gemini-2.5-flash",
@@ -190,44 +183,11 @@ Singleton {
"key_get_link": "https://aistudio.google.com/app/apikey", "key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"), "key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini", "api_format": "gemini",
"tools": [{
"google_search": {}
}]
}),
"gemini-2.5-flash-tools": aiModelComponent.createObject(this, {
"name": "Gemini 2.5 Flash (Tools)",
"icon": "google-gemini-symbolic",
"description": Translation.tr("Experimental | Online | Google's model\nCan do a little more but takes an extra turn to perform search"),
"homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent",
"model": "gemini-2.5-flash",
"requires_key": true,
"key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini",
"tools": root.tools["gemini"],
}),
"gemini-2.5-flash-lite-search": aiModelComponent.createObject(this, {
"name": "Gemini 2.5 Flash-Lite (Search)",
"icon": "google-gemini-symbolic",
"description": Translation.tr("Experimental | Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput."),
"homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent",
"model": "gemini-2.5-flash-lite",
"requires_key": true,
"key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini",
"tools": [{
"google_search": {}
}]
}), }),
"gemini-2.5-flash-lite": aiModelComponent.createObject(this, { "gemini-2.5-flash-lite": aiModelComponent.createObject(this, {
"name": "Gemini 2.5 Flash-Lite", "name": "Gemini 2.5 Flash-Lite",
"icon": "google-gemini-symbolic", "icon": "google-gemini-symbolic",
"description": Translation.tr("Experimental | Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput."), "description": Translation.tr("Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput."),
"homepage": "https://aistudio.google.com", "homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent",
"model": "gemini-2.5-flash-lite", "model": "gemini-2.5-flash-lite",
@@ -236,19 +196,6 @@ Singleton {
"key_get_link": "https://aistudio.google.com/app/apikey", "key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"), "key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini", "api_format": "gemini",
"tools": root.tools["gemini"],
}),
"openrouter-llama4-maverick": aiModelComponent.createObject(this, {
"name": "Llama 4 Maverick",
"icon": "ollama-symbolic",
"description": Translation.tr("Online via %1 | %2's model").arg("OpenRouter").arg("Meta"),
"homepage": "https://openrouter.ai/meta-llama/llama-4-maverick:free",
"endpoint": "https://openrouter.ai/api/v1/chat/completions",
"model": "meta-llama/llama-4-maverick:free",
"requires_key": true,
"key_id": "openrouter",
"key_get_link": "https://openrouter.ai/settings/keys",
"key_get_description": Translation.tr("**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key"),
}), }),
"openrouter-deepseek-r1": aiModelComponent.createObject(this, { "openrouter-deepseek-r1": aiModelComponent.createObject(this, {
"name": "DeepSeek R1", "name": "DeepSeek R1",
@@ -453,6 +400,15 @@ Singleton {
} }
} }
function setTool(tool) {
if (!root.tools[models[currentModelId]?.api_format] || !(tool in root.tools[models[currentModelId]?.api_format])) {
root.addMessage(Translation.tr("Invalid tool. Supported tools:\n- %1").arg(Object.keys(root.tools[models[currentModelId]?.api_format]).join("\n- ")), root.interfaceRole);
return false;
}
Config.options.ai.tool = tool;
return true;
}
function getTemperature() { function getTemperature() {
return root.temperature; return root.temperature;
} }
@@ -535,7 +491,7 @@ Singleton {
const endpoint = root.currentApiStrategy.buildEndpoint(model); const endpoint = root.currentApiStrategy.buildEndpoint(model);
const messageArray = root.messageIDs.map(id => root.messageByID[id]); const messageArray = root.messageIDs.map(id => root.messageByID[id]);
const filteredMessageArray = messageArray.filter(message => message.role !== Ai.interfaceRole); const filteredMessageArray = messageArray.filter(message => message.role !== Ai.interfaceRole);
const data = root.currentApiStrategy.buildRequestData(model, filteredMessageArray, root.systemPrompt, root.temperature); const data = root.currentApiStrategy.buildRequestData(model, filteredMessageArray, root.systemPrompt, root.temperature, root.tools[model.api_format][root.currentTool]);
// console.log("[Ai] Request data: ", JSON.stringify(data, null, 2)); // console.log("[Ai] Request data: ", JSON.stringify(data, null, 2));
let requestHeaders = { let requestHeaders = {
@@ -580,9 +536,9 @@ Singleton {
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
// console.log("[Ai] Raw response line: ", data);
if (data.length === 0) return; if (data.length === 0) return;
if (requester.message.thinking) requester.message.thinking = false; if (requester.message.thinking) requester.message.thinking = false;
// console.log("[Ai] Raw response line: ", data);
// Handle response line // Handle response line
try { try {
@@ -696,18 +652,8 @@ Singleton {
function handleFunctionCall(name, args: var, message: AiMessageData) { function handleFunctionCall(name, args: var, message: AiMessageData) {
if (name === "switch_to_search_mode") { if (name === "switch_to_search_mode") {
const modelId = root.currentModelId; const modelId = root.currentModelId;
if (modelId.endsWith("-tools")) { root.currentTool = "search"
const searchModelId = modelId.replace(/-tools$/, "-search"); root.postResponseHook = () => { root.currentTool = "functions" }
if (root.modelList.indexOf(searchModelId) !== -1) {
root.setModel(searchModelId, false);
root.postResponseHook = () => root.setModel(modelId, false);
} else {
root.addMessage(Translation.tr("No corresponding search model found for %1").arg(modelId), Ai.interfaceRole);
}
} else {
root.addMessage(Translation.tr("Cannot switch to search mode from %1").arg(root.currentModelId), Ai.interfaceRole);
return;
}
addFunctionOutputMessage(name, Translation.tr("Switched to search mode. Continue with the user's request.")) addFunctionOutputMessage(name, Translation.tr("Switched to search mode. Continue with the user's request."))
requester.makeRequest(); requester.makeRequest();
} else if (name === "get_shell_config") { } else if (name === "get_shell_config") {
@@ -12,7 +12,6 @@ import QtQuick;
* - key_get_link: Link to get an API key * - key_get_link: Link to get an API key
* - key_get_description: Description of pricing and how to get an API key * - key_get_description: Description of pricing and how to get an API key
* - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai". * - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai".
* - tools: List of tools that the model can use.
* - extraParams: Extra parameters to be passed to the model. This is a JSON object. * - extraParams: Extra parameters to be passed to the model. This is a JSON object.
*/ */
@@ -2,7 +2,7 @@ import QtQuick
QtObject { QtObject {
function buildEndpoint(model: AiModel): string { throw new Error("Not implemented") } function buildEndpoint(model: AiModel): string { throw new Error("Not implemented") }
function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real) { throw new Error("Not implemented") } function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>) { throw new Error("Not implemented") }
function buildAuthorizationHeader(apiKeyEnvVarName: string): string { throw new Error("Not implemented") } function buildAuthorizationHeader(apiKeyEnvVarName: string): string { throw new Error("Not implemented") }
function parseResponseLine(line: string, message: AiMessageData) { throw new Error("Not implemented") } function parseResponseLine(line: string, message: AiMessageData) { throw new Error("Not implemented") }
function onRequestFinished(message: AiMessageData): var { return {} } // Default: no special handling function onRequestFinished(message: AiMessageData): var { return {} } // Default: no special handling
@@ -9,13 +9,12 @@ ApiStrategy {
return result; return result;
} }
function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real) { function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>) {
const tools = model.tools ?? [];
let baseData = { let baseData = {
"contents": messages.map(message => { "contents": messages.map(message => {
const geminiApiRoleName = (message.role === "assistant") ? "model" : message.role; const geminiApiRoleName = (message.role === "assistant") ? "model" : message.role;
const usingSearch = tools[0].google_search != undefined const usingSearch = tools[0]?.google_search !== undefined
if (!usingSearch && message.functionCall != undefined && message.functionCall.length > 0) { if (!usingSearch && message.functionCall != undefined && message.functionName.length > 0) {
return { return {
"role": geminiApiRoleName, "role": geminiApiRoleName,
"parts": [{ "parts": [{
@@ -25,7 +24,7 @@ ApiStrategy {
}] }]
} }
} }
if (!usingSearch && message.functionResponse != undefined && message.functionResponse.length > 0) { if (!usingSearch && message.functionResponse != undefined && message.functionName.length > 0) {
return { return {
"role": geminiApiRoleName, "role": geminiApiRoleName,
"parts": [{ "parts": [{
@@ -8,7 +8,7 @@ ApiStrategy {
return model.endpoint; return model.endpoint;
} }
function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real) { function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>) {
let baseData = { let baseData = {
"model": model.model, "model": model.model,
"messages": [ "messages": [