Config graph model (#411)

* config to specify graph model
This commit is contained in:
arkml 2026-02-27 15:52:19 +05:30 committed by GitHub
parent cccb7a8a65
commit 9df1bb6765
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 166 additions and 86 deletions

View file

@ -57,14 +57,14 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({}) const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({})
const [modelsLoading, setModelsLoading] = useState(false) const [modelsLoading, setModelsLoading] = useState(false)
const [modelsError, setModelsError] = useState<string | null>(null) const [modelsError, setModelsError] = useState<string | null>(null)
const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string }>>({ const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>>({
openai: { apiKey: "", baseURL: "", model: "" }, openai: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
anthropic: { apiKey: "", baseURL: "", model: "" }, anthropic: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
google: { apiKey: "", baseURL: "", model: "" }, google: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
openrouter: { apiKey: "", baseURL: "", model: "" }, openrouter: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
aigateway: { apiKey: "", baseURL: "", model: "" }, aigateway: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "" }, ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "", knowledgeGraphModel: "" },
"openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "" }, "openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "", knowledgeGraphModel: "" },
}) })
const [testState, setTestState] = useState<{ status: "idle" | "testing" | "success" | "error"; error?: string }>({ const [testState, setTestState] = useState<{ status: "idle" | "testing" | "success" | "error"; error?: string }>({
status: "idle", status: "idle",
@ -87,7 +87,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
const [slackConnecting, setSlackConnecting] = useState(false) const [slackConnecting, setSlackConnecting] = useState(false)
const updateProviderConfig = useCallback( const updateProviderConfig = useCallback(
(provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string }>) => { (provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => {
setProviderConfigs(prev => ({ setProviderConfigs(prev => ({
...prev, ...prev,
[provider]: { ...prev[provider], ...updates }, [provider]: { ...prev[provider], ...updates },
@ -287,6 +287,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
const apiKey = activeConfig.apiKey.trim() || undefined const apiKey = activeConfig.apiKey.trim() || undefined
const baseURL = activeConfig.baseURL.trim() || undefined const baseURL = activeConfig.baseURL.trim() || undefined
const model = activeConfig.model.trim() const model = activeConfig.model.trim()
const knowledgeGraphModel = activeConfig.knowledgeGraphModel.trim() || undefined
const providerConfig = { const providerConfig = {
provider: { provider: {
flavor: llmProvider, flavor: llmProvider,
@ -294,6 +295,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
baseURL, baseURL,
}, },
model, model,
knowledgeGraphModel,
} }
const result = await window.ipc.invoke("models:test", providerConfig) const result = await window.ipc.invoke("models:test", providerConfig)
if (result.success) { if (result.success) {
@ -657,39 +659,74 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
)} )}
</div> </div>
<div className="space-y-2"> <div className="grid grid-cols-2 gap-3">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Model</span> <div className="space-y-2">
{modelsLoading ? ( <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Assistant model</span>
<div className="flex items-center gap-2 text-sm text-muted-foreground"> {modelsLoading ? (
<Loader2 className="size-4 animate-spin" /> <div className="flex items-center gap-2 text-sm text-muted-foreground">
Loading models... <Loader2 className="size-4 animate-spin" />
</div> Loading...
) : showModelInput ? ( </div>
<Input ) : showModelInput ? (
value={activeConfig.model} <Input
onChange={(e) => updateProviderConfig(llmProvider, { model: e.target.value })} value={activeConfig.model}
placeholder="Enter model" onChange={(e) => updateProviderConfig(llmProvider, { model: e.target.value })}
/> placeholder="Enter model"
) : ( />
<Select ) : (
value={activeConfig.model} <Select
onValueChange={(value) => updateProviderConfig(llmProvider, { model: value })} value={activeConfig.model}
> onValueChange={(value) => updateProviderConfig(llmProvider, { model: value })}
<SelectTrigger> >
<SelectValue placeholder="Select a model" /> <SelectTrigger>
</SelectTrigger> <SelectValue placeholder="Select a model" />
<SelectContent> </SelectTrigger>
{modelsForProvider.map((model) => ( <SelectContent>
<SelectItem key={model.id} value={model.id}> {modelsForProvider.map((model) => (
{model.name || model.id} <SelectItem key={model.id} value={model.id}>
</SelectItem> {model.name || model.id}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
)} </Select>
{modelsError && ( )}
<div className="text-xs text-destructive">{modelsError}</div> {modelsError && (
)} <div className="text-xs text-destructive">{modelsError}</div>
)}
</div>
<div className="space-y-2">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Knowledge graph model</span>
{modelsLoading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="size-4 animate-spin" />
Loading...
</div>
) : showModelInput ? (
<Input
value={activeConfig.knowledgeGraphModel}
onChange={(e) => updateProviderConfig(llmProvider, { knowledgeGraphModel: e.target.value })}
placeholder={activeConfig.model || "Enter model"}
/>
) : (
<Select
value={activeConfig.knowledgeGraphModel || "__same__"}
onValueChange={(value) => updateProviderConfig(llmProvider, { knowledgeGraphModel: value === "__same__" ? "" : value })}
>
<SelectTrigger>
<SelectValue placeholder="Select a model" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__same__">Same as assistant</SelectItem>
{modelsForProvider.map((model) => (
<SelectItem key={model.id} value={model.id}>
{model.name || model.id}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
</div> </div>
{showApiKey && ( {showApiKey && (

View file

@ -167,14 +167,14 @@ const defaultBaseURLs: Partial<Record<LlmProviderFlavor, string>> = {
function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) { function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
const [provider, setProvider] = useState<LlmProviderFlavor>("openai") const [provider, setProvider] = useState<LlmProviderFlavor>("openai")
const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string }>>({ const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>>({
openai: { apiKey: "", baseURL: "", model: "" }, openai: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
anthropic: { apiKey: "", baseURL: "", model: "" }, anthropic: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
google: { apiKey: "", baseURL: "", model: "" }, google: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
openrouter: { apiKey: "", baseURL: "", model: "" }, openrouter: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
aigateway: { apiKey: "", baseURL: "", model: "" }, aigateway: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "" }, ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "", knowledgeGraphModel: "" },
"openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "" }, "openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "", knowledgeGraphModel: "" },
}) })
const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({}) const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({})
const [modelsLoading, setModelsLoading] = useState(false) const [modelsLoading, setModelsLoading] = useState(false)
@ -199,7 +199,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
(!requiresBaseURL || activeConfig.baseURL.trim().length > 0) (!requiresBaseURL || activeConfig.baseURL.trim().length > 0)
const updateConfig = useCallback( const updateConfig = useCallback(
(prov: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string }>) => { (prov: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => {
setProviderConfigs(prev => ({ setProviderConfigs(prev => ({
...prev, ...prev,
[prov]: { ...prev[prov], ...updates }, [prov]: { ...prev[prov], ...updates },
@ -229,6 +229,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
apiKey: parsed.provider.apiKey || "", apiKey: parsed.provider.apiKey || "",
baseURL: parsed.provider.baseURL || (defaultBaseURLs[flavor] || ""), baseURL: parsed.provider.baseURL || (defaultBaseURLs[flavor] || ""),
model: parsed.model, model: parsed.model,
knowledgeGraphModel: parsed.knowledgeGraphModel || "",
}, },
})) }))
} }
@ -296,6 +297,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
baseURL: activeConfig.baseURL.trim() || undefined, baseURL: activeConfig.baseURL.trim() || undefined,
}, },
model: activeConfig.model.trim(), model: activeConfig.model.trim(),
knowledgeGraphModel: activeConfig.knowledgeGraphModel.trim() || undefined,
} }
const result = await window.ipc.invoke("models:test", providerConfig) const result = await window.ipc.invoke("models:test", providerConfig)
if (result.success) { if (result.success) {
@ -362,40 +364,75 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
)} )}
</div> </div>
{/* Model selection */} {/* Model selection - side by side */}
<div className="space-y-2"> <div className="grid grid-cols-2 gap-3">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Model</span> <div className="space-y-2">
{modelsLoading ? ( <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Assistant model</span>
<div className="flex items-center gap-2 text-sm text-muted-foreground"> {modelsLoading ? (
<Loader2 className="size-4 animate-spin" /> <div className="flex items-center gap-2 text-sm text-muted-foreground">
Loading models... <Loader2 className="size-4 animate-spin" />
</div> Loading...
) : showModelInput ? ( </div>
<Input ) : showModelInput ? (
value={activeConfig.model} <Input
onChange={(e) => updateConfig(provider, { model: e.target.value })} value={activeConfig.model}
placeholder="Enter model" onChange={(e) => updateConfig(provider, { model: e.target.value })}
/> placeholder="Enter model"
) : ( />
<Select ) : (
value={activeConfig.model} <Select
onValueChange={(value) => updateConfig(provider, { model: value })} value={activeConfig.model}
> onValueChange={(value) => updateConfig(provider, { model: value })}
<SelectTrigger> >
<SelectValue placeholder="Select a model" /> <SelectTrigger>
</SelectTrigger> <SelectValue placeholder="Select a model" />
<SelectContent> </SelectTrigger>
{modelsForProvider.map((model) => ( <SelectContent>
<SelectItem key={model.id} value={model.id}> {modelsForProvider.map((model) => (
{model.name || model.id} <SelectItem key={model.id} value={model.id}>
</SelectItem> {model.name || model.id}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
)} </Select>
{modelsError && ( )}
<div className="text-xs text-destructive">{modelsError}</div> {modelsError && (
)} <div className="text-xs text-destructive">{modelsError}</div>
)}
</div>
<div className="space-y-2">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Knowledge graph model</span>
{modelsLoading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="size-4 animate-spin" />
Loading...
</div>
) : showModelInput ? (
<Input
value={activeConfig.knowledgeGraphModel}
onChange={(e) => updateConfig(provider, { knowledgeGraphModel: e.target.value })}
placeholder={activeConfig.model || "Enter model"}
/>
) : (
<Select
value={activeConfig.knowledgeGraphModel || "__same__"}
onValueChange={(value) => updateConfig(provider, { knowledgeGraphModel: value === "__same__" ? "" : value })}
>
<SelectTrigger>
<SelectValue placeholder="Select a model" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__same__">Same as assistant</SelectItem>
{modelsForProvider.map((model) => (
<SelectItem key={model.id} value={model.id}>
{model.name || model.id}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
</div> </div>
{/* API Key */} {/* API Key */}

View file

@ -706,7 +706,12 @@ export async function* streamAgent({
// set up provider + model // set up provider + model
const provider = createProvider(modelConfig.provider); const provider = createProvider(modelConfig.provider);
const model = provider.languageModel(modelConfig.model); const knowledgeGraphAgents = ["note_creation", "email-draft", "meeting-prep"];
const modelId = (knowledgeGraphAgents.includes(state.agentName!) && modelConfig.knowledgeGraphModel)
? modelConfig.knowledgeGraphModel
: modelConfig.model;
const model = provider.languageModel(modelId);
logger.log(`using model: ${modelId}`);
let loopCounter = 0; let loopCounter = 0;
while (true) { while (true) {

View file

@ -10,4 +10,5 @@ export const LlmProvider = z.object({
export const LlmModelConfig = z.object({ export const LlmModelConfig = z.object({
provider: LlmProvider, provider: LlmProvider,
model: z.string(), model: z.string(),
knowledgeGraphModel: z.string().optional(),
}); });