Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
5094f21
(improvement) sdk: dynamic tools integration
mialso Mar 23, 2026
5f5ce14
(chore) sdk: rename history append, improve tests
mialso Mar 23, 2026
a26efdd
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Mar 24, 2026
a5f3d06
Merge branch 'main' into improvement/sdk-dynamic-tools-interface-support
mialso Mar 24, 2026
2a10f45
(chore) sdk: update bun.lock, example sdk import
mialso Mar 24, 2026
0d3a650
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Mar 24, 2026
d4d5a9f
(chore) sdk: tools mode constants and description
mialso Mar 26, 2026
94f57ba
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Mar 26, 2026
a3e4059
(chore) sdk: cleanup naming
mialso Mar 26, 2026
59aba04
(chore) sdk: llamacpp config const reuse
mialso Mar 26, 2026
b8c7136
(chore) sdk: semicolon
mialso Mar 26, 2026
7eb0828
(internal) sdk: upgrade embed-llamacpp
mialso Mar 27, 2026
8785d46
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Mar 27, 2026
a720a33
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Mar 31, 2026
8fa7156
(internal) sdk: tool test dynamic mode improved
mialso Mar 31, 2026
06e97a4
(fix) sdk: dynamic tools assistant msg back
mialso Mar 31, 2026
15c4491
(chore) sdk: missing semicolon
mialso Mar 31, 2026
f0ac901
(chore) sdk: naming
mialso Mar 31, 2026
474e6e6
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Mar 31, 2026
b1a08b7
feat: anchored tools cache optimization and agentic example
olyasir Apr 1, 2026
c01b702
fix: send all consecutive tool responses during chain continuation
olyasir Apr 2, 2026
8b11f50
fix: stabilize long chain test scenario
olyasir Apr 2, 2026
a0cf11a
feat: add stats assertions to agentic-tools example
olyasir Apr 2, 2026
2baa19e
doc: document dynamic tools contract between app and SDK
olyasir Apr 2, 2026
858819b
test: add context sliding during chain scenario
olyasir Apr 2, 2026
2a7a5d3
test: stricter sliding assertion catches missing adjustAfterSlide
olyasir Apr 2, 2026
6c2344d
(internal) sdk: server remove unused logic
mialso Apr 3, 2026
844786e
(fix) sdk: assistant msg back rm incorrect
mialso Apr 3, 2026
4fe44a4
(internal) sdk: improve examples
mialso Apr 3, 2026
a8fd16a
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 3, 2026
09d9e1d
(chore) sdk: code style
mialso Apr 3, 2026
2ca2ce6
fix: use Boolean coercion for toolsTrimmed stat
olyasir Apr 6, 2026
4576665
(internal) sdk: adjust llm addon tools_compact interface
mialso Apr 7, 2026
de097f8
Revert "(internal) sdk: adjust llm addon tools_compact interface"
mialso Apr 7, 2026
e1e9d04
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 7, 2026
2840f4d
(fix) sdk: correct llamacpp-llm api usage
mialso Apr 7, 2026
3984f17
(draft) sdk: compact tools impr examples
mialso Apr 15, 2026
c5590a1
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 15, 2026
b30e062
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 16, 2026
4353e4f
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 21, 2026
a533c11
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 22, 2026
62931a3
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 22, 2026
ba5050a
(internal) sdk: migrate load and tool parse apis
mialso Apr 23, 2026
c970b30
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 23, 2026
a3d6efd
(chore) sdk: debugs linter off
mialso Apr 24, 2026
ce32c9f
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 24, 2026
a55fc18
(internal) sdk: debug stats handling
mialso Apr 24, 2026
a489553
Merge remote-tracking branch 'origin/main' into improvement/sdk-dynam…
mialso Apr 24, 2026
06d5e0f
(chore) sdk: native tools example no cache
mialso Apr 24, 2026
1667ce0
(internal) sdk: add bun-lock, native tools without dynamic
mialso Apr 24, 2026
514f13a
Merge branch 'main' into improvement/sdk-dynamic-tools-interface-support
olyasir Apr 27, 2026
710fb87
Merge remote-tracking branch 'upstream/main' into QVAC-13559-sdk-dyna…
Apr 28, 2026
7cb6483
chore: drop debug-stats wiring and consolidate to single example
Apr 28, 2026
d7c5c33
fix: tests-qvac typecheck for tools tests
Apr 28, 2026
61dccf4
fix: restore tools_mode to tools_compact addon translation
Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ bun run examples/path/to/example.ts
- `llama.cpp` with P2P registry: [`examples/llamacpp-p2p.ts`](examples/llamacpp-p2p.ts)
- `llama.cpp` with HTTP: [`examples/llamacpp-http.ts`](examples/llamacpp-http.ts)
- `llama.cpp` with tools/function calls: [`examples/llamacpp-native-tools.ts`](examples/llamacpp-native-tools.ts)
- `llama.cpp` with tools/function calls (dynamic mode): [`examples/llamacpp-dynamic-tools.ts`](examples/llamacpp-dynamic-tools.ts)
- `llama.cpp` with multimodal inference: [`examples/llamacpp-multimodal.ts`](examples/llamacpp-multimodal.ts)
- `llama.cpp` with KV cache: [`examples/kv-cache-example.ts`](examples/kv-cache-example.ts)

