ollamaClient.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { config } from "../config/index.js";
  2. import fs from "node:fs";
  3. async function ollamaFetch(path, body) {
  4. let res;
  5. try {
  6. res = await fetch(`${config.ollama.url}${path}`, {
  7. method: "POST",
  8. headers: { "content-type": "application/json" },
  9. body: JSON.stringify(body)
  10. });
  11. } catch (err) {
  12. // #region debug-point C:ollama-fetch-throw
  13. (() => {
  14. let u = "http://127.0.0.1:7777/event";
  15. let s = "chat-502-gateway";
  16. try {
  17. const e = fs.readFileSync(".dbg/chat-502-gateway.env", "utf8");
  18. u = e.match(/DEBUG_SERVER_URL=(.+)/)?.[1] || u;
  19. s = e.match(/DEBUG_SESSION_ID=(.+)/)?.[1] || s;
  20. } catch {}
  21. fetch(u, {
  22. method: "POST",
  23. headers: { "content-type": "application/json" },
  24. body: JSON.stringify({
  25. sessionId: s,
  26. runId: "post-fix",
  27. hypothesisId: "C",
  28. location: "api/services/ollamaClient.js",
  29. msg: "[DEBUG] ollama fetch threw",
  30. data: {
  31. url: config.ollama.url,
  32. path,
  33. model: body?.model ?? null,
  34. promptLen: typeof body?.prompt === "string" ? body.prompt.length : null,
  35. messagesCount: Array.isArray(body?.messages) ? body.messages.length : null,
  36. name: err?.name ?? null,
  37. message: typeof err?.message === "string" ? err.message : null
  38. },
  39. ts: Date.now()
  40. })
  41. }).catch(() => {});
  42. })();
  43. // #endregion
  44. throw err;
  45. }
  46. if (!res.ok) {
  47. const text = await res.text().catch(() => "");
  48. // #region debug-point C:ollama-non-200
  49. (() => {
  50. let u = "http://127.0.0.1:7777/event";
  51. let s = "chat-502-gateway";
  52. try {
  53. const e = fs.readFileSync(".dbg/chat-502-gateway.env", "utf8");
  54. u = e.match(/DEBUG_SERVER_URL=(.+)/)?.[1] || u;
  55. s = e.match(/DEBUG_SESSION_ID=(.+)/)?.[1] || s;
  56. } catch {}
  57. fetch(u, {
  58. method: "POST",
  59. headers: { "content-type": "application/json" },
  60. body: JSON.stringify({
  61. sessionId: s,
  62. runId: "post-fix",
  63. hypothesisId: "C",
  64. location: "api/services/ollamaClient.js",
  65. msg: "[DEBUG] ollama non-200",
  66. data: {
  67. url: config.ollama.url,
  68. path,
  69. status: res.status,
  70. statusText: res.statusText,
  71. model: body?.model ?? null,
  72. promptLen: typeof body?.prompt === "string" ? body.prompt.length : null,
  73. messagesCount: Array.isArray(body?.messages) ? body.messages.length : null,
  74. responseSnippet: typeof text === "string" ? text.slice(0, 300) : ""
  75. },
  76. ts: Date.now()
  77. })
  78. }).catch(() => {});
  79. })();
  80. // #endregion
  81. let msg = `ollama_error:${res.status}:${text || res.statusText}`;
  82. if (
  83. res.status === 404 &&
  84. typeof body?.model === "string" &&
  85. typeof text === "string" &&
  86. text.toLowerCase().includes("model") &&
  87. text.toLowerCase().includes("not found")
  88. ) {
  89. let installed = [];
  90. try {
  91. const tagsRes = await fetch(`${config.ollama.url}/api/tags`);
  92. const tags = await tagsRes.json().catch(() => ({}));
  93. installed = Array.isArray(tags?.models) ? tags.models.map((m) => m?.name).filter(Boolean) : [];
  94. } catch {}
  95. const installedList = installed.length ? installed.slice(0, 10).join(",") : "none";
  96. msg = `ollama_model_not_found:${body.model}:installed=${installedList}:hint=ollama pull ${body.model}`;
  97. }
  98. const err = new Error(msg);
  99. err.statusCode = 502;
  100. throw err;
  101. }
  102. return res.json();
  103. }
  104. export async function embedTexts(texts) {
  105. const embeddings = [];
  106. for (const text of texts) {
  107. const data = await ollamaFetch("/api/embeddings", {
  108. model: config.ollama.embeddingsModel,
  109. prompt: text
  110. });
  111. embeddings.push(data.embedding);
  112. }
  113. return embeddings;
  114. }
  115. export async function chatCompletion({ messages }) {
  116. const data = await ollamaFetch("/api/chat", {
  117. model: config.ollama.chatModel,
  118. messages,
  119. stream: false
  120. });
  121. return {
  122. content: data?.message?.content ?? ""
  123. };
  124. }
  125. export async function visionExtractFromImage({ imageBase64, prompt }) {
  126. const data = await ollamaFetch("/api/chat", {
  127. model: config.ollama.visionModel,
  128. messages: [
  129. {
  130. role: "user",
  131. content: prompt,
  132. images: [imageBase64]
  133. }
  134. ],
  135. stream: false
  136. });
  137. return {
  138. content: data?.message?.content ?? ""
  139. };
  140. }