Skip to content

Commit 8e9c03b

Browse files
author
Sebastian BURGIN-FIX (ext)
committed
wip
1 parent 97eb2e2 commit 8e9c03b

10 files changed

Lines changed: 201 additions & 516 deletions

File tree

database/seeders/AiModelDailyUsagesTableSeeder.php

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,29 @@
33
namespace Database\Seeders;
44

55
use App\Actions\StoreLlmUsageAction;
6-
use App\Models\AiModel;
6+
use Database\Seeders\Concerns\ReadsCsv;
77
use Illuminate\Database\Seeder;
8-
use Illuminate\Support\Carbon;
98

109
class AiModelDailyUsagesTableSeeder extends Seeder
1110
{
12-
private const int DAYS = 180;
11+
use ReadsCsv;
1312

1413
/**
15-
* Seed demo usage data for the active models, mirroring the shape of the
16-
* LiteLLM import so the analytics page has something to show locally.
14+
* Seed local usage data from the current LLM usage export, so the
15+
* analytics page has real-shaped data to show locally.
1716
*/
1817
public function run(): void
1918
{
20-
$models = AiModel::whereNull('archived_at')->pluck('name');
21-
22-
if ($models->isEmpty()) {
23-
return;
24-
}
25-
26-
$rows = [];
27-
28-
foreach ($models as $model) {
29-
$isRetrieval = str_contains($model, 'embedding') || str_contains($model, 'reranker');
30-
$baseRequests = random_int(20, 400);
31-
32-
for ($i = self::DAYS; $i >= 1; $i--) {
33-
$date = Carbon::today()->subDays($i);
34-
35-
// Not every model is used every day - weekends are mostly quiet.
36-
if (random_int(1, 100) <= ($date->isWeekend() ? 70 : 15)) {
37-
continue;
38-
}
39-
40-
$requests = random_int((int) ($baseRequests * 0.4), $baseRequests * 2);
41-
$promptTokens = $requests * random_int(300, 4_000);
42-
$completionTokens = $isRetrieval
43-
? $requests * random_int(1, 10)
44-
: $requests * random_int(100, 1_500);
45-
46-
$rows[] = [
47-
'date' => $date->format('Y-m-d'),
48-
'model' => (string) $model,
49-
'prompt_tokens' => $promptTokens,
50-
'completion_tokens' => $completionTokens,
51-
'total_tokens' => $promptTokens + $completionTokens,
52-
'requests' => $requests,
53-
'spend' => round(($promptTokens + $completionTokens) / 1_000_000 * 0.35, 6),
54-
];
55-
}
56-
}
57-
58-
(new StoreLlmUsageAction)->store(collect($rows));
19+
$rows = collect($this->readCsv('ai_model_daily_usages.csv'))->map(fn (array $row) => [
20+
'date' => $row['date'],
21+
'model' => $row['model'],
22+
'prompt_tokens' => (int) $row['prompt_tokens'],
23+
'completion_tokens' => (int) $row['completion_tokens'],
24+
'total_tokens' => (int) $row['total_tokens'],
25+
'requests' => (int) $row['requests'],
26+
'spend' => (float) $row['spend'],
27+
]);
28+
29+
(new StoreLlmUsageAction)->store($rows);
5930
}
6031
}

database/seeders/AiModelsTableSeeder.php

Lines changed: 28 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -4,230 +4,43 @@
44

55
use App\Enums\AiModelCategoryEnum;
66
use App\Models\AiModel;
7+
use Database\Seeders\Concerns\ReadsCsv;
78
use Illuminate\Database\Seeder;
8-
use Illuminate\Support\Arr;
99