Expand Down
6 changes: 3 additions & 3 deletions packages/sdk/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

208 changes: 208 additions & 0 deletions packages/sdk/examples/llamacpp-dynamic-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { z } from "zod";
import {
completion,
loadModel,
unloadModel,
type ToolInput,
type ToolCall,
type CompletionStats,
type CompletionParams,
QWEN3_1_7B_INST_Q4,
} from "@qvac/sdk";

// Define Zod schemas for tool parameters
const weatherSchema = z.object({
city: z.string().describe("City name"),
});

const horoscopeSchema = z.object({
sign: z.string().describe("An astrological sign like Taurus or Aquarius"),
});

// Map tool names to their schemas for runtime validation
const toolSchemas = {
get_weather: weatherSchema,
get_horoscope: horoscopeSchema,
};

// Simple tool definitions - just name, description, and Zod schema!
const tools1 = [
{
name: "get_weather",
description: "Get current weather for a city",
parameters: weatherSchema,
},
];

const tools2 = [
{
name: "get_horoscope",
description: "Get today's horoscope for an astrological sign",
parameters: horoscopeSchema,
},
];

type ChatSesssionParam = CompletionParams & {
tools: ToolInput[]
}
async function chatSession ({ modelId, history, tools, kvCache }: ChatSesssionParam) {
const result = completion({ modelId, history, kvCache, stream: true, tools });

// Consume token stream
const tokensTask = (async () => {
for await (const token of result.tokenStream) {
process.stdout.write(token);
}
})();

// Consume tool call events
const toolsTask = (async () => {
for await (const evt of result.toolCallStream) {
if (evt.type === "toolCall") {
console.log(
`\n\n→ Tool Call Detected: ${evt.call.name}(${JSON.stringify(evt.call.arguments)})`,
);
console.log(` ID: ${evt.call.id}`);
} else if (evt.type === "toolCallError") {
console.warn(`\n⚠️ Tool Error: ${evt.error.message}`);
console.warn(` Code: ${evt.error.code}`);
}
}
})();

await Promise.all([tokensTask, toolsTask]);

const stats: CompletionStats | undefined = await result.stats;
const toolCalls: ToolCall[] = await result.toolCalls;

console.log("\n\n📋 Parsed Tool Calls:");
if (toolCalls.length > 0) {
for (const call of toolCalls) {
console.log(` - ${call.name}(${JSON.stringify(call.arguments)})`);

const schema = toolSchemas[call.name as keyof typeof toolSchemas];
if (schema) {
const validated = schema.safeParse(call.arguments);
if (validated.success) {
console.log(` ✓ Arguments validated with Zod`);
} else {
console.log(` ✗ Validation failed:`, validated.error);
}
}
}
} else {
console.log(" No tool calls detected in response");
}

console.log("\n📊 Performance Stats:", stats);

// Execute tool calls and send results back to the model
if (toolCalls.length > 0) {
console.log("\n\n🔧 Simulating Tool Execution...");

// Simulate tool execution (in a real app, you'd call actual APIs)
const toolResults = toolCalls.map((call) => {
let result = "";
if (call.name === "get_weather") {
const args = call.arguments as { city: string; country?: string };
result = `The weather in ${args.city} is sunny, 22°C with light clouds.`;
} else if (call.name === "get_horoscope") {
const args = call.arguments as { sign: string };
result = `Horoscope for ${args.sign}: Today is a great day for new beginnings and creative endeavors!`;
}
console.log(` ✓ ${call.name}: ${result}`);
return { toolCallId: call.id, result };
});

// Add tool results to conversation history
history.push({
role: "assistant",
content: await result.text,
});

// Add tool results as tool messages
for (const toolResult of toolResults) {
history.push({
role: "tool",
content: toolResult.result,
});
}
}

// Send follow-up question with tool results
console.log("\n\n🤖 Follow-up Response with Tool Results:");
const followUpResult = completion({
modelId,
history,
stream: true,
kvCache,
tools,
});

history.push({
role: "assistant",
content: await followUpResult.text,
});

for await (const token of followUpResult.tokenStream) {
process.stdout.write(token);
}


const followUpStats = await followUpResult.stats;
console.log("\n\n📊 Follow-up Stats:", followUpStats);
}

