@prsense/llm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/events.d.ts +9 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +9 -0
- package/dist/events.js.map +1 -0
- package/dist/helper.d.ts +3 -0
- package/dist/helper.d.ts.map +1 -0
- package/dist/helper.js +8 -0
- package/dist/helper.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/retry.d.ts +6 -0
- package/dist/middleware/retry.d.ts.map +1 -0
- package/dist/middleware/retry.js +21 -0
- package/dist/middleware/retry.js.map +1 -0
- package/dist/middleware/timeout.d.ts +3 -0
- package/dist/middleware/timeout.d.ts.map +1 -0
- package/dist/middleware/timeout.js +11 -0
- package/dist/middleware/timeout.js.map +1 -0
- package/dist/providers/anthropic.d.ts +7 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +52 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/claude.d.ts +7 -0
- package/dist/providers/claude.d.ts.map +1 -0
- package/dist/providers/claude.js +40 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/gemini.d.ts +7 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +46 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/google.d.ts +7 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +61 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/ollama.d.ts +3 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +56 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/ollamaEmbeddings.d.ts +6 -0
- package/dist/providers/ollamaEmbeddings.d.ts.map +1 -0
- package/dist/providers/ollamaEmbeddings.js +48 -0
- package/dist/providers/ollamaEmbeddings.js.map +1 -0
- package/dist/providers/openai.d.ts +8 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +49 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openaiEmbeddings.d.ts +6 -0
- package/dist/providers/openaiEmbeddings.d.ts.map +1 -0
- package/dist/providers/openaiEmbeddings.js +30 -0
- package/dist/providers/openaiEmbeddings.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +32 -0
- package/src/events.ts +11 -0
- package/src/helper.ts +13 -0
- package/src/index.ts +8 -0
- package/src/middleware/retry.ts +26 -0
- package/src/middleware/timeout.ts +13 -0
- package/src/providers/anthropic.ts +71 -0
- package/src/providers/google.ts +82 -0
- package/src/providers/ollama.ts +77 -0
- package/src/providers/ollamaEmbeddings.ts +67 -0
- package/src/providers/openai.ts +71 -0
- package/src/providers/openaiEmbeddings.ts +37 -0
- package/src/types.ts +81 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
export function createOllamaClient(config) {
|
|
3
|
+
const baseUrl = config.baseUrl ?? "http://localhost:11434";
|
|
4
|
+
return {
|
|
5
|
+
async generate(req) {
|
|
6
|
+
const { prompt } = req;
|
|
7
|
+
const res = await fetch(`${baseUrl}/api/generate`, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
model: config.model,
|
|
14
|
+
prompt: `
|
|
15
|
+
${prompt.system}
|
|
16
|
+
|
|
17
|
+
### REVIEW INPUT START ###
|
|
18
|
+
|
|
19
|
+
${prompt.user}
|
|
20
|
+
|
|
21
|
+
### REVIEW INPUT END ###
|
|
22
|
+
|
|
23
|
+
Remember:
|
|
24
|
+
Return ONLY JSON.
|
|
25
|
+
`,
|
|
26
|
+
stream: false,
|
|
27
|
+
options: {
|
|
28
|
+
temperature: config.temperature ?? 0.1,
|
|
29
|
+
top_p: 0.9,
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`Ollama error: ${res.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
const json = (await res.json());
|
|
37
|
+
const usage = json.prompt_eval_count !== undefined && json.eval_count !== undefined
|
|
38
|
+
? {
|
|
39
|
+
promptTokens: json.prompt_eval_count,
|
|
40
|
+
completionTokens: json.eval_count,
|
|
41
|
+
totalTokens: json.prompt_eval_count + json.eval_count,
|
|
42
|
+
}
|
|
43
|
+
: undefined;
|
|
44
|
+
if (usage) {
|
|
45
|
+
return {
|
|
46
|
+
text: json.response,
|
|
47
|
+
usage,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
text: json.response,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=ollama.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/providers/ollama.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAe/B,MAAM,UAAU,kBAAkB,CAAC,MAAoB;IACrD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,wBAAwB,CAAC;IAE3D,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,GAAe;YAC5B,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;YAEvB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,eAAe,EAAE;gBACjD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,MAAM,EAAE;EAChB,MAAM,CAAC,MAAM;;;;EAIb,MAAM,CAAC,IAAI;;;;;;CAMZ;oBACS,MAAM,EAAE,KAAK;oBACb,OAAO,EAAE;wBACP,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,GAAG;wBACtC,KAAK,EAAE,GAAG;qBACX;iBACF,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;YACrD,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAmB,CAAC;YAElD,MAAM,KAAK,GACT,IAAI,CAAC,iBAAiB,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;gBACnE,CAAC,CAAC;oBACE,YAAY,EAAE,IAAI,CAAC,iBAAiB;oBACpC,gBAAgB,EAAE,IAAI,CAAC,UAAU;oBACjC,WAAW,EAAE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,UAAU;iBACtD;gBACH,CAAC,CAAC,SAAS,CAAC;YAEhB,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO;oBACL,IAAI,EAAE,IAAI,CAAC,QAAQ;oBACnB,KAAK;iBACN,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,QAAQ;aACpB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollamaEmbeddings.d.ts","sourceRoot":"","sources":["../../src/providers/ollamaEmbeddings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,eAAe,CA4DlB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
export function createOllamaEmbeddingClient(opts) {
|
|
3
|
+
const baseUrl = opts.baseUrl ?? "http://localhost:11434";
|
|
4
|
+
let cachedDimension = null;
|
|
5
|
+
async function detectDimension() {
|
|
6
|
+
if (cachedDimension !== null)
|
|
7
|
+
return cachedDimension;
|
|
8
|
+
const res = await fetch(`${baseUrl}/api/embeddings`, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: { "Content-Type": "application/json" },
|
|
11
|
+
body: JSON.stringify({
|
|
12
|
+
model: opts.model,
|
|
13
|
+
prompt: "dimension test",
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
throw new Error(`Ollama embedding dimension detection failed: ${res.status}`);
|
|
18
|
+
}
|
|
19
|
+
const json = (await res.json());
|
|
20
|
+
cachedDimension = json.embedding.length;
|
|
21
|
+
return cachedDimension;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
async embed(texts) {
|
|
25
|
+
const results = [];
|
|
26
|
+
for (const text of texts) {
|
|
27
|
+
const res = await fetch(`${baseUrl}/api/embeddings`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
model: opts.model,
|
|
34
|
+
prompt: text,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`Ollama embedding error: ${res.status} ${await res.text()}`);
|
|
39
|
+
}
|
|
40
|
+
const json = (await res.json());
|
|
41
|
+
results.push(json.embedding);
|
|
42
|
+
}
|
|
43
|
+
return results;
|
|
44
|
+
},
|
|
45
|
+
dimension: detectDimension,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=ollamaEmbeddings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollamaEmbeddings.js","sourceRoot":"","sources":["../../src/providers/ollamaEmbeddings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B,MAAM,UAAU,2BAA2B,CAAC,IAG3C;IACC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,wBAAwB,CAAC;IACzD,IAAI,eAAe,GAAkB,IAAI,CAAC;IAC1C,KAAK,UAAU,eAAe;QAC5B,IAAI,eAAe,KAAK,IAAI;YAAE,OAAO,eAAe,CAAC;QAErD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,iBAAiB,EAAE;YACnD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,gBAAgB;aACzB,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,gDAAgD,GAAG,CAAC,MAAM,EAAE,CAC7D,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;QAE3D,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QACxC,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,KAAe;YACzB,MAAM,OAAO,GAAe,EAAE,CAAC;YAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,iBAAiB,EAAE;oBACnD,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;qBACnC;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,KAAK,EAAE,IAAI,CAAC,KAAK;wBACjB,MAAM,EAAE,IAAI;qBACb,CAAC;iBACH,CAAC,CAAC;gBAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;oBACZ,MAAM,IAAI,KAAK,CACb,2BAA2B,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAC5D,CAAC;gBACJ,CAAC;gBAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAC;gBAEF,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC/B,CAAC;YAED,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,SAAS,EAAE,eAAe;KAC3B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,SAAS,EAKV,MAAM,aAAa,CAAC;AAErB,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,SAAS,CAuDZ"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// packages/llm/src/providers/openai.ts
|
|
2
|
+
import OpenAI from "openai";
|
|
3
|
+
import { LlmError, } from "../types.js";
|
|
4
|
+
export function createOpenAiClient(config) {
|
|
5
|
+
const client = new OpenAI({
|
|
6
|
+
apiKey: config.apiKey,
|
|
7
|
+
baseURL: config.baseUrl,
|
|
8
|
+
});
|
|
9
|
+
return {
|
|
10
|
+
async generate(req) {
|
|
11
|
+
const { prompt } = req;
|
|
12
|
+
const temperature = req.temperature ?? config.temperature ?? 0.05;
|
|
13
|
+
try {
|
|
14
|
+
const request = {
|
|
15
|
+
model: config.model,
|
|
16
|
+
temperature,
|
|
17
|
+
messages: [
|
|
18
|
+
{ role: "system", content: prompt.system },
|
|
19
|
+
{ role: "user", content: prompt.user },
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
if (req.maxTokens !== undefined) {
|
|
23
|
+
request.max_tokens = req.maxTokens;
|
|
24
|
+
}
|
|
25
|
+
const res = await client.chat.completions.create(request);
|
|
26
|
+
const text = res.choices[0]?.message?.content;
|
|
27
|
+
if (!text) {
|
|
28
|
+
throw new Error("OpenAI returned empty response");
|
|
29
|
+
}
|
|
30
|
+
const usage = res.usage && res.usage.prompt_tokens !== undefined
|
|
31
|
+
? {
|
|
32
|
+
promptTokens: res.usage.prompt_tokens,
|
|
33
|
+
completionTokens: res.usage.completion_tokens ?? 0,
|
|
34
|
+
totalTokens: res.usage.total_tokens ??
|
|
35
|
+
res.usage.prompt_tokens + (res.usage.completion_tokens ?? 0),
|
|
36
|
+
}
|
|
37
|
+
: undefined;
|
|
38
|
+
if (usage) {
|
|
39
|
+
return { text, usage };
|
|
40
|
+
}
|
|
41
|
+
return { text };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
throw new LlmError("OpenAI request failed", err);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAIL,QAAQ,GAET,MAAM,aAAa,CAAC;AAErB,MAAM,UAAU,kBAAkB,CAAC,MAKlC;IACC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,GAAe;YAC5B,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;YAEvB,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC;YAElE,IAAI,CAAC;gBACH,MAAM,OAAO,GAAQ;oBACnB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,WAAW;oBACX,QAAQ,EAAE;wBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;wBAC1C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE;qBACvC;iBACF,CAAC;gBAEF,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;oBAChC,OAAO,CAAC,UAAU,GAAG,GAAG,CAAC,SAAS,CAAC;gBACrC,CAAC;gBAED,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAE1D,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC;gBAE9C,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;gBACpD,CAAC;gBAED,MAAM,KAAK,GACT,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,aAAa,KAAK,SAAS;oBAChD,CAAC,CAAC;wBACE,YAAY,EAAE,GAAG,CAAC,KAAK,CAAC,aAAa;wBACrC,gBAAgB,EAAE,GAAG,CAAC,KAAK,CAAC,iBAAiB,IAAI,CAAC;wBAClD,WAAW,EACT,GAAG,CAAC,KAAK,CAAC,YAAY;4BACtB,GAAG,CAAC,KAAK,CAAC,aAAa,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,IAAI,CAAC,CAAC;qBAC/D;oBACH,CAAC,CAAC,SAAS,CAAC;gBAEhB,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;gBACzB,CAAC;gBAED,OAAO,EAAE,IAAI,EAAE,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,QAAQ,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiEmbeddings.d.ts","sourceRoot":"","sources":["../../src/providers/openaiEmbeddings.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,eAAe,CA6BlB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//packages/llm/src/providers/openaiEmbeddings.ts
|
|
2
|
+
import OpenAI from "openai";
|
|
3
|
+
export function createOpenAiEmbeddingClient(opts) {
|
|
4
|
+
const client = new OpenAI({
|
|
5
|
+
apiKey: opts.apiKey,
|
|
6
|
+
});
|
|
7
|
+
let cachedDimension = null;
|
|
8
|
+
async function embed(texts) {
|
|
9
|
+
const response = await client.embeddings.create({
|
|
10
|
+
model: opts.model,
|
|
11
|
+
input: texts,
|
|
12
|
+
});
|
|
13
|
+
return response.data.map((d) => d.embedding);
|
|
14
|
+
}
|
|
15
|
+
async function detectDimension() {
|
|
16
|
+
if (cachedDimension !== null)
|
|
17
|
+
return cachedDimension;
|
|
18
|
+
const vectors = await embed(["dimension test"]);
|
|
19
|
+
if (!vectors.length || !vectors[0]) {
|
|
20
|
+
throw new Error("Failed to detect embedding dimension: empty response");
|
|
21
|
+
}
|
|
22
|
+
cachedDimension = vectors[0].length;
|
|
23
|
+
return cachedDimension;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
embed,
|
|
27
|
+
dimension: detectDimension,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=openaiEmbeddings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openaiEmbeddings.js","sourceRoot":"","sources":["../../src/providers/openaiEmbeddings.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,MAAM,UAAU,2BAA2B,CAAC,IAG3C;IACC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;IACH,IAAI,eAAe,GAAkB,IAAI,CAAC;IAE1C,KAAK,UAAU,KAAK,CAAC,KAAe;QAClC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;YAC9C,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;IACD,KAAK,UAAU,eAAe;QAC5B,IAAI,eAAe,KAAK,IAAI;YAAE,OAAO,eAAe,CAAC;QAErD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC1E,CAAC;QACD,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACpC,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,OAAO;QACL,KAAK;QACL,SAAS,EAAE,eAAe;KAC3B,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal, explicit prompt contract.
|
|
3
|
+
* No chat history, no tools, no streaming (yet).
|
|
4
|
+
*/
|
|
5
|
+
export type LlmPrompt = {
|
|
6
|
+
system: string;
|
|
7
|
+
user: string;
|
|
8
|
+
};
|
|
9
|
+
export type LlmUsage = {
|
|
10
|
+
promptTokens: number;
|
|
11
|
+
completionTokens: number;
|
|
12
|
+
totalTokens: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Raw LLM output.
|
|
16
|
+
* Parsing happens in the engine.
|
|
17
|
+
*/
|
|
18
|
+
export type LlmResponse = {
|
|
19
|
+
text: string;
|
|
20
|
+
usage?: LlmUsage;
|
|
21
|
+
};
|
|
22
|
+
export type OllamaConfig = {
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
model: string;
|
|
25
|
+
temperature?: number;
|
|
26
|
+
};
|
|
27
|
+
export type LlmRequest = {
|
|
28
|
+
prompt: LlmPrompt;
|
|
29
|
+
temperature?: number;
|
|
30
|
+
maxTokens?: number;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* LLM port used by the engine.
|
|
35
|
+
* Providers implement this.
|
|
36
|
+
*/
|
|
37
|
+
export type LlmClient = {
|
|
38
|
+
generate(req: LlmRequest): Promise<LlmResponse>;
|
|
39
|
+
};
|
|
40
|
+
export type LlmMiddleware = (client: LlmClient) => LlmClient;
|
|
41
|
+
export declare class LlmError extends Error {
|
|
42
|
+
readonly cause?: unknown | undefined;
|
|
43
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
44
|
+
}
|
|
45
|
+
export type ClaudeConfig = {
|
|
46
|
+
apiKey: string;
|
|
47
|
+
model: string;
|
|
48
|
+
};
|
|
49
|
+
export type GeminiConfig = {
|
|
50
|
+
apiKey: string;
|
|
51
|
+
model: string;
|
|
52
|
+
};
|
|
53
|
+
export type OpenAiConfig = {
|
|
54
|
+
apiKey: string;
|
|
55
|
+
model: string;
|
|
56
|
+
baseUrl?: string;
|
|
57
|
+
};
|
|
58
|
+
export type EmbeddingClient = {
|
|
59
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
60
|
+
dimension(): Promise<number>;
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,SAAS,CAAC;IAMlB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,SAAS,CAAC;AAE7D,qBAAa,QAAS,SAAQ,KAAK;aAGf,KAAK,CAAC,EAAE,OAAO;gBAD/B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAIlC;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5C,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9B,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAoDA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IAFlB,YACE,OAAe,EACC,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,UAAK,GAAL,KAAK,CAAU;IAGjC,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prsense/llm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LLM interface and providers for PRsense",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "Navdeep Saini",
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
19
|
+
"@google/generative-ai": "^0.24.1",
|
|
20
|
+
"node-fetch": "^3.3.2",
|
|
21
|
+
"openai": "^6.16.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.0.3",
|
|
25
|
+
"tsx": "^4.21.0",
|
|
26
|
+
"typescript": "^5.9.3"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -b",
|
|
30
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// packages/llm/src/events.ts
|
|
2
|
+
|
|
3
|
+
export const LlmEvents = {
|
|
4
|
+
RequestStarted: "llm.request.started",
|
|
5
|
+
RequestSucceeded: "llm.request.succeeded",
|
|
6
|
+
RequestFailed: "llm.request.failed",
|
|
7
|
+
RequestRetried: "llm.request.retried",
|
|
8
|
+
RequestTimedOut: "llm.request.timed_out",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type LlmEventName = (typeof LlmEvents)[keyof typeof LlmEvents];
|
package/src/helper.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LlmClient, LlmMiddleware } from "./types.js";
|
|
2
|
+
|
|
3
|
+
// helper function for composition
|
|
4
|
+
export function composeLlm(
|
|
5
|
+
client: LlmClient,
|
|
6
|
+
...middlewares: LlmMiddleware[]
|
|
7
|
+
): LlmClient {
|
|
8
|
+
return middlewares.reduce((acc, mw) => mw(acc), client);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// usage
|
|
12
|
+
// const base = createOllamaClient(config)
|
|
13
|
+
// const llm = composeLlm(base, withTimeout(30_000), withRetries({retries: 2}))
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./providers/ollama.js";
|
|
3
|
+
export * from "./providers/openai.js";
|
|
4
|
+
export * from "./providers/anthropic.js";
|
|
5
|
+
export * from "./providers/google.js";
|
|
6
|
+
export * from "./providers/ollamaEmbeddings.js";
|
|
7
|
+
export * from "./providers/openaiEmbeddings.js";
|
|
8
|
+
//TODO add gemini embeddings client if available
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LlmMiddleware } from "../types.js";
|
|
2
|
+
export function withRetries(opts: {
|
|
3
|
+
retries: number;
|
|
4
|
+
backoffMs?: number;
|
|
5
|
+
}): LlmMiddleware {
|
|
6
|
+
const { retries, backoffMs = 200 } = opts;
|
|
7
|
+
|
|
8
|
+
return (client) => ({
|
|
9
|
+
async generate(req) {
|
|
10
|
+
let lastErr: unknown;
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i <= retries; i++) {
|
|
13
|
+
try {
|
|
14
|
+
return await client.generate(req);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
lastErr = err;
|
|
17
|
+
if (i < retries) {
|
|
18
|
+
await new Promise((r) => setTimeout(r, backoffMs * (i + 1)));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
throw lastErr;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LlmMiddleware } from "../types.js";
|
|
2
|
+
export function withTimeout(ms: number): LlmMiddleware {
|
|
3
|
+
return (client) => ({
|
|
4
|
+
async generate(req) {
|
|
5
|
+
return Promise.race([
|
|
6
|
+
client.generate(req),
|
|
7
|
+
new Promise<never>((_, reject) =>
|
|
8
|
+
setTimeout(() => reject(new Error("LLM timeout")), ms),
|
|
9
|
+
),
|
|
10
|
+
]);
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// packages/llm/src/providers/anthropic.ts
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import type { TextBlock } from "@anthropic-ai/sdk/resources/messages/messages";
|
|
4
|
+
import {
|
|
5
|
+
LlmClient,
|
|
6
|
+
LlmRequest,
|
|
7
|
+
LlmResponse,
|
|
8
|
+
LlmError,
|
|
9
|
+
LlmUsage,
|
|
10
|
+
} from "../types.js";
|
|
11
|
+
|
|
12
|
+
export function createAnthropicClient(config: {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
model: string;
|
|
15
|
+
temperature?: number;
|
|
16
|
+
}): LlmClient {
|
|
17
|
+
const client = new Anthropic({
|
|
18
|
+
apiKey: config.apiKey,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
async generate(req: LlmRequest): Promise<LlmResponse> {
|
|
23
|
+
const { prompt, temperature = 0 } = req;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const res = await client.messages.create({
|
|
27
|
+
model: config.model,
|
|
28
|
+
max_tokens: req.maxTokens ?? 4096,
|
|
29
|
+
temperature,
|
|
30
|
+
system: prompt.system,
|
|
31
|
+
messages: [
|
|
32
|
+
{
|
|
33
|
+
role: "user",
|
|
34
|
+
content: prompt.user,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const text = res.content
|
|
40
|
+
.filter((c): c is TextBlock => c.type === "text")
|
|
41
|
+
.map((c) => c.text)
|
|
42
|
+
.join("\n");
|
|
43
|
+
|
|
44
|
+
if (!text) {
|
|
45
|
+
throw new Error("Claude returned empty response");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const usage: LlmUsage | undefined =
|
|
49
|
+
res.usage && res.usage.input_tokens !== undefined
|
|
50
|
+
? {
|
|
51
|
+
promptTokens: res.usage.input_tokens,
|
|
52
|
+
completionTokens: res.usage.output_tokens ?? 0,
|
|
53
|
+
totalTokens:
|
|
54
|
+
res.usage.input_tokens + (res.usage.output_tokens ?? 0),
|
|
55
|
+
}
|
|
56
|
+
: undefined;
|
|
57
|
+
|
|
58
|
+
if (usage) {
|
|
59
|
+
return {
|
|
60
|
+
text,
|
|
61
|
+
usage,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { text };
|
|
66
|
+
} catch (err) {
|
|
67
|
+
throw new LlmError("Claude request failed", err);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// packages/llm/src/providers/google.ts
|
|
2
|
+
import {
|
|
3
|
+
GoogleGenerativeAI,
|
|
4
|
+
HarmCategory,
|
|
5
|
+
HarmBlockThreshold,
|
|
6
|
+
} from "@google/generative-ai";
|
|
7
|
+
import { LlmClient, LlmRequest, LlmResponse } from "../types.js";
|
|
8
|
+
|
|
9
|
+
function stripMarkdownJson(text: string): string {
|
|
10
|
+
const trimmed = text.trim();
|
|
11
|
+
|
|
12
|
+
if (!trimmed.startsWith("```")) return trimmed;
|
|
13
|
+
|
|
14
|
+
const lines = trimmed.split("\n");
|
|
15
|
+
|
|
16
|
+
if (lines[0]?.startsWith("```")) lines.shift();
|
|
17
|
+
if (lines.at(-1)?.startsWith("```")) lines.pop();
|
|
18
|
+
|
|
19
|
+
return lines.join("\n").trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createGoogleClient(config: {
|
|
23
|
+
apiKey: string;
|
|
24
|
+
model: string;
|
|
25
|
+
temperature?: number;
|
|
26
|
+
}): LlmClient {
|
|
27
|
+
const genAI = new GoogleGenerativeAI(config.apiKey);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
async generate(req: LlmRequest): Promise<LlmResponse> {
|
|
31
|
+
const { prompt, temperature = 0.05 } = req;
|
|
32
|
+
|
|
33
|
+
const model = genAI.getGenerativeModel({
|
|
34
|
+
model: config.model,
|
|
35
|
+
systemInstruction: prompt.system,
|
|
36
|
+
safetySettings: [
|
|
37
|
+
{
|
|
38
|
+
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
|
39
|
+
threshold: HarmBlockThreshold.BLOCK_NONE,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const res = await model.generateContent({
|
|
45
|
+
contents: [
|
|
46
|
+
{
|
|
47
|
+
role: "user",
|
|
48
|
+
parts: [{ text: prompt.user }],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
generationConfig: {
|
|
52
|
+
temperature,
|
|
53
|
+
maxOutputTokens: 1024,
|
|
54
|
+
responseMimeType: "application/json",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const candidate = res.response.candidates?.[0];
|
|
59
|
+
|
|
60
|
+
const raw =
|
|
61
|
+
candidate?.content?.parts
|
|
62
|
+
?.map((p) => ("text" in p ? p.text : ""))
|
|
63
|
+
.join("") ?? "";
|
|
64
|
+
|
|
65
|
+
if (!raw) {
|
|
66
|
+
throw new Error("Gemini returned empty response");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const text = stripMarkdownJson(raw);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
text,
|
|
73
|
+
usage: {
|
|
74
|
+
promptTokens: res.response.usageMetadata?.promptTokenCount ?? 0,
|
|
75
|
+
completionTokens:
|
|
76
|
+
res.response.usageMetadata?.candidatesTokenCount ?? 0,
|
|
77
|
+
totalTokens: res.response.usageMetadata?.totalTokenCount ?? 0,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import {
|
|
3
|
+
LlmClient,
|
|
4
|
+
LlmRequest,
|
|
5
|
+
LlmResponse,
|
|
6
|
+
OllamaConfig,
|
|
7
|
+
LlmUsage,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
|
|
10
|
+
type OllamaResponse = {
|
|
11
|
+
response: string;
|
|
12
|
+
prompt_eval_count?: number;
|
|
13
|
+
eval_count?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function createOllamaClient(config: OllamaConfig): LlmClient {
|
|
17
|
+
const baseUrl = config.baseUrl ?? "http://localhost:11434";
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
async generate(req: LlmRequest): Promise<LlmResponse> {
|
|
21
|
+
const { prompt } = req;
|
|
22
|
+
|
|
23
|
+
const res = await fetch(`${baseUrl}/api/generate`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model: config.model,
|
|
30
|
+
prompt: `
|
|
31
|
+
${prompt.system}
|
|
32
|
+
|
|
33
|
+
### REVIEW INPUT START ###
|
|
34
|
+
|
|
35
|
+
${prompt.user}
|
|
36
|
+
|
|
37
|
+
### REVIEW INPUT END ###
|
|
38
|
+
|
|
39
|
+
Remember:
|
|
40
|
+
Return ONLY JSON.
|
|
41
|
+
`,
|
|
42
|
+
stream: false,
|
|
43
|
+
options: {
|
|
44
|
+
temperature: config.temperature ?? 0.1,
|
|
45
|
+
top_p: 0.9,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
throw new Error(`Ollama error: ${res.statusText}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const json = (await res.json()) as OllamaResponse;
|
|
55
|
+
|
|
56
|
+
const usage: LlmUsage | undefined =
|
|
57
|
+
json.prompt_eval_count !== undefined && json.eval_count !== undefined
|
|
58
|
+
? {
|
|
59
|
+
promptTokens: json.prompt_eval_count,
|
|
60
|
+
completionTokens: json.eval_count,
|
|
61
|
+
totalTokens: json.prompt_eval_count + json.eval_count,
|
|
62
|
+
}
|
|
63
|
+
: undefined;
|
|
64
|
+
|
|
65
|
+
if (usage) {
|
|
66
|
+
return {
|
|
67
|
+
text: json.response,
|
|
68
|
+
usage,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
text: json.response,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|