1010
class AiModelsTableSeeder extends Seeder
1111
{
12-
public function run(): void
13-
{
14-
$this->active();
15-
$this->archived();
16-
}
12+
use ReadsCsv;
1713

18-
private function active(): void
14+
public function run(): void
1915
{
20-
$models = [
21-
// Reasoning & Coding
22-
[
23-
'category' => AiModelCategoryEnum::REASONING_CODING,
24-
'order' => 1,
25-
'name' => 'deepseek-v4:flash',
26-
'provider' => 'DeepSeek AI (CN)',
27-
'ram' => '~102 GB RAM',
28-
'license' => 'MIT',
29-
'role' => [
30-
'de' => 'Flaggschiff: Analysen, Buchungslogik, komplexes Coding',
31-
'en' => 'Flagship: analysis, booking logic, complex coding',
32-
],
33-
'link_label' => 'Hugging Face',
34-
'link_url' => 'https://huggingface.co/unsloth',
35-
],
36-
[
37-
'category' => AiModelCategoryEnum::REASONING_CODING,
38-
'order' => 2,
39-
'name' => 'kimi-linear:48b',
40-
'provider' => 'Moonshot AI (CN)',
41-
'ram' => '30 GB RAM',
42-
'license' => 'MIT',
43-
'role' => [
44-
'de' => 'Alltags-Sprinter, lange Dokumente & Kontextverständnis',
45-
'en' => 'Everyday sprinter, long documents & context understanding',
46-
],
47-
'link_label' => 'Hugging Face',
48-
'link_url' => 'https://huggingface.co/moonshotai',
49-
],
50-
[
51-
'category' => AiModelCategoryEnum::REASONING_CODING,
52-
'order' => 3,
53-
'name' => 'qwen3-coder:30b',
54-
'provider' => 'Alibaba / Qwen (CN)',
55-
'ram' => '18 GB RAM',
56-
'license' => 'Apache-2.0',
57-
'role' => [
58-
'de' => 'Coding-Spezialist für schnelle Iterationen & Agenten-Tasks',
59-
'en' => 'Coding specialist for fast iterations & agent tasks',
60-
],
61-
'link_label' => 'Ollama',
62-
'link_url' => 'https://ollama.com/library/qwen3-coder',
63-
],
64-
[
65-
'category' => AiModelCategoryEnum::REASONING_CODING,
66-
'order' => 4,
67-
'name' => 'qwen3.6:35b',
68-
'provider' => 'Alibaba / Qwen (CN)',
69-
'ram' => '23 GB RAM',
70-
'license' => 'Apache-2.0',
71-
'role' => [
72-
'de' => 'Reserve-Mittelklasse',
73-
'en' => 'Mid-range reserve',
74-
],
75-
'link_label' => 'Ollama',
76-
'link_url' => 'https://ollama.com/library/qwen3.6',
77-
],
78-
79-
// Vision & Dokumente
80-
[
81-
'category' => AiModelCategoryEnum::VISION_DOCUMENTS,
82-
'order' => 1,
83-
'name' => 'qwen3-vl:32b',
84-
'provider' => 'Alibaba / Qwen (CN)',
85-
'ram' => '20 GB RAM',
86-
'license' => 'Apache-2.0',
87-
'role' => [
88-
'de' => 'Detailliertes Bildverständnis: Screenshots, Diagramme, Belege',
89-
'en' => 'Detailed image understanding: screenshots, diagrams, receipts',
90-
],
91-
'link_label' => 'Ollama',
92-
'link_url' => 'https://ollama.com/library/qwen3-vl',
93-
],
94-
[
95-
'category' => AiModelCategoryEnum::VISION_DOCUMENTS,
96-
'order' => 2,
97-
'name' => 'qwen3-vl:8b',
98-
'provider' => 'Alibaba / Qwen (CN)',
99-
'ram' => '6,1 GB RAM',
100-
'license' => 'Apache-2.0',
101-
'role' => [
102-
'de' => 'Schnelle Vision-Variante für einfache Bildaufgaben',
103-
'en' => 'Fast vision variant for simple image tasks',
104-
],
105-
'link_label' => 'Ollama',
106-
'link_url' => 'https://ollama.com/library/qwen3-vl',
107-
],
108-
[
109-
'category' => AiModelCategoryEnum::VISION_DOCUMENTS,
110-
'order' => 3,
111-
'name' => 'gemma4:31b',
112-
'provider' => 'Google',
113-
'ram' => '19 GB RAM',
114-
'license' => 'Gemma',
115-
'role' => [
116-
'de' => 'Bild-Input + stilsichere Texte',
117-
'en' => 'Image input + polished writing',
118-
],
119-
'link_label' => 'Ollama',
120-
'link_url' => 'https://ollama.com/library/gemma4',
121-
],
122-
[
123-
'category' => AiModelCategoryEnum::VISION_DOCUMENTS,
124-
'order' => 4,
125-
'name' => 'deepseek-ocr',
126-
'provider' => 'DeepSeek AI (CN)',
127-
'ram' => '6,7 GB RAM',
128-
'license' => 'MIT',
129-
'role' => [
130-
'de' => 'PDF/Scan → Markdown',
131-
'en' => 'PDF/scan → Markdown',
132-
],
133-
'link_label' => 'Ollama',
134-
'link_url' => 'https://ollama.com/library/deepseek-ocr',
135-
],
136-
137-
// Retrieval & Suche
138-
[
139-
'category' => AiModelCategoryEnum::RETRIEVAL_SEARCH,
140-
'order' => 1,
141-
'name' => 'qwen3-embedding:8b',
142-
'provider' => 'Alibaba / Qwen (CN)',
143-
'ram' => '4,7 GB RAM',
144-
'license' => 'Apache-2.0',
145-
'role' => [
146-
'de' => 'Vektoren für Ähnlichkeitssuche',
147-
'en' => 'Vectors for similarity search',
148-
],
149-
'link_label' => 'Ollama',
150-
'link_url' => 'https://ollama.com/library/qwen3-embedding',
151-
],
152-
];
153-
154-
foreach ($models as $model) {
155-
AiModel::updateOrCreate(
156-
['name' => $model['name']],
157-
array_merge(['archived_at' => null, 'replaced_by_id' => null], $model)
16+
$rows = collect($this->readCsv('ai_models.csv'));
17+
18+
$models = $rows->map(function (array $row) {
19+
return AiModel::updateOrCreate(
20+
['name' => $row['name']],
21+
[
22+
'category' => AiModelCategoryEnum::from($row['category']),
23+
'order' => (int) $row['order'],
24+
'provider' => $row['provider'] !== '' ? $row['provider'] : null,
25+
'ram' => $row['ram'] !== '' ? $row['ram'] : null,
26+
'license' => $row['license'] !== '' ? $row['license'] : null,
27+
'role' => $this->decodeJson($row['role']),
28+
'link_label' => $row['link_label'] !== '' ? $row['link_label'] : null,
29+
'link_url' => $row['link_url'] !== '' ? $row['link_url'] : null,
30+
'archived_at' => $row['archived_at'] !== '' ? $row['archived_at'] : null,
31+
]
15832
);
159-
}
160-
}
33+
});
16134

162-
private function archived(): void
163-
{
164-
$archivedAt = '2026-07-01';
35+
$idsByCsvId = $rows->pluck('id')->combine($models->pluck('id'));
16536

166-
$models = [
167-
[
168-
'category' => AiModelCategoryEnum::REASONING_CODING,
169-
'order' => 1,
170-
'name' => 'qwen3.5:122b',
171-
'replaced_by' => 'deepseek-v4:flash',
172-
],
173-
[
174-
'category' => AiModelCategoryEnum::REASONING_CODING,
175-
'order' => 2,
176-
'name' => 'qwen3.6:35b-a3b',
177-
'replaced_by' => 'kimi-linear:48b',
178-
],
179-
[
180-
'category' => AiModelCategoryEnum::REASONING_CODING,
181-
'order' => 3,
182-
'name' => 'qwen3.6:27b',
183-
'replaced_by' => 'qwen3.6:35b',
184-
],
185-
[
186-
'category' => AiModelCategoryEnum::REASONING_CODING,
187-
'order' => 4,
188-
'name' => 'qwen3.5:9b',
189-
'replaced_by' => 'kimi-linear:48b',
190-
],
191-
[
192-
'category' => AiModelCategoryEnum::REASONING_CODING,
193-
'order' => 5,
194-
'name' => 'qwen3.5:4b',
195-
'replaced_by' => 'kimi-linear:48b',
196-
],
197-
[
198-
'category' => AiModelCategoryEnum::VISION_DOCUMENTS,
199-
'order' => 1,
200-
'name' => 'gemma4:12b',
201-
'replaced_by' => 'qwen3-vl:8b',
202-
],
203-
[
204-
'category' => AiModelCategoryEnum::VISION_DOCUMENTS,
205-
'order' => 2,
206-
'name' => 'gemma4:e4b',
207-
'replaced_by' => 'qwen3-vl:8b',
208-
],
209-
[
210-
'category' => AiModelCategoryEnum::RETRIEVAL_SEARCH,
211-
'order' => 1,
212-
'name' => 'qwen3-embedding:4b',
213-
'replaced_by' => 'qwen3-embedding:8b',
214-
],
215-
[
216-
'category' => AiModelCategoryEnum::RETRIEVAL_SEARCH,
217-
'order' => 2,
218-
'name' => 'qwen3-reranker:8b',
219-
'replaced_by' => null,
220-
],
221-
];
37+
$rows->each(function (array $row) use ($idsByCsvId, $models) {
38+
if ($row['replaced_by_id'] === '') {
39+
return;
40+
}
22241

223-
foreach ($models as $model) {
224-
$replacedByName = Arr::pull($model, 'replaced_by');
225-
$replacedById = $replacedByName ? AiModel::where('name', $replacedByName)->value('id') : null;
226-
227-
AiModel::updateOrCreate(
228-
['name' => $model['name']],
229-
array_merge(['archived_at' => $archivedAt, 'replaced_by_id' => $replacedById], $model)
230-
);
231-
}
42+
$models->firstWhere('id', $idsByCsvId->get($row['id']))
43+
->update(['replaced_by_id' => $idsByCsvId->get($row['replaced_by_id'])]);
44+
});
23245
}
23346
}

0 commit comments

Comments
 (0)