Skip to content

Commit ca3492d

Browse files
committed
fix(ai-llm): avoid duplicate Ollama model sync
1 parent dea735b commit ca3492d

2 files changed

Lines changed: 53 additions & 14 deletions

File tree

infra/docker/images/ollama/start-ollama.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ wait_for_ready() {
2626
}
2727

2828
sync_models_if_configured() {
29+
if [ "${OLLAMA_SYNC_ON_START:-0}" != "1" ]; then
30+
echo "Skipping startup model sync; Atlas Lab bootstrap handles model reconciliation."
31+
return
32+
fi
33+
2934
if [ -f /opt/atlas-lab/model-sync/sync-ollama-models.sh ]; then
3035
sh /opt/atlas-lab/model-sync/sync-ollama-models.sh
3136
else

src/services/bootstrap.service.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { Listr } from 'listr2';
22
import pWaitFor from 'p-wait-for';
33
import { createComposeCommandArgs, type ComposeLayerSelection } from '../lib/compose.js';
44
import type { BootstrapCommandOptions } from '../types/cli.types.js';
5-
import type { ProjectContext } from '../types/project.types.js';
5+
import type { AiLlmBootstrapEnv, ProjectContext } from '../types/project.types.js';
66
import { ensureGiteaAdmin } from './gitea-admin.service.js';
77
import { ensurePenpotAdmin } from './penpot-admin.service.js';
88
import { ensurePlaneAdmin, waitForPlaneBootstrapPrerequisites } from './plane-admin.service.js';
99
import { printCommandHeader } from '../ui/banner.js';
1010
import { formatTaskTitle, printInfo, printSuccess } from '../ui/logger.js';
1111
import { runCommand } from '../utils/process.js';
1212
import { parseAiLlmBootstrapEnv, parseBootstrapEnv } from './project.service.js';
13+
import { collectConfiguredOllamaModels } from '../utils/model-lists.js';
1314

1415
const VERBOSE_TASK_RENDERER = 'verbose' as const;
1516

@@ -81,7 +82,7 @@ export function createBootstrapTasks(
8182
tasks.push({
8283
title: formatTaskTitle('bootstrap', 'Align Ollama runtime models'),
8384
task: async () => {
84-
const result = await ensureOllamaModels(context);
85+
const result = await ensureOllamaModels(context, aiLlmEnv);
8586
printInfo(`Ollama runtime models ${result}.`, 'bootstrap');
8687
}
8788
});
@@ -93,11 +94,26 @@ export function createBootstrapTasks(
9394
* Waits for Ollama and ensures the configured runtime models are present locally.
9495
*/
9596
async function ensureOllamaModels(
96-
context: ProjectContext
97+
context: ProjectContext,
98+
env: AiLlmBootstrapEnv
9799
): Promise<'present' | 'pulled'> {
98100
await waitForService(context, 'ollama', 180, { includeAiLlm: true });
99101

100-
const syncResult = await runCommand(
102+
const configuredModels = collectConfiguredOllamaModels(env);
103+
const missingModels = await listMissingOllamaModels(context, configuredModels);
104+
105+
if (missingModels.length === 0) {
106+
printInfo('All configured Ollama models are already available locally.', 'bootstrap');
107+
return 'present';
108+
}
109+
110+
printInfo(
111+
`Syncing ${missingModels.length} missing Ollama model${missingModels.length === 1 ? '' : 's'}: ${missingModels.join(', ')}`,
112+
'bootstrap'
113+
);
114+
printInfo('Large Ollama models can take a long time to download; progress will stream below.', 'bootstrap');
115+
116+
await runCommand(
101117
'docker',
102118
createComposeCommandArgs(context, [
103119
'exec',
@@ -108,24 +124,42 @@ async function ensureOllamaModels(
108124
], { includeAiLlm: true }),
109125
{
110126
cwd: context.projectRoot,
111-
captureOutput: true,
112127
scope: 'bootstrap'
113128
}
114129
);
115130

116-
const lines = syncResult.stdout
117-
.split(/\r?\n/u)
118-
.map((line) => line.trim())
119-
.filter((line) => line.length > 0);
131+
return 'pulled';
132+
}
133+
134+
/**
135+
* Lists the configured Ollama models that are still missing locally inside the container.
136+
*/
137+
async function listMissingOllamaModels(
138+
context: ProjectContext,
139+
configuredModels: string[]
140+
): Promise<string[]> {
141+
const missingModels: string[] = [];
142+
143+
for (const modelName of configuredModels) {
144+
const inspectResult = await runCommand(
145+
'docker',
146+
createComposeCommandArgs(context, ['exec', '-T', 'ollama', 'ollama', 'show', modelName], {
147+
includeAiLlm: true
148+
}),
149+
{
150+
allowFailure: true,
151+
captureOutput: true,
152+
cwd: context.projectRoot,
153+
scope: 'bootstrap'
154+
}
155+
);
120156

121-
for (const line of lines) {
122-
if (!line.startsWith('ATLAS_OLLAMA_MODEL_SYNC_RESULT=')) {
123-
printInfo(line, 'bootstrap');
157+
if (inspectResult.exitCode !== 0) {
158+
missingModels.push(modelName);
124159
}
125160
}
126161

127-
const resultLine = lines.find((line) => line.startsWith('ATLAS_OLLAMA_MODEL_SYNC_RESULT='));
128-
return resultLine?.endsWith('pulled') ? 'pulled' : 'present';
162+
return missingModels;
129163
}
130164

131165
/**

0 commit comments

Comments
 (0)