mcard-js
Version:
MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers
403 lines (398 loc) • 12.7 kB
JavaScript
import {
BaseLLMProvider,
DEFAULT_PROVIDER,
LLMConfig,
LLM_PROVIDERS,
OllamaProvider
} from "./chunk-QXPHMSPA.js";
import {
IO
} from "./chunk-MPMRBT5R.js";
import {
Either
} from "./chunk-2KADE3SE.js";
// src/ptr/llm/providers/WebLLMProvider.ts
var WebLLMProvider = class extends BaseLLMProvider {
provider_name = "webllm";
config;
engine = null;
current_model = null;
initialization_promise = null;
constructor() {
super();
this.config = LLM_PROVIDERS["webllm"];
}
async _get_engine(model_id) {
if (this.engine && this.current_model === model_id) {
return Either.right(this.engine);
}
if (this.initialization_promise) {
await this.initialization_promise;
if (this.engine && this.current_model === model_id) {
return Either.right(this.engine);
}
}
this.initialization_promise = (async () => {
try {
if (typeof window === "undefined") {
throw new Error("WebLLM only supports browser environments.");
}
let webllm = window.webllm;
if (!webllm) {
try {
webllm = await import("@mlc-ai/web-llm");
} catch (e) {
}
}
if (!webllm) {
throw new Error("WebLLM library not found. Please include @mlc-ai/web-llm or add script tag.");
}
if (!this.engine) {
this.engine = await webllm.CreateMLCEngine(model_id, {
initProgressCallback: (report) => {
console.debug(`[WebLLM] ${report.text}`);
}
});
} else {
await this.engine.reload(model_id);
}
this.current_model = model_id;
} catch (e) {
this.engine = null;
this.current_model = null;
throw e;
}
})();
try {
await this.initialization_promise;
return Either.right(this.engine);
} catch (e) {
this.initialization_promise = null;
return Either.left(`WebLLM init failed: ${e.message || e}`);
}
}
async complete(prompt, params) {
const model = params.model || this.config.default_model;
const engineResult = await this._get_engine(model);
if (engineResult.isLeft) return Either.left(engineResult.left);
const engine = engineResult.right;
try {
const completion = await engine.chat.completions.create({
messages: [{ role: "user", content: prompt }],
temperature: params.temperature,
max_tokens: params.max_tokens,
top_p: params.top_p,
stream: false
});
const content = completion.choices[0]?.message?.content || "";
return Either.right(content);
} catch (e) {
return Either.left(`WebLLM completion error: ${e.message || e}`);
}
}
async chat(messages, params) {
const model = params.model || this.config.default_model;
const engineResult = await this._get_engine(model);
if (engineResult.isLeft) return Either.left(engineResult.left);
const engine = engineResult.right;
try {
const completion = await engine.chat.completions.create({
messages,
temperature: params.temperature,
max_tokens: params.max_tokens,
top_p: params.top_p,
stream: false
});
const choice = completion.choices[0];
return Either.right({
content: choice?.message?.content || "",
role: choice?.message?.role || "assistant",
model,
usage: completion.usage
});
} catch (e) {
return Either.left(`WebLLM chat error: ${e.message || e}`);
}
}
async validate_connection() {
if (typeof window === "undefined") return false;
if (window.webllm) return true;
try {
await import("@mlc-ai/web-llm");
return true;
} catch {
return false;
}
}
async list_models() {
return Either.right(this.config.available_models);
}
};
// src/ptr/llm/providers/MLCLLMProvider.ts
import * as http from "http";
import * as https from "https";
var MLCLLMProvider = class extends BaseLLMProvider {
provider_name = "mlc-llm";
base_url;
timeout;
config;
constructor(base_url = null, timeout = 120) {
super();
this.config = LLM_PROVIDERS["mlc-llm"];
this.base_url = (base_url || this.config.base_url).replace(/\/$/, "");
this.timeout = timeout * 1e3;
}
async _fetch_json(endpoint, options) {
if (typeof globalThis.fetch === "function") {
try {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(`${this.base_url}${endpoint}`, {
...options,
signal: controller.signal
});
clearTimeout(id);
if (!response.ok) {
return Either.left(`HTTP error ${response.status}: ${await response.text()}`);
}
const data = await response.json();
return Either.right(data);
} catch (e) {
return Either.left(`Connection error: ${e.message}`);
}
}
return this._node_request(endpoint, options);
}
_node_request(endpoint, options) {
const urlStr = `${this.base_url}${endpoint}`;
const url = new URL(urlStr);
const isHttps = url.protocol === "https:";
const client = isHttps ? https : http;
const reqOptions = {
method: options.method || "GET",
headers: options.headers || {},
timeout: this.timeout
};
return new Promise((resolve) => {
const req = client.request(url, reqOptions, (res) => {
let body = "";
res.on("data", (chunk) => body += chunk);
res.on("end", () => {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
try {
resolve(Either.right(JSON.parse(body)));
} catch (e) {
resolve(Either.left(`Parse error: ${e}`));
}
} else {
resolve(Either.left(`HTTP Error ${res.statusCode}: ${body}`));
}
});
});
req.on("error", (e) => resolve(Either.left(e.message)));
req.on("timeout", () => {
req.destroy();
resolve(Either.left("Request timed out"));
});
if (options.body) {
req.write(options.body);
}
req.end();
});
}
async complete(prompt, params) {
const data = {
model: params.model || this.config.default_model,
prompt,
max_tokens: params.max_tokens,
temperature: params.temperature,
top_p: params.top_p,
stream: false
};
const result = await this._fetch_json(this.config.api_path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (result.isLeft) return Either.left(result.left);
const response = result.right;
if (response.choices && response.choices.length > 0) {
return Either.right(response.choices[0].text || "");
}
return Either.left(`Unexpected response format: ${JSON.stringify(response)}`);
}
async chat(messages, params) {
const data = {
model: params.model || this.config.default_model,
messages,
max_tokens: params.max_tokens,
temperature: params.temperature,
top_p: params.top_p,
stream: false
};
const result = await this._fetch_json(this.config.chat_path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (result.isLeft) return Either.left(result.left);
const response = result.right;
if (response.choices && response.choices.length > 0) {
const message = response.choices[0].message;
return Either.right({
content: message.content,
role: message.role,
model: response.model,
usage: response.usage
});
}
return Either.left(`Unexpected response format: ${JSON.stringify(response)}`);
}
async validate_connection() {
const result = await this._fetch_json(this.config.models_path, { method: "GET" });
return result.isRight;
}
async list_models() {
const result = await this._fetch_json(this.config.models_path, { method: "GET" });
if (result.isLeft) return Either.left(result.left);
const response = result.right;
if (response.data && Array.isArray(response.data)) {
return Either.right(response.data.map((m) => m.id));
}
return Either.left("Invalid models response");
}
};
// src/ptr/llm/LLMRuntime.ts
function get_provider(provider_name = DEFAULT_PROVIDER, base_url = null, timeout = 120) {
if (provider_name === "ollama") {
return new OllamaProvider(base_url, timeout);
}
if (provider_name === "webllm") {
return new WebLLMProvider();
}
if (provider_name === "mlc-llm") {
return new MLCLLMProvider(base_url, timeout);
}
throw new Error(`Unknown provider: ${provider_name}`);
}
var LLMRuntime = class {
provider_name;
_provider = null;
constructor(provider_name = DEFAULT_PROVIDER) {
this.provider_name = provider_name;
}
get provider() {
if (!this._provider) {
this._provider = get_provider(this.provider_name);
}
return this._provider;
}
async execute(codeOrPath, context, config, chapterDir) {
let configCtx = {};
if (typeof context === "object" && context !== null) {
configCtx = context;
}
const concrete = config;
const llmConfig = LLMConfig.from_concrete(concrete, configCtx);
if (llmConfig.provider !== this.provider_name) {
this._provider = get_provider(llmConfig.provider, llmConfig.endpoint_url, llmConfig.timeout);
}
let prompt = "";
if (typeof context === "string") {
prompt = context;
} else {
prompt = JSON.stringify(context);
}
let result;
if (llmConfig.system_prompt) {
result = await this._execute_chat(prompt, llmConfig);
} else {
result = await this._execute_completion(prompt, llmConfig);
}
if (result.isLeft) {
return `Error: ${result.left}`;
}
return this._format_response(result.right, llmConfig);
}
async _execute_completion(prompt, config) {
const params = config.to_provider_params();
return this.provider.complete(prompt, params);
}
async _execute_chat(prompt, config) {
const messages = [];
if (config.system_prompt) {
messages.push({ role: "system", content: config.system_prompt });
}
messages.push({ role: "user", content: prompt });
if (config.assistant_instruction) {
messages.push({ role: "assistant", content: config.assistant_instruction });
}
const params = config.to_provider_params();
return this.provider.chat(messages, params);
}
_format_response(response, config) {
let content = response;
if (response && typeof response === "object" && "content" in response) {
content = response.content;
}
if (config.response_format === "json") {
try {
if (typeof content === "string") {
const start = content.indexOf("{");
const end = content.lastIndexOf("}") + 1;
if (start >= 0 && end > start) {
return JSON.parse(content.substring(start, end));
}
}
return content;
} catch (e) {
return content;
}
}
return content;
}
};
function promptMonad(prompt, config = {}) {
return IO.of(async () => {
try {
const llmConfig = new LLMConfig(config);
const runtime = new LLMRuntime(llmConfig.provider);
const params = llmConfig.to_provider_params();
return runtime.provider.complete(prompt, params);
} catch (e) {
return Either.left(`LLM execution failed: ${e}`);
}
});
}
function chatMonad(messages = null, prompt = null, system_prompt = "", config = {}) {
return IO.of(async () => {
try {
const configData = { ...config };
if (system_prompt) configData.system_prompt = system_prompt;
const llmConfig = new LLMConfig(configData);
const runtime = new LLMRuntime(llmConfig.provider);
const msgs = messages ? [...messages] : [];
if (msgs.length === 0) {
if (llmConfig.system_prompt) {
msgs.push({ role: "system", content: llmConfig.system_prompt });
}
if (prompt) {
msgs.push({ role: "user", content: prompt });
}
if (llmConfig.assistant_instruction) {
msgs.push({ role: "assistant", content: llmConfig.assistant_instruction });
}
}
const params = llmConfig.to_provider_params();
return runtime.provider.chat(msgs, params);
} catch (e) {
return Either.left(`LLM chat failed: ${e}`);
}
});
}
export {
get_provider,
LLMRuntime,
promptMonad,
chatMonad
};