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:
@@ -110,7 +110,8 @@ Open **Settings → Ollama Settings** to configure the plugin.
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| Ollama URL | `http://localhost:11434` | Base URL of your Ollama instance |
|
||||
| Model | `llama3` | Model used for chat responses |
|
||||
| Chat Model | `deepseek-v4-flash` | Model used for normal chat, Ask mode, and Research mode |
|
||||
| Agent Model | `glm-5.1` | Model used for Edit, Organize, Workflow, and auto-organizer tasks |
|
||||
| **Default Agent Mode** | `Ask` | Default chat mode (Ask, Edit, Organize, Research, Workflow) |
|
||||
| Vault Search Limit | `5` | Maximum number of vault entries to include in context |
|
||||
| Max Context Length | `8000` | Maximum characters of vault content sent to the AI per message |
|
||||
|
||||
+53
-21
@@ -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;
|
||||
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
export const DEFAULT_SETTINGS = {
|
||||
ollamaUrl: 'http://localhost:11434',
|
||||
model: 'llama3',
|
||||
chatModel: 'deepseek-v4-flash',
|
||||
agentModel: 'glm-5.1',
|
||||
model: 'deepseek-v4-flash',
|
||||
vaultSearchLimit: 5,
|
||||
maxMessageHistory: 50,
|
||||
maxContextLength: 8000,
|
||||
|
||||
+22
-9
@@ -173,6 +173,10 @@ export default class OllamaPlugin extends Plugin {
|
||||
// Backward compatibility: old flat format vs new nested format
|
||||
const loadedSettings = (data.settings ?? data) as Partial<PluginSettings>;
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, loadedSettings);
|
||||
const legacyModel = loadedSettings.model ?? DEFAULT_SETTINGS.model;
|
||||
this.settings.chatModel = loadedSettings.chatModel ?? legacyModel;
|
||||
this.settings.agentModel = loadedSettings.agentModel ?? legacyModel;
|
||||
this.settings.model = this.settings.chatModel;
|
||||
|
||||
const memoryData: StructuredMemoryData =
|
||||
(data.structuredMemory as StructuredMemoryData | undefined) ??
|
||||
@@ -197,17 +201,13 @@ export default class OllamaPlugin extends Plugin {
|
||||
}
|
||||
|
||||
initializeAutoOrganizer(): void {
|
||||
if (!this.autoTagger) {
|
||||
this.autoTagger = new AutoTagger(
|
||||
this.app.vault,
|
||||
this.app,
|
||||
this.settings.ollamaUrl,
|
||||
this.settings.model,
|
||||
this.settings.agentModel,
|
||||
this.settings.autoTagConfig
|
||||
);
|
||||
} else {
|
||||
this.autoTagger.updateConfig(this.settings.autoTagConfig);
|
||||
}
|
||||
|
||||
if (!this.autoLinker) {
|
||||
const vaultIndexer = new VaultIndexer(this.app.vault, undefined, this.vaultVectorStore);
|
||||
@@ -448,12 +448,25 @@ class OllamaSettingTab extends PluginSettingTab {
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Model')
|
||||
.setDesc('Ollama model to use (default: llama3)')
|
||||
.setName('Chat Model')
|
||||
.setDesc('Model for normal chat, Ask mode, and Research mode (default: deepseek-v4-flash)')
|
||||
.addText((text) =>
|
||||
text.setValue(this.plugin.settings.model).onChange(async (value) => {
|
||||
this.plugin.settings.model = value;
|
||||
text.setValue(this.plugin.settings.chatModel).onChange(async (value) => {
|
||||
this.plugin.settings.chatModel = value.trim();
|
||||
this.plugin.settings.model = this.plugin.settings.chatModel;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.notifyChatViews();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Agent Model')
|
||||
.setDesc('Model for Edit, Organize, Workflow, and auto-organizer tasks (default: glm-5.1)')
|
||||
.addText((text) =>
|
||||
text.setValue(this.plugin.settings.agentModel).onChange(async (value) => {
|
||||
this.plugin.settings.agentModel = value.trim();
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.notifyChatViews();
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -314,6 +314,8 @@ export interface VaultIndexConfig {
|
||||
|
||||
export interface PluginSettings {
|
||||
ollamaUrl: string;
|
||||
chatModel: string;
|
||||
agentModel: string;
|
||||
model: string;
|
||||
vaultSearchLimit: number;
|
||||
maxMessageHistory: number;
|
||||
|
||||
@@ -33,6 +33,8 @@ jest.mock('obsidian', () => ({
|
||||
|
||||
const mockSettings: PluginSettings = {
|
||||
ollamaUrl: 'http://localhost:11434',
|
||||
chatModel: 'llama3',
|
||||
agentModel: 'llama3',
|
||||
model: 'llama3',
|
||||
vaultSearchLimit: 3,
|
||||
maxMessageHistory: 50,
|
||||
|
||||
Reference in New Issue
Block a user