Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion packages/tasks/src/local-apps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \\

expect(snippet[0].content).toContain(`llama-server -hf bartowski/Llama-3.2-3B-Instruct-GGUF:{{QUANT_TAG}}`);
expect(snippet[1].setup).toContain("npm install -g @mariozechner/pi-coding-agent");
expect(snippet[1].content).toContain(`"id": "Llama-3.2-3B-Instruct-GGUF"`);
expect(snippet[1].content).toContain(`"id": "bartowski/Llama-3.2-3B-Instruct-GGUF:{{QUANT_TAG}}"`);
expect(snippet[2].content).toContain("pi");
});

Expand All @@ -161,6 +161,46 @@ curl -X POST "http://localhost:8000/v1/chat/completions" \\
expect(snippet[2].content).toContain("pi");
});

it("hermes-agent", async () => {
const { snippet: snippetFunc } = LOCAL_APPS["hermes-agent"];
const model: ModelData = {
id: "bartowski/Llama-3.2-3B-Instruct-GGUF",
tags: ["conversational"],
gguf: { total: 1, context_length: 4096, chat_template: "{% if tools %}" },
inference: "",
};
const snippet = snippetFunc(model);

expect(snippet[0].content).toContain(`llama-server -hf bartowski/Llama-3.2-3B-Instruct-GGUF:{{QUANT_TAG}}`);
expect(snippet[1].content).toContain("hermes config set model.provider custom");
expect(snippet[1].content).toContain("hermes config set model.base_url http://127.0.0.1:8080/v1");
expect(snippet[1].content).toContain(
"hermes config set model.default bartowski/Llama-3.2-3B-Instruct-GGUF:{{QUANT_TAG}}",
);
expect(snippet[2].content).toContain("hermes");
});

it("hermes-agent - mlx", async () => {
const { snippet: snippetFunc } = LOCAL_APPS["hermes-agent"];
const model: ModelData = {
id: "mlx-community/Llama-3.2-3B-Instruct-mlx",
tags: ["mlx", "conversational"],
pipeline_tag: "text-generation",
config: {
tokenizer_config: {
chat_template: "{% if tools %}...{% endif %}",
},
},
inference: "",
};
const snippet = snippetFunc(model);

expect(snippet[0].setup).toContain("uv tool install mlx-lm");
expect(snippet[1].content).toContain("hermes config set model.provider custom");
expect(snippet[1].content).toContain("hermes config set model.default mlx-community/Llama-3.2-3B-Instruct-mlx");
expect(snippet[2].content).toContain("hermes");
});

it("docker model runner", async () => {
const { snippet: snippetFunc } = LOCAL_APPS["docker-model-runner"];
const model: ModelData = {
Expand Down
64 changes: 52 additions & 12 deletions packages/tasks/src/local-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ function isUnslothModel(model: ModelData) {
return model.tags.includes("unsloth") || isLlamaCppGgufModel(model);
}

function isToolCallingLocalAgentModel(model: ModelData): boolean {
return (
(isLlamaCppGgufModel(model) || isMlxModel(model)) &&
model.tags.includes("conversational") &&
!!getChatTemplate(model)?.includes("tools")
);
}

function getQuantTag(filepath?: string): string {
const defaultTag = ":{{QUANT_TAG}}";

Expand Down Expand Up @@ -463,12 +471,8 @@ const snippetMlxLm = (model: ModelData): LocalAppSnippet[] => {
];
};

const snippetPi = (model: ModelData, filepath?: string): LocalAppSnippet[] => {
const modelName = model.id.split("/").pop() ?? model.id;
const isMLX = isMlxModel(model);

// Step 1: Server — differs by backend
const serverStep: LocalAppSnippet = isMLX
const getLocalServerStep = (model: ModelData, filepath?: string): LocalAppSnippet => {
return isMlxModel(model)
? {
title: "Start the MLX server",
setup: "# Install MLX LM:\nuv tool install mlx-lm",
Expand All @@ -479,16 +483,21 @@ const snippetPi = (model: ModelData, filepath?: string): LocalAppSnippet[] => {
setup: "# Install llama.cpp:\nbrew install llama.cpp",
content: `# Start a local OpenAI-compatible server:\nllama-server -hf ${model.id}${getQuantTag(filepath)}`,
};
};

const snippetPi = (model: ModelData, filepath?: string): LocalAppSnippet[] => {
const isMLX = isMlxModel(model);
const modelId = isMLX ? model.id : `${model.id}${getQuantTag(filepath)}`;
const serverStep = getLocalServerStep(model, filepath);

// Step 2: Pi config — port and provider name differ
const modelsJson = JSON.stringify(
{
providers: {
[isMLX ? "mlx-lm" : "llama-cpp"]: {
baseUrl: "http://localhost:8080/v1",
api: "openai-completions",
apiKey: "none",
models: [{ id: isMLX ? model.id : modelName }],
models: [{ id: modelId }],
},
},
},
Expand All @@ -510,6 +519,33 @@ const snippetPi = (model: ModelData, filepath?: string): LocalAppSnippet[] => {
];
};

const snippetHermesAgent = (model: ModelData, filepath?: string): LocalAppSnippet[] => {
const modelId = isMlxModel(model) ? model.id : `${model.id}${getQuantTag(filepath)}`;
Comment thread
gary149 marked this conversation as resolved.
const serverStep = getLocalServerStep(model, filepath);

return [
serverStep,
{
title: "Configure Hermes",
setup: [
"# Install Hermes:",
"curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash",
"hermes setup",
].join("\n"),
content: [
"# Point Hermes at the local server:",
"hermes config set model.provider custom",
"hermes config set model.base_url http://127.0.0.1:8080/v1",
`hermes config set model.default ${modelId}`,
].join("\n"),
},
{
title: "Run Hermes",
content: "hermes",
},
];
};

const snippetDockerModelRunner = (model: ModelData, filepath?: string): string => {
// Only add quant tag for GGUF models, not safetensors
const quantTag = isLlamaCppGgufModel(model) ? getQuantTag(filepath) : "";
Expand Down Expand Up @@ -755,12 +791,16 @@ export const LOCAL_APPS = {
prettyLabel: "Pi",
docsUrl: "https://github.com/badlogic/pi-mono",
mainTask: "text-generation",
displayOnModelPage: (model) =>
(isLlamaCppGgufModel(model) || isMlxModel(model)) &&
model.tags.includes("conversational") &&
!!getChatTemplate(model)?.includes("tools"),
displayOnModelPage: isToolCallingLocalAgentModel,
snippet: snippetPi,
},
"hermes-agent": {
prettyLabel: "Hermes Agent",
docsUrl: "https://hermes-agent.nousresearch.com/",
mainTask: "text-generation",
displayOnModelPage: isToolCallingLocalAgentModel,
snippet: snippetHermesAgent,
},
} satisfies Record<string, LocalApp>;

export type LocalAppKey = keyof typeof LOCAL_APPS;
Loading