|
1 | 1 | #!/usr/bin/env node |
2 | 2 | import { readFileSync, writeFileSync } from 'node:fs' |
3 | | -import { DEFAULT_TEMPLATE_QUESTIONNAIRE as TEMPLATE } from '../app/settings.js' |
| 3 | +import { DEFAULT_TEMPLATE_QUESTIONNAIRE as TEMPLATE } from '../app/settings.mjs' |
| 4 | +import CachedInferenceEngine from '../app/cached-inference-engine.mjs' |
4 | 5 |
|
5 | | -const CACHE_FILE = new URL('../app/data/responses.json', import.meta.url) |
6 | 6 | const APP_DATA_FILE = new URL('../app/data/app-data.json', import.meta.url) |
7 | 7 |
|
8 | | -function load() { |
9 | | - return JSON.parse(readFileSync(CACHE_FILE, 'utf-8')) |
10 | | -} |
11 | | - |
12 | | -function actionLs() { |
13 | | - const data = load() |
| 8 | +class ResponseCacheManager { |
14 | 9 |
|
15 | | - for (const [key, entry] of Object.entries(data)) { |
16 | | - console.log(`${key}\t${entry.model}\t${entry.response.length}`) |
| 10 | + constructor() { |
| 11 | + this.engine = new CachedInferenceEngine({ |
| 12 | + serviceUrl: process.env.AJ_LLM_API, |
| 13 | + model: process.env.AJ_MODEL |
| 14 | + }) |
17 | 15 | } |
18 | | -} |
19 | 16 |
|
20 | | -function actionModels() { |
21 | | - const data = load() |
22 | | - const counts = {} |
23 | | - |
24 | | - for (const entry of Object.values(data)) { |
25 | | - counts[entry.model] = (counts[entry.model] || 0) + 1 |
| 17 | + async loadCache() { |
| 18 | + await this.engine.loadCache('../app/data/responses.json') |
26 | 19 | } |
27 | 20 |
|
28 | | - for (const [model, count] of Object.entries(counts)) { |
29 | | - console.log(`${model}\t${count}`) |
| 21 | + getCacheEntries() { |
| 22 | + return this.engine.getCache() |
30 | 23 | } |
31 | | -} |
32 | | - |
33 | | -function hash(string) { |
34 | | - let h = 0 |
35 | | - for (const char of string) { |
36 | | - h = (h << 5) - h + char.charCodeAt(0) |
37 | | - h |= 0 |
38 | | - } |
39 | | - return h |
40 | | -} |
41 | | - |
42 | | -function buildPrompt(template, variables) { |
43 | | - let ret = template |
44 | | - for (const [k, v] of Object.entries(variables)) { |
45 | | - ret = ret.replace(`{${k}}`, v) |
46 | | - } |
47 | | - return ret |
48 | | -} |
49 | 24 |
|
50 | | -async function actionFetch() { |
51 | | - const serviceUrl = process.env.AJ_LLM_API |
52 | | - const model = process.env.AJ_MODEL |
| 25 | + actionLs() { |
| 26 | + const entries = this.getCacheEntries() |
53 | 27 |
|
54 | | - if (!serviceUrl || !model) { |
55 | | - console.error('Set AJ_LLM_API and AJ_MODEL environment variables') |
56 | | - process.exit(1) |
| 28 | + for (const [key, entry] of Object.entries(entries)) { |
| 29 | + console.log(`${key}\t${entry.model}\t${entry.response.length}`) |
| 30 | + } |
57 | 31 | } |
58 | 32 |
|
59 | | - const cache = load() |
60 | | - const appData = JSON.parse(readFileSync(APP_DATA_FILE, 'utf-8')) |
61 | | - |
62 | | - for (const [caseKey, caseData] of Object.entries(appData.cases)) { |
63 | | - for (const question of appData.questions) { |
64 | | - const prompt = buildPrompt(TEMPLATE, { |
65 | | - STATEMENT: caseData.statement, |
66 | | - QUESTION: question.text, |
67 | | - }) |
| 33 | + actionModels() { |
| 34 | + const entries = this.getCacheEntries() |
| 35 | + const counts = {} |
68 | 36 |
|
69 | | - const key = hash(`${model}-${prompt}`) |
| 37 | + for (const entry of Object.values(entries)) { |
| 38 | + counts[entry.model] = (counts[entry.model] || 0) + 1 |
| 39 | + } |
70 | 40 |
|
71 | | - if (cache[key]) { |
72 | | - continue |
73 | | - } |
| 41 | + for (const [model, count] of Object.entries(counts)) { |
| 42 | + console.log(`${model}\t${count}`) |
| 43 | + } |
| 44 | + } |
74 | 45 |
|
75 | | - const url = serviceUrl.replace(/\/+$/, '') + '/chat/completions' |
76 | | - const res = await fetch(url, { |
77 | | - method: 'POST', |
78 | | - headers: { 'Content-Type': 'application/json' }, |
79 | | - body: JSON.stringify({ |
80 | | - model, |
81 | | - messages: [{ role: 'user', content: prompt }], |
82 | | - stream: false, |
83 | | - max_tokens: 4000, |
84 | | - }), |
85 | | - }) |
86 | | - |
87 | | - if (!res.ok) { |
88 | | - console.error(`API error (${res.status}) for ${caseKey} / ${question.text}`) |
89 | | - continue |
| 46 | + async actionFetch() { |
| 47 | + const cache = this.engine.getCache() |
| 48 | + const appData = JSON.parse(readFileSync(APP_DATA_FILE, 'utf-8')) |
| 49 | + |
| 50 | + for (const [caseKey, caseData] of Object.entries(appData.cases)) { |
| 51 | + for (const question of appData.questions) { |
| 52 | + const prompt = this.engine.getPromptFromTemplate(TEMPLATE, { |
| 53 | + STATEMENT: caseData.statement, |
| 54 | + QUESTION: question.text, |
| 55 | + }) |
| 56 | + |
| 57 | + const key = CachedInferenceEngine.hash(`${model}-${prompt}`) |
| 58 | + |
| 59 | + if (cache[key]) { |
| 60 | + continue |
| 61 | + } |
| 62 | + |
| 63 | + const url = serviceUrl.replace(/\/+$/, '') + '/chat/completions' |
| 64 | + const res = await fetch(url, { |
| 65 | + method: 'POST', |
| 66 | + headers: { 'Content-Type': 'application/json' }, |
| 67 | + body: JSON.stringify({ |
| 68 | + model, |
| 69 | + messages: [{ role: 'user', content: prompt }], |
| 70 | + stream: false, |
| 71 | + max_tokens: 4000, |
| 72 | + }), |
| 73 | + }) |
| 74 | + |
| 75 | + if (!res.ok) { |
| 76 | + console.error(`API error (${res.status}) for ${caseKey} / ${question.text}`) |
| 77 | + continue |
| 78 | + } |
| 79 | + |
| 80 | + const data = await res.json() |
| 81 | + const response = data?.choices?.[0]?.message?.content |
| 82 | + |
| 83 | + if (response) { |
| 84 | + cache[key] = { response, model, hash: key } |
| 85 | + console.log(`cached ${caseKey} / ${question.text}`) |
| 86 | + } |
90 | 87 | } |
| 88 | + } |
| 89 | + } |
91 | 90 |
|
92 | | - const data = await res.json() |
93 | | - const response = data?.choices?.[0]?.message?.content |
94 | | - |
95 | | - if (response) { |
96 | | - cache[key] = { response, model, hash: key } |
97 | | - console.log(`cached ${caseKey} / ${question.text}`) |
| 91 | + async runAction(action) { |
| 92 | + await this.loadCache() |
| 93 | + |
| 94 | + if (action === 'ls') { |
| 95 | + this.actionLs() |
| 96 | + } else if (action === 'models') { |
| 97 | + this.actionModels() |
| 98 | + } else if (action === 'fetch') { |
| 99 | + this.actionFetch() |
| 100 | + } else { |
| 101 | + if (action && action !== 'help') { |
| 102 | + console.error('Unknown action:', action) |
98 | 103 | } |
99 | | - |
100 | | - writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2)) |
| 104 | + console.error('Usage: node cache.mjs <ls|models|fetch>') |
| 105 | + process.exit(1) |
101 | 106 | } |
102 | 107 | } |
103 | 108 | } |
104 | 109 |
|
105 | | -const action = process.argv[2] |
106 | | -if (action === 'ls') { |
107 | | - actionLs() |
108 | | -} else if (action === 'models') { |
109 | | - actionModels() |
110 | | -} else if (action === 'fetch') { |
111 | | - actionFetch() |
112 | | -} else { |
113 | | - console.error('Unknown action:', action) |
114 | | - console.error('Usage: node cache.mjs <ls|models|fetch>') |
115 | | - process.exit(1) |
116 | | -} |
| 110 | +const app = new ResponseCacheManager() |
| 111 | +await app.runAction(process.argv[2]) |
0 commit comments