Add LLM tool calling for creating tasks, notes, and searching from chat
Ollama tool/function calling integration allows the LLM to create tasks, create notes, and search existing notes on behalf of the user during chat. Multi-round tool loop (max 5 rounds) lets the model execute tools then produce a natural language response. Tool results are persisted in a new JSONB column on messages and rendered as compact cards with linked titles. - Migration 0013: add tool_calls JSONB column to messages - New services/tools.py: tool definitions + execute_tool dispatcher - llm.py: ChatChunk dataclass, stream_chat_with_tools(), date in system prompt - generation_task.py: multi-round tool call loop with SSE tool_call events - Frontend: ToolCallRecord type, streamingToolCalls in store, ToolCallCard component, rendering in ChatMessage and ChatView streaming bubble Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import type {
|
||||
OllamaStatus,
|
||||
RunningModel,
|
||||
SendMessageResponse,
|
||||
ToolCallRecord,
|
||||
} from "@/types/chat";
|
||||
|
||||
export const useChatStore = defineStore("chat", () => {
|
||||
@@ -26,6 +27,7 @@ export const useChatStore = defineStore("chat", () => {
|
||||
const loading = ref(false);
|
||||
const streaming = ref(false);
|
||||
const streamingContent = ref("");
|
||||
const streamingToolCalls = ref<ToolCallRecord[]>([]);
|
||||
const lastContextMeta = ref<ContextMeta | null>(null);
|
||||
const models = ref<OllamaModel[]>([]);
|
||||
const runningModels = ref<RunningModel[]>([]);
|
||||
@@ -187,6 +189,12 @@ export const useChatStore = defineStore("chat", () => {
|
||||
case "chunk":
|
||||
streamingContent.value += event.data.chunk as string;
|
||||
break;
|
||||
case "tool_call":
|
||||
streamingToolCalls.value = [
|
||||
...streamingToolCalls.value,
|
||||
event.data.tool_call as ToolCallRecord,
|
||||
];
|
||||
break;
|
||||
case "done":
|
||||
gotDone = true;
|
||||
{
|
||||
@@ -196,12 +204,16 @@ export const useChatStore = defineStore("chat", () => {
|
||||
role: "assistant",
|
||||
content: streamingContent.value,
|
||||
context_note_id: null,
|
||||
tool_calls: streamingToolCalls.value.length
|
||||
? [...streamingToolCalls.value]
|
||||
: null,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
if (currentConversation.value?.id === convId) {
|
||||
currentConversation.value.messages.push(assistantMsg);
|
||||
}
|
||||
streamingContent.value = "";
|
||||
streamingToolCalls.value = [];
|
||||
streaming.value = false;
|
||||
|
||||
// Update conversation in list
|
||||
@@ -216,6 +228,7 @@ export const useChatStore = defineStore("chat", () => {
|
||||
gotDone = true;
|
||||
streaming.value = false;
|
||||
streamingContent.value = "";
|
||||
streamingToolCalls.value = [];
|
||||
useToastStore().show(
|
||||
"Chat error: " + (event.data.error as string),
|
||||
"error",
|
||||
@@ -252,6 +265,7 @@ export const useChatStore = defineStore("chat", () => {
|
||||
|
||||
streaming.value = false;
|
||||
streamingContent.value = "";
|
||||
streamingToolCalls.value = [];
|
||||
}
|
||||
|
||||
async function cancelGeneration() {
|
||||
@@ -357,6 +371,7 @@ export const useChatStore = defineStore("chat", () => {
|
||||
loading,
|
||||
streaming,
|
||||
streamingContent,
|
||||
streamingToolCalls,
|
||||
lastContextMeta,
|
||||
models,
|
||||
runningModels,
|
||||
|
||||
Reference in New Issue
Block a user