Add dual-model support with separate chat and agent models

Split the single model setting into `chatModel` and `agentModel` to allow
using different LLMs for conversational modes (Ask, Research) versus
agentic modes (Edit, Organize, Workflow, auto-organizer). Defaults are
`deepseek-v4-flash` for chat and `glm-5.1` for agents.

Includes backward compatibility migration from legacy `model` field,
updated settings UI, per-mode tool filtering via new `agent-modes.ts`
configs, and vault search scoring improvements (exact phrase, recency,
filename bonuses).
This commit is contained in:
2026-05-21 09:00:11 +02:00
parent ae747a4470
commit 96f201bf3f
7 changed files with 3310 additions and 303 deletions
+53 -21
View File
@@ -67,12 +67,11 @@ export class ChatView extends ItemView {
this.listenersAttached = false;
this.settings = settings;
this.currentAgentMode = settings.agentMode ?? 'ask';
this.ollamaClient = new OllamaClient(
settings.ollamaUrl,
settings.model,
undefined,
settings.cacheConfig
);
this.ollamaClient = this.createOllamaClient(settings.chatModel ?? settings.model, settings);
this.agentOllamaClient =
(settings.agentModel ?? settings.model) === (settings.chatModel ?? settings.model)
? this.ollamaClient
: this.createOllamaClient(settings.agentModel ?? settings.model, settings);
this.vaultIndexer = new VaultIndexer(this.app.vault, undefined, vectorStore);
this.toolExecutor = new ToolExecutor(this.app.vault, this.app, telemetryManager);
this.actionPreviewBuilder = new ActionPreviewBuilder(this.app.vault, this.app);
@@ -84,7 +83,7 @@ export class ChatView extends ItemView {
this.app.vault,
this.app,
settings.ollamaUrl,
settings.model,
settings.agentModel ?? settings.model,
{ cacheConfig: settings.cacheConfig }
);
}
@@ -95,20 +94,22 @@ export class ChatView extends ItemView {
if (this.modeSelectorEl) {
this.modeSelectorEl.value = this.currentAgentMode;
}
this.ollamaClient = new OllamaClient(
newSettings.ollamaUrl,
newSettings.model,
undefined,
newSettings.cacheConfig
this.ollamaClient = this.createOllamaClient(
newSettings.chatModel ?? newSettings.model,
newSettings
);
this.agentOllamaClient =
(newSettings.agentModel ?? newSettings.model) === (newSettings.chatModel ?? newSettings.model)
? this.ollamaClient
: this.createOllamaClient(newSettings.agentModel ?? newSettings.model, newSettings);
this.workflowEngine = new WorkflowEngine(
this.app.vault,
this.app,
newSettings.ollamaUrl,
newSettings.model,
newSettings.agentModel ?? newSettings.model,
{ cacheConfig: newSettings.cacheConfig }
);
void this.ollamaClient.initializeCache().catch(() => {
void this.initializeClientCaches().catch(() => {
new Notice(
'Semantic cache unavailable: could not connect to ChromaDB. Check the ChromaDB URL in settings.'
);
@@ -122,6 +123,9 @@ export class ChatView extends ItemView {
async clearCache(): Promise<void> {
await this.ollamaClient.clearCache();
if (this.agentOllamaClient !== this.ollamaClient) {
await this.agentOllamaClient.clearCache();
}
}
getViewType(): string {
@@ -138,7 +142,7 @@ export class ChatView extends ItemView {
async onOpen(): Promise<void> {
try {
await this.ollamaClient.initializeCache();
await this.initializeClientCaches();
} catch {
new Notice(
'Semantic cache unavailable: could not connect to ChromaDB. Check the ChromaDB URL in settings.'
@@ -744,7 +748,7 @@ export class ChatView extends ItemView {
if (followUpMessages.length > 0) {
const finalMessages = [...messages, followUp, ...followUpMessages];
const followUpStartTime = Date.now();
const response = await this.ollamaClient.chat(finalMessages, tools);
const response = await this.getActiveOllamaClient().chat(finalMessages, tools);
const followUpDurationMs = Date.now() - followUpStartTime;
const finalResponse = response.content || fullResponse;
this.updateMessageById(assistantMessageId, {
@@ -755,7 +759,7 @@ export class ChatView extends ItemView {
// Record follow-up LLM call telemetry
this.telemetryManager?.recordLlmCall({
model: this.settings.model,
model: this.getActiveModel(),
promptTokens: Math.round(finalMessages.reduce((sum, m) => sum + m.content.length, 0) / 4),
completionTokens: Math.round(finalResponse.length / 4),
totalTokens: Math.round(
@@ -812,7 +816,7 @@ export class ChatView extends ItemView {
if (followUpMessages.length > 0) {
const finalMessages = [...messages, followUp, ...followUpMessages];
const followUpStartTime = Date.now();
const response = await this.ollamaClient.chat(finalMessages, tools);
const response = await this.getActiveOllamaClient().chat(finalMessages, tools);
const followUpDurationMs = Date.now() - followUpStartTime;
const finalResponse = response.content || 'Actions applied successfully.';
this.updateMessageById(assistantMessageId, {
@@ -823,7 +827,7 @@ export class ChatView extends ItemView {
// Record follow-up LLM call telemetry
this.telemetryManager?.recordLlmCall({
model: this.settings.model,
model: this.getActiveModel(),
promptTokens: Math.round(finalMessages.reduce((sum, m) => sum + m.content.length, 0) / 4),
completionTokens: Math.round(finalResponse.length / 4),
totalTokens: Math.round(
@@ -1090,7 +1094,9 @@ export class ChatView extends ItemView {
// Prepend structured memory as a system message if available
const messagesWithMemory = this.buildMessagesWithMemory(completeMessages);
const stream = this.ollamaClient.streamChat(messagesWithMemory, tools);
const activeClient = this.getActiveOllamaClient();
const activeModel = this.getActiveModel();
const stream = activeClient.streamChat(messagesWithMemory, tools);
let fullResponse = '';
let toolCalls: OllamaToolCall[] = [];
@@ -1131,7 +1137,7 @@ export class ChatView extends ItemView {
completionTokens > 0 ? completionTokens : fullResponse.length / 4;
this.telemetryManager?.recordLlmCall({
model: this.settings.model,
model: activeModel,
promptTokens: Math.round(estimatedPromptTokens),
completionTokens: Math.round(estimatedCompletionTokens),
totalTokens: Math.round(estimatedPromptTokens + estimatedCompletionTokens),
@@ -1232,6 +1238,7 @@ export class ChatView extends ItemView {
private listenersAttached: boolean = false;
private settings: PluginSettings;
private ollamaClient: OllamaClient;
private agentOllamaClient: OllamaClient;
private vaultIndexer: VaultIndexer;
private toolExecutor: ToolExecutor;
private actionPreviewBuilder: ActionPreviewBuilder;
@@ -1253,6 +1260,31 @@ export class ChatView extends ItemView {
tools: OllamaTool[];
assistantMessageId: string;
} | null = null;
private createOllamaClient(model: string, settings: PluginSettings): OllamaClient {
return new OllamaClient(settings.ollamaUrl, model, undefined, settings.cacheConfig);
}
private async initializeClientCaches(): Promise<void> {
await this.ollamaClient.initializeCache();
if (this.agentOllamaClient !== this.ollamaClient) {
await this.agentOllamaClient.initializeCache();
}
}
private getActiveModel(): string {
return this.isAgenticMode(this.currentAgentMode)
? (this.settings.agentModel ?? this.settings.model)
: (this.settings.chatModel ?? this.settings.model);
}
private getActiveOllamaClient(): OllamaClient {
return this.isAgenticMode(this.currentAgentMode) ? this.agentOllamaClient : this.ollamaClient;
}
private isAgenticMode(mode: AgentMode): boolean {
return mode === 'edit' || mode === 'organize' || mode === 'workflow';
}
}
const MAX_TOOL_CALLS = 5;