chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
@@ -46,7 +46,7 @@ describe("llama.cpp extension", () => {
|
||||
process.cwd(),
|
||||
createEventBus(),
|
||||
runtime,
|
||||
"<inline:llama.cpp>",
|
||||
"builtin:llama.cpp",
|
||||
);
|
||||
|
||||
expect(extension.commands.get("llama")?.description).toBe("Manage llama.cpp router models");
|
||||
@@ -91,6 +91,57 @@ describe("llama.cpp extension", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// Regression test for #9528.
|
||||
it("discovers chat-template thinking support for loaded models", async () => {
|
||||
let propsRequests = 0;
|
||||
const { url } = await listen((request, response) => {
|
||||
if (request.url === "/models") {
|
||||
json(response, {
|
||||
data: [{ id: "qwen", status: { value: "loaded" }, meta: { n_ctx: 32768 } }],
|
||||
});
|
||||
return;
|
||||
}
|
||||
const requestUrl = new URL(request.url ?? "", "http://localhost");
|
||||
if (requestUrl.pathname === "/props") {
|
||||
propsRequests++;
|
||||
expect(requestUrl.searchParams.get("model")).toBe("qwen");
|
||||
expect(requestUrl.searchParams.get("autoload")).toBe("false");
|
||||
json(response, { chat_template: "{% if enable_thinking %}think{% endif %}" });
|
||||
return;
|
||||
}
|
||||
response.writeHead(404).end();
|
||||
});
|
||||
|
||||
const controller = createLlamaProvider();
|
||||
await controller.provider.refreshModels?.({
|
||||
credential: { type: "api_key", key: "local", env: { LLAMA_BASE_URL: url } },
|
||||
stored: undefined,
|
||||
publish: async (publication) => {
|
||||
publication.update?.();
|
||||
return true;
|
||||
},
|
||||
allowNetwork: true,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(propsRequests).toBe(1);
|
||||
expect(controller.provider.getModels()).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "qwen",
|
||||
reasoning: true,
|
||||
thinkingLevelMap: {
|
||||
off: "off",
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: "medium",
|
||||
high: null,
|
||||
xhigh: null,
|
||||
},
|
||||
compat: expect.objectContaining({ thinkingFormat: "qwen-chat-template" }),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("persists and restores selectable models for cache-only startup refreshes", async () => {
|
||||
let cachedEntry: ModelsStoreEntry | undefined;
|
||||
const { url } = await listen((request, response) => {
|
||||
@@ -104,6 +155,10 @@ describe("llama.cpp extension", () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.url === "/props?model=loaded&autoload=false") {
|
||||
json(response, {});
|
||||
return;
|
||||
}
|
||||
response.writeHead(404).end();
|
||||
});
|
||||
|
||||
@@ -122,7 +177,12 @@ describe("llama.cpp extension", () => {
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
expect(first.provider.getModels().map((model) => model.id)).toEqual(["loaded", "sleeping"]);
|
||||
expect(cachedEntry?.models.map((model) => model.id)).toEqual(["loaded", "sleeping"]);
|
||||
expect(cachedEntry?.models.map((model) => [model.id, model.api])).toEqual([
|
||||
["loaded", "openai-completions"],
|
||||
["sleeping", "openai-completions"],
|
||||
["loaded", "llama-cpp-classify"],
|
||||
["sleeping", "llama-cpp-classify"],
|
||||
]);
|
||||
|
||||
const second = createLlamaProvider();
|
||||
await second.provider.refreshModels?.({
|
||||
@@ -136,6 +196,89 @@ describe("llama.cpp extension", () => {
|
||||
expect.objectContaining({ id: "loaded", baseUrl: `${url}/v1`, contextWindow: 32768 }),
|
||||
expect.objectContaining({ id: "sleeping", baseUrl: `${url}/v1`, contextWindow: 32768 }),
|
||||
]);
|
||||
expect(second.provider.getAllModels?.().filter((model) => model.type === "classifier")).toEqual([
|
||||
expect.objectContaining({ id: "loaded", api: "llama-cpp-classify", baseUrl: url, contextWindow: 32768 }),
|
||||
expect.objectContaining({ id: "sleeping", api: "llama-cpp-classify", baseUrl: url, contextWindow: 32768 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves cached llama.cpp context for unloaded autoload presets", async () => {
|
||||
let cachedEntry: ModelsStoreEntry | undefined;
|
||||
let loaded = true;
|
||||
let unloadedArgs: string[] | undefined;
|
||||
const { url } = await listen((request, response) => {
|
||||
if (request.url === "/models") {
|
||||
json(response, {
|
||||
data: [
|
||||
loaded
|
||||
? {
|
||||
id: "qwen",
|
||||
status: { value: "loaded" },
|
||||
source: "preset",
|
||||
meta: { n_ctx: 65536, n_ctx_train: 128000 },
|
||||
}
|
||||
: {
|
||||
id: "qwen",
|
||||
status: { value: "unloaded", ...(unloadedArgs && { args: unloadedArgs }) },
|
||||
source: "preset",
|
||||
meta: { n_ctx_train: 128000 },
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.url === "/props?model=qwen&autoload=false") {
|
||||
json(response, {});
|
||||
return;
|
||||
}
|
||||
if (request.url === "/props") {
|
||||
json(response, { role: "router", models_autoload: true });
|
||||
return;
|
||||
}
|
||||
response.writeHead(404).end();
|
||||
});
|
||||
|
||||
const publish = async (publication: ModelsPublication): Promise<boolean> => {
|
||||
if (publication.persist === null) cachedEntry = undefined;
|
||||
else if (publication.persist !== undefined) cachedEntry = structuredClone(publication.persist);
|
||||
publication.update?.();
|
||||
return true;
|
||||
};
|
||||
const storedContextWindows = () =>
|
||||
cachedEntry?.models.map((model) => ("contextWindow" in model ? model.contextWindow : undefined));
|
||||
const credential = { type: "api_key" as const, key: "local", env: { LLAMA_BASE_URL: url } };
|
||||
|
||||
const first = createLlamaProvider();
|
||||
await first.provider.refreshModels?.({
|
||||
credential,
|
||||
stored: cachedEntry,
|
||||
publish,
|
||||
allowNetwork: true,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
expect(storedContextWindows()).toEqual([65536, 65536]);
|
||||
|
||||
loaded = false;
|
||||
const second = createLlamaProvider();
|
||||
await second.provider.refreshModels?.({
|
||||
credential,
|
||||
stored: cachedEntry,
|
||||
publish,
|
||||
allowNetwork: true,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
expect(second.provider.getModels()).toEqual([expect.objectContaining({ id: "qwen", contextWindow: 65536 })]);
|
||||
expect(storedContextWindows()).toEqual([65536, 65536]);
|
||||
|
||||
unloadedArgs = ["llama-server", "--ctx-size", "32768"];
|
||||
await second.provider.refreshModels?.({
|
||||
credential,
|
||||
stored: cachedEntry,
|
||||
publish,
|
||||
allowNetwork: true,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
expect(storedContextWindows()).toEqual([32768, 32768]);
|
||||
});
|
||||
|
||||
it("exposes unloaded presets only when router autoload is enabled", async () => {
|
||||
@@ -179,7 +322,234 @@ describe("llama.cpp extension", () => {
|
||||
|
||||
expect(propsRequests).toBe(1);
|
||||
expect(controller.provider.getModels().map((model) => model.id)).toEqual(["preset"]);
|
||||
expect(cachedEntry?.models.map((model) => model.id)).toEqual(["preset"]);
|
||||
expect(cachedEntry?.models.map((model) => [model.id, model.api])).toEqual([
|
||||
["preset", "openai-completions"],
|
||||
["preset", "llama-cpp-classify"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("classifies with selectable models through llama-server", async () => {
|
||||
const paths: string[] = [];
|
||||
const { url } = await listen((request, response) => {
|
||||
let body = "";
|
||||
request.on("data", (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
request.on("end", () => {
|
||||
paths.push(request.url ?? "");
|
||||
const payload = JSON.parse(body) as { model: string; content?: string };
|
||||
expect(payload.model).toBe("qwen");
|
||||
if (request.url === "/tokenize") {
|
||||
json(response, { tokens: [...(payload.content ?? "")].map((char) => char.codePointAt(0)) });
|
||||
} else if (request.url === "/apply-template") {
|
||||
json(response, { prompt: "<|im_start|>assistant\n" });
|
||||
} else if (request.url === "/completion") {
|
||||
json(response, {
|
||||
completion_probabilities: [
|
||||
{
|
||||
top_logprobs: [
|
||||
{ id: 66, token: "B", logprob: -0.1 },
|
||||
{ id: 65, token: "A", logprob: -2.4 },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
response.writeHead(404).end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const controller = createLlamaProvider();
|
||||
controller.setCatalog([{ id: "qwen", status: { value: "loaded" } }], url);
|
||||
const classifier = controller.provider.getAllModels?.().find((model) => model.type === "classifier");
|
||||
if (classifier?.type !== "classifier") throw new Error("missing classifier model");
|
||||
|
||||
// Provider auth resolves the OpenAI-compatible /v1 URL, which replaces the model's base URL.
|
||||
const result = await controller.provider.classify!(
|
||||
{ ...classifier, baseUrl: `${url}/v1` },
|
||||
{
|
||||
state: { message: "The build is red again." },
|
||||
questions: {
|
||||
kind: { type: "choice", instructions: "What is this about?", criteria: { billing: "", ci: "" } },
|
||||
},
|
||||
},
|
||||
{ apiKey: "local" },
|
||||
);
|
||||
|
||||
expect(result.errorMessage).toBeUndefined();
|
||||
expect(result.answers.kind).toMatchObject({ type: "choice", choice: "ci" });
|
||||
expect(paths).toContain("/completion");
|
||||
});
|
||||
|
||||
it("exposes decision models reported by the catalog as native System One classifiers", async () => {
|
||||
const propsModels: string[] = [];
|
||||
const systemOneRequests: unknown[] = [];
|
||||
const { url } = await listen((request, response) => {
|
||||
let body = "";
|
||||
request.on("data", (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
request.on("end", () => {
|
||||
const requestUrl = new URL(request.url ?? "", "http://localhost");
|
||||
if (requestUrl.pathname === "/models") {
|
||||
json(response, {
|
||||
data: [
|
||||
{
|
||||
id: "qwen",
|
||||
status: { value: "loaded" },
|
||||
architecture: { input_modalities: ["text"], output_modalities: ["text"] },
|
||||
meta: { n_ctx: 32768 },
|
||||
},
|
||||
{
|
||||
id: "kev",
|
||||
status: { value: "loaded" },
|
||||
architecture: { input_modalities: ["text"], output_modalities: ["decisions"] },
|
||||
meta: { n_ctx: 8192 },
|
||||
},
|
||||
{
|
||||
id: "laya",
|
||||
status: { value: "sleeping" },
|
||||
architecture: { input_modalities: ["text"], output_modalities: ["decisions"] },
|
||||
},
|
||||
// Servers before llama.cpp 0.6.0 may omit architecture.
|
||||
{ id: "legacy", status: { value: "loaded" } },
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (requestUrl.pathname === "/props") {
|
||||
propsModels.push(requestUrl.searchParams.get("model") ?? "");
|
||||
json(response, {});
|
||||
return;
|
||||
}
|
||||
if (requestUrl.pathname === "/v1/systemone") {
|
||||
const payload = JSON.parse(body) as { model: string; questions: Record<string, unknown> };
|
||||
systemOneRequests.push(payload);
|
||||
json(response, {
|
||||
model: payload.model,
|
||||
answers: Object.fromEntries(
|
||||
Object.keys(payload.questions).map((id) => [id, { type: "noul", noul: 0.82 }]),
|
||||
),
|
||||
usage: { input_tokens: 42, output_tokens: 0 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
response.writeHead(404).end();
|
||||
});
|
||||
});
|
||||
|
||||
let cachedEntry: ModelsStoreEntry | undefined;
|
||||
const publish = async (publication: ModelsPublication): Promise<boolean> => {
|
||||
if (publication.persist !== undefined && publication.persist !== null) {
|
||||
cachedEntry = structuredClone(publication.persist);
|
||||
}
|
||||
publication.update?.();
|
||||
return true;
|
||||
};
|
||||
const credential = { type: "api_key" as const, key: "local", env: { LLAMA_BASE_URL: url } };
|
||||
const controller = createLlamaProvider();
|
||||
await controller.provider.refreshModels?.({
|
||||
credential,
|
||||
stored: undefined,
|
||||
publish,
|
||||
allowNetwork: true,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
|
||||
// The catalog identifies decision models, so a refresh neither probes them nor reads their chat template.
|
||||
expect(systemOneRequests).toEqual([]);
|
||||
expect(propsModels.sort()).toEqual(["legacy", "qwen"]);
|
||||
expect(controller.provider.getModels().map((model) => model.id)).toEqual(["qwen", "legacy"]);
|
||||
expect(cachedEntry?.models.map((model) => [model.id, model.api, model.baseUrl])).toEqual([
|
||||
["qwen", "openai-completions", `${url}/v1`],
|
||||
["legacy", "openai-completions", `${url}/v1`],
|
||||
["qwen", "llama-cpp-classify", url],
|
||||
["kev", "typesafe-system-one", `${url}/v1`],
|
||||
["laya", "typesafe-system-one", `${url}/v1`],
|
||||
["legacy", "llama-cpp-classify", url],
|
||||
]);
|
||||
|
||||
const kev = controller.provider.getAllModels?.().find((model) => model.id === "kev");
|
||||
if (kev?.type !== "classifier") throw new Error("missing decision classifier model");
|
||||
expect(kev.contextWindow).toBe(8192);
|
||||
const result = await controller.provider.classify!(
|
||||
kev,
|
||||
{
|
||||
state: { message: "I was charged twice." },
|
||||
questions: {
|
||||
angry: {
|
||||
type: "bool",
|
||||
instructions: "Is the customer angry?",
|
||||
criteria: { true: "angry", false: "calm" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{ apiKey: "local" },
|
||||
);
|
||||
expect(result.errorMessage).toBeUndefined();
|
||||
expect(result.answers.angry).toEqual({ type: "bool", probability: 0.82 });
|
||||
expect(result.usage).toMatchObject({ input: 42, output: 0 });
|
||||
expect(systemOneRequests).toEqual([
|
||||
{
|
||||
model: "kev",
|
||||
state: { message: "I was charged twice." },
|
||||
questions: {
|
||||
angry: {
|
||||
type: "noul",
|
||||
instructions: "Is the customer angry?",
|
||||
criteria: { true: "angry", false: "calm" },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// A cache-only startup restores decision models with their native API.
|
||||
const restored = createLlamaProvider();
|
||||
await restored.provider.refreshModels?.({
|
||||
credential,
|
||||
stored: cachedEntry,
|
||||
publish,
|
||||
allowNetwork: false,
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
expect(restored.provider.getModels().map((model) => model.id)).toEqual(["qwen", "legacy"]);
|
||||
expect(
|
||||
restored.provider
|
||||
.getAllModels?.()
|
||||
.filter((model) => model.type === "classifier")
|
||||
.map((model) => [model.id, model.api]),
|
||||
).toEqual([
|
||||
["qwen", "llama-cpp-classify"],
|
||||
["kev", "typesafe-system-one"],
|
||||
["laya", "typesafe-system-one"],
|
||||
["legacy", "llama-cpp-classify"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("lists decision models that also output text for chat", () => {
|
||||
const controller = createLlamaProvider();
|
||||
controller.setCatalog(
|
||||
[
|
||||
{ id: "decide", status: { value: "sleeping" }, architecture: { output_modalities: ["decisions"] } },
|
||||
{
|
||||
id: "hybrid",
|
||||
status: { value: "loaded" },
|
||||
architecture: { output_modalities: ["text", "decisions"] },
|
||||
},
|
||||
],
|
||||
"http://localhost:8080",
|
||||
);
|
||||
expect(controller.provider.getModels().map((model) => model.id)).toEqual(["hybrid"]);
|
||||
expect(
|
||||
controller.provider
|
||||
.getAllModels?.()
|
||||
.filter((model) => model.type === "classifier")
|
||||
.map((model) => [model.id, model.api]),
|
||||
).toEqual([
|
||||
["decide", "typesafe-system-one"],
|
||||
["hybrid", "typesafe-system-one"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("hides unloaded presets when router autoload is disabled", async () => {
|
||||
|
||||
Reference in New Issue
Block a user