Files
openhuman/scripts/mock-api/routes/llm/shared.mjs
T

154 lines
4.6 KiB
JavaScript

import { behavior, parseBehaviorJson } from "../../state.mjs";
const DEFAULT_MODELS = [
{ id: "e2e-mock-model", family: "chat", owned_by: "openhuman-mock" },
{
id: "openhuman-reasoning-mock",
family: "reasoning",
owned_by: "openhuman-mock",
},
{ id: "o3-mini-mock", family: "reasoning", owned_by: "openhuman-mock" },
{
id: "openhuman-agentic-mock",
family: "agentic",
owned_by: "openhuman-mock",
},
{ id: "gpt-4.1-agent-mock", family: "agentic", owned_by: "openhuman-mock" },
{ id: "openhuman-coder-mock", family: "coding", owned_by: "openhuman-mock" },
{ id: "gpt-5-codex-mock", family: "coding", owned_by: "openhuman-mock" },
{
id: "openhuman-summary-mock",
family: "summarization",
owned_by: "openhuman-mock",
},
{
id: "memory-summarizer-mock",
family: "summarization",
owned_by: "openhuman-mock",
},
];
export function listMockLlmModels() {
const configured = parseBehaviorJson("llmModelCatalog", null);
const catalog =
Array.isArray(configured) && configured.length > 0
? configured
: DEFAULT_MODELS;
return catalog.map((entry, index) => ({
id: String(entry.id || `mock-model-${index + 1}`),
object: "model",
created: entry.created || 1_710_000_000,
owned_by: entry.owned_by || entry.ownedBy || "openhuman-mock",
family:
entry.family ||
detectModelFamily({
model: String(entry.id || `mock-model-${index + 1}`),
}),
}));
}
export function headerValue(headers, name) {
const raw = headers?.[name];
if (Array.isArray(raw)) return raw.join(", ");
return typeof raw === "string" ? raw : "";
}
export function pickProbeText(parsedBody) {
if (!parsedBody || !Array.isArray(parsedBody.messages)) return "";
for (let i = parsedBody.messages.length - 1; i >= 0; i -= 1) {
const m = parsedBody.messages[i];
if (!m || typeof m !== "object") continue;
if (m.role === "user" || m.role === "tool") {
if (typeof m.content === "string") return m.content;
if (Array.isArray(m.content)) {
return m.content
.filter((c) => c && c.type === "text" && typeof c.text === "string")
.map((c) => c.text)
.join(" ");
}
}
}
return "";
}
export function normalizeMessageContent(content) {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
return content
.filter(
(item) => item && item.type === "text" && typeof item.text === "string",
)
.map((item) => item.text)
.join(" ");
}
export function collectMessagesByRole(parsedBody, role) {
if (!Array.isArray(parsedBody?.messages)) return [];
return parsedBody.messages
.filter((message) => message?.role === role)
.map((message) => ({
...message,
normalizedContent: normalizeMessageContent(message.content),
}));
}
export function latestRoleMessage(parsedBody, role) {
const matches = collectMessagesByRole(parsedBody, role);
return matches[matches.length - 1] || null;
}
export function resolveThreadKey(ctx) {
const { parsedBody, req } = ctx;
const headers = req?.headers || {};
const body = parsedBody || {};
return (
headerValue(headers, "x-mock-thread-id") ||
headerValue(headers, "x-thread-id") ||
body.mockThreadId ||
body.threadId ||
body.conversationId ||
body.sessionId ||
body.metadata?.thread_id ||
body.metadata?.conversation_id ||
body.metadata?.session_id ||
body.user ||
null
);
}
function overrideFamilyForModel(model) {
const overrides = parseBehaviorJson("llmModelFamilyOverrides", []);
if (!Array.isArray(overrides)) return null;
for (const entry of overrides) {
if (!entry || typeof entry.family !== "string") continue;
if (typeof entry.model === "string" && entry.model === model) {
return entry.family;
}
if (
typeof entry.match === "string" &&
model.includes(entry.match.toLowerCase())
) {
return entry.family;
}
}
return null;
}
export function detectModelFamily({ model = "", parsedBody } = {}) {
const lower = String(model || "").toLowerCase();
const override = overrideFamilyForModel(lower);
if (override) return override;
if (/codex|coder|code|devstral|program|repair/.test(lower)) return "coding";
if (/summary|summar|memory|brief|distill|extract/.test(lower)) {
return "summarization";
}
if (/agent|tool|operator|workflow|computer|action/.test(lower)) {
return "agentic";
}
if (/reason|thinking|o1|o3|o4|r1|deepseek/.test(lower)) return "reasoning";
if (Array.isArray(parsedBody?.tools) && parsedBody.tools.length > 0) {
return "agentic";
}
return behavior().llmDefaultFamily || "chat";
}