type ToolInvocationParam = Pick<CompletionParams, 'kvCache'> & {
toolVariants: [ToolInput[], ToolInput[]]
}
async function runToolInvocationTest({ kvCache, toolVariants }: ToolInvocationParam) {
try {
// Load model from provided file path with tools support enabled
const modelId = await loadModel({
modelSrc: QWEN3_1_7B_INST_Q4,
modelType: "llm",
modelConfig: {
ctx_size: 4096,
tools: true, // Enable tools support
toolsMode: 'dynamic',
},
onProgress: (progress) =>
console.log(`Loading: ${progress.percentage.toFixed(1)}%`),
});
console.log(`✅ Model loaded successfully! Model ID: ${modelId}`);

// Create conversation history
const history = [
{
role: "system",
content:"You are a helpful assistant that can use tools.",
},
{
role: "user",
content: "What's the weather in Tokyo?",
},
];

console.log("\n🤖 AI Response:");
console.log("(Streaming with tool definitions in prompt)\n");

await chatSession({ modelId, history, tools: toolVariants[0], kvCache })

history.push({
role: "user",
content: "only in case the weather in Tokyo is good, check my horoscope for Aquarius; if the weather is bad - check Taurus; need only one horoscope depending on the whether",
})

await chatSession({ modelId, history, tools: toolVariants[1], kvCache })


console.log("\n\n🎉 Completed!");
await unloadModel({ modelId, clearStorage: false });
} catch (error) {
console.error("❌ Error:", error);
process.exit(1);
}
}
// using same kvCache for a single session
await runToolInvocationTest({ kvCache: `id-${Date.now()}`, toolVariants: [tools1, tools2] })
2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
"@qvac/embed-llamacpp": "^0.12.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to bump to 0.13.0, to use the same version of llama.cpp as the llm-addon

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gianni-cor but dynamic tools feature PR has a bump to 0.14.0 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, ok sorry mixed the package - upgraded "@qvac/embed-llamacpp": "^0.13.0", ✔️

"@qvac/error": "^0.1.1",
"@qvac/langdetect-text-cld2": "0.3.0",
"@qvac/llm-llamacpp": "^0.12.1",
"@qvac/llm-llamacpp": "^0.14.0",
"@qvac/logging": "^0.1.0",
"@qvac/ocr-onnx": "^0.2.0",
"@qvac/rag": "^0.4.2",
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/schemas/llamacpp-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const llmConfigBaseSchema = z.object({
stop_sequences: z.array(z.string()).optional(),
n_discarded: z.number().optional(),
tools: z.boolean().optional(),
toolsMode: z.enum(["static", "dynamic"]).optional(),
projectionModelSrc: modelSrcInputSchema.optional(),
});

Expand All @@ -52,6 +53,7 @@ export const LLM_CONFIG_DEFAULTS = {
gpu_layers: 99,
device: "gpu",
system_prompt: "You are a helpful assistant.",
toolsMode: "static",
} as const satisfies Partial<LlmConfigInput>;

// Full schema - applies defaults via transform (no duplication)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
} from "@/server/bare/registry/model-registry";
import {
checkForToolEvents,
insertToolsIntoHistory,
appendToolsToHistory,
prependToolsToHistory,
setupToolGrammar,
} from "@/server/utils/tool-integration";
import { parseToolCalls } from "@/server/utils/tool-parser";
Expand Down Expand Up @@ -161,13 +162,16 @@ function prepareMessagesForCache(
content: string;
attachments?: { path: string }[] | undefined;
}[],
tools?: Tool[],
): ChatHistory[] {
const addTools = tools?.length ? transformMessages(tools) : [];
if (cacheExists && history.length > 0) {
const lastMessage = history[history.length - 1];
const lastTransformedMessages = transformMessage(lastMessage!);
return [
{ role: "session", content: cachePathToUse },
...lastTransformedMessages,
...addTools,
];
}

Expand All @@ -178,6 +182,7 @@ function prepareMessagesForCache(
return [
{ role: "session", content: cachePathToUse },
...transformedHistoryWithoutSystem,
...addTools,
];
}

Expand Down Expand Up @@ -257,6 +262,7 @@ export async function* completion(

const modelConfig = getModelConfig(modelId);
const toolsEnabled = (modelConfig as { tools?: boolean }).tools === true;
const toolsMode = (modelConfig as { toolsMode?: string }).toolsMode;

let historyWithTools: Array<
| {
Expand All @@ -268,7 +274,11 @@ export async function* completion(
> = history;

if (tools && tools.length > 0 && toolsEnabled) {
historyWithTools = insertToolsIntoHistory(history, tools);
if (toolsMode === "dynamic") {
historyWithTools = appendToolsToHistory(history, tools);
} else {
historyWithTools = prependToolsToHistory(history, tools);
}
Comment thread
lauripiisang marked this conversation as resolved.
Outdated
setupToolGrammar(modelConfig as Record<string, unknown>, tools);
}

Expand All @@ -278,7 +288,13 @@ export async function* completion(
if (kvCache) {
const modelConfig = getModelConfig(modelId);
const systemPromptFromHistory = extractSystemPrompt(history);
const configHash = generateConfigHash(systemPromptFromHistory, tools);
const toolsModeForHash = (modelConfig as { toolsMode?: string }).toolsMode;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this the same than the toolsMode constant in the outer scope?

@mialso mialso Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simon-iribarren yes, I but I've decided to follow current logic since there are 2 model configs right now and kvCache "branch" has it's own, like

  const modelConfig = getModelConfig(modelId);
  // <...>
  if (kvCache) {
    const modelConfig = getModelConfig(modelId);
    // at this point using toolsMode from the "second" config

hence this is just for consistency - otherwise prob we should refactor to have a single modelConfig?

const systemTools = toolsMode !== "dynamic" && tools?.length && toolsEnabled;
const dynamicTools = toolsMode === "dynamic" && tools?.length && toolsEnabled;
const configHash = generateConfigHash(
systemPromptFromHistory,
toolsModeForHash !== "dynamic" ? tools : undefined,
);

const systemPromptToUse =
systemPromptFromHistory ||
Expand All @@ -298,7 +314,7 @@ export async function* completion(
cachePathToUse,
systemPromptToUse,
kvCache,
tools && toolsEnabled ? tools : undefined,
systemTools ? tools : undefined,
);
markCacheInitialized(modelId, configHash, kvCache);
cacheExists = true;
Expand All @@ -308,6 +324,7 @@ export async function* completion(
cachePathToUse,
cacheExists,
history,
dynamicTools ? tools : undefined,
);
logMessagesToAddon(messagesToSend, "PROMPT_SEND");

Expand Down Expand Up @@ -345,7 +362,7 @@ export async function* completion(
cachePathToUse,
systemPromptToUse,
"auto",
tools && toolsEnabled ? tools : undefined,
systemTools ? tools : undefined,
);
markCacheInitialized(modelId, configHash, currentCacheInfo.cacheKey);
cacheExists = true;
Expand All @@ -355,6 +372,7 @@ export async function* completion(
cachePathToUse,
cacheExists,
history,
dynamicTools ? tools : undefined,
);
logMessagesToAddon(messagesToSend, "PROMPT_SEND");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ function transformLlmConfig(llmConfig: LlmConfig) {
delete transformed["stop_sequences"];
}

if ("tools_mode" in transformed) {
transformed["tools_at_end"] = transformed["tools_mode"] === "dynamic" ? "true" : "false";
delete transformed["tools_mode"];
}

return transformed;
}

Expand Down
Loading
Loading