Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ const agent = new Agent({
],
});


await agent.loadTools();

for await (const chunk of agent.run("What are the top 5 trending models on Hugging Face?")) {
if ("choices" in chunk) {
const delta = chunk.choices[0]?.delta;
Expand Down
1 change: 1 addition & 0 deletions packages/mcp-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./McpClient";
export * from "./Agent";
export type { ChatCompletionInputMessageTool } from "./McpClient";
export type { ServerConfig } from "./types";
71 changes: 71 additions & 0 deletions packages/tiny-agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# @huggingface/tiny-agents

A lightweight, composable agent framework for AI applications built on Hugging Face's JS stack.

## Installation

```bash
npm install @huggingface/tiny-agents
# or
yarn add @huggingface/tiny-agents
# or
pnpm add @huggingface/tiny-agents
```

## CLI Usage

```bash
npx @huggingface/tiny-agents [command] "agent/id"

```

```
Usage:
tiny-agents [flags]
tiny-agents run "agent/id"
tiny-agents serve "agent/id"

Available Commands:
run Run the Agent in command-line
serve Run the Agent as an OpenAI-compatible HTTP server
Copy link
Collaborator

@evalstate evalstate May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

being able to run OR serve from the same config is 👌

```


## Programmatic Usage

```typescript
import { Agent } from '@huggingface/tiny-agents';

const HF_TOKEN = "hf_...";

// Create an Agent
const agent = new Agent({
provider: "auto",
model: "Qwen/Qwen2.5-72B-Instruct",
apiKey: HF_TOKEN,
servers: [
{
// Playwright MCP
command: "npx",
args: ["@playwright/mcp@latest"],
},
],
});

await agent.loadTools();

// Use the Agent
for await (const chunk of agent.run("What are the top 5 trending models on Hugging Face?")) {
if ("choices" in chunk) {
const delta = chunk.choices[0]?.delta;
if (delta.content) {
console.log(delta.content);
}
}
}
```


## License

MIT
58 changes: 58 additions & 0 deletions packages/tiny-agents/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@huggingface/tiny-agents",
"packageManager": "[email protected]",
"version": "0.1.0",
"description": "Lightweight, composable agents for AI applications",
"repository": "https://github.com/huggingface/huggingface.js.git",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"bin": {
"tiny-agents": "./dist/cli.js"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"engines": {
"node": ">=18"
},
"source": "index.ts",
"scripts": {
"lint": "eslint --quiet --fix --ext .cjs,.ts .",
"lint:check": "eslint --ext .cjs,.ts .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepublishOnly": "pnpm run build",
"build": "tsup src/index.ts --format cjs,esm --clean && tsc --emitDeclarationOnly --declaration",
"prepare": "pnpm run build",
"test": "vitest run",
"check": "tsc",
"cli": "tsx src/cli.ts"
},
"files": [
"src",
"dist",
"tsconfig.json"
],
"keywords": [
"huggingface",
"agent",
"ai",
"llm",
"tiny-agent"
],
"author": "Hugging Face",
"license": "MIT",
"dependencies": {
"@huggingface/inference": "workspace:^",
"@huggingface/mcp-client": "workspace:^",
"zod": "^3.24.4"
}
}
28 changes: 28 additions & 0 deletions packages/tiny-agents/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"model": "Qwen/Qwen3-32B",
"provider": "novita",
Comment on lines +2 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to try tiny-agent but got an error:

Error: model features function calling not support
    at innerStreamingRequest (/home/wauplin/projects/huggingface.js/packages/inference/dist/index.cjs:1839:15)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async chatCompletionStream (/home/wauplin/projects/huggingface.js/packages/inference/dist/index.cjs:2115:3)
    at async Agent.processSingleTurnWithTools (/home/wauplin/projects/huggingface.js/packages/mcp-client/dist/src/index.js:198:22)
    at async Agent.run (/home/wauplin/projects/huggingface.js/packages/mcp-client/dist/src/index.js:328:9)
    at mainCliLoop (/home/wauplin/projects/huggingface.js/packages/tiny-agents/src/lib/mainCliLoop.ts:53:20)
    at main (/home/wauplin/projects/huggingface.js/packages/tiny-agents/src/cli.ts:146:3)

Node.js v20.11.1
 ELIFECYCLE  Command failed with exit code 7.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"model": "Qwen/Qwen3-32B",
"provider": "novita",
"model": "Qwen/Qwen2.5-72B-Instruct",
"provider": "nebius",

changing to this made it work for me

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel free to open a PR to add your own agent @Wauplin 😁

Does the other one work for you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opened a separate PR: #1466

"servers": [
{
"type": "sse",
"config": {
"url": "https://evalstate-flux1-schnell.hf.space/gradio_api/mcp/sse"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
You are an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved, or if you need more info from the user to solve the problem.

If you are not sure about anything pertaining to the user’s request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.

You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.
13 changes: 13 additions & 0 deletions packages/tiny-agents/src/agents/julien-c/local-coder/agent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
Copy link
Collaborator

@evalstate evalstate May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://gitmcp.io/ <-- for demo this is a nice remote server for a coding agent. (it makes any github repo an MCP server!). (just announced it now supports streaming http!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good can you add it as a suggestion so i can commit it?

"model": "Qwen/Qwen2.5-72B-Instruct",
"provider": "nebius",
"servers": [
{
"type": "stdio",
"config": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
]
}
117 changes: 117 additions & 0 deletions packages/tiny-agents/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env node
import { dirname, join } from "node:path";
import { parseArgs } from "node:util";
import { readFile } from "node:fs/promises";
import { z } from "zod";
import { PROVIDERS_OR_POLICIES } from "@huggingface/inference";
import { Agent } from "@huggingface/mcp-client";
import type { ServerConfig } from "@huggingface/mcp-client";
import { version as packageVersion } from "../package.json";

const USAGE_HELP = `
Usage:
tiny-agents [flags]
tiny-agents run "agent/id"
tiny-agents serve "agent/id"

Available Commands:
run Run the Agent in command-line
serve Run the Agent as an OpenAI-compatible HTTP server

Flags:
-h, --help help for tiny-agents
-v, --version Show version information
`.trim();

const CLI_COMMANDS = ["run", "serve"] as const;
function isValidCommand(command: string): command is (typeof CLI_COMMANDS)[number] {
return (CLI_COMMANDS as unknown as string[]).includes(command);
}

const FILENAME_CONFIG = "agent.json";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const FILENAME_PROMPT = "PROMPT.md";

async function loadConfigFrom(loadFrom: string): Promise<string> {
try {
/// First try it as a local directory path, then we will try as a path inside the repo itself
return await readFile(loadFrom, { encoding: "utf8" });
} catch {
const srcDir = dirname(__filename);
const configPath = join(srcDir, "agents", loadFrom, FILENAME_CONFIG);
try {
return await readFile(configPath, { encoding: "utf8" });
} catch {
console.error(`Config file not found! Loading from the HF Hub is not implemented yet`);
process.exit(1);
}
}
}

async function main() {
const {
values: { help, version },
positionals,
} = parseArgs({
options: {
help: {
type: "boolean",
short: "h",
},
version: {
type: "boolean",
short: "v",
},
},
allowPositionals: true,
});
if (version) {
console.log(packageVersion);
process.exit(0);
}
const command = positionals[0];
const loadFrom = positionals[1];
if (help) {
console.log(USAGE_HELP);
process.exit(0);
}
if (positionals.length !== 2 || !isValidCommand(command)) {
console.error(`You need to call run or serve, followed by an agent id (local path or Hub identifier).`);
console.log(USAGE_HELP);
process.exit(1);
}

if (command === "serve") {
console.error(`Serve is not implemented yet, coming soon!`);
process.exit(1);
} else {
const configJson = await loadConfigFrom(loadFrom);

const ConfigSchema = z.object({
model: z.string(),
provider: z.enum(PROVIDERS_OR_POLICIES),
servers: z.array(z.custom<ServerConfig>()),
});

let config: z.infer<typeof ConfigSchema>;
try {
const parsedConfig = JSON.parse(configJson);
config = ConfigSchema.parse(parsedConfig);
} catch (error) {
console.error("Invalid configuration file:", error instanceof Error ? error.message : error);
process.exit(1);
}
const agent = new Agent({
provider: config.provider,
model: config.model,
apiKey: process.env.HF_TOKEN ?? "",
servers: config.servers,
});

console.log(agent);

// TODO: hook main loop from mcp-client/cli.ts
}
}

main();
1 change: 1 addition & 0 deletions packages/tiny-agents/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

21 changes: 21 additions & 0 deletions packages/tiny-agents/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"lib": ["ES2022", "DOM"],
"module": "CommonJS",
"moduleResolution": "node",
"target": "ES2022",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"skipLibCheck": true,
"noImplicitOverride": true,
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"resolveJsonModule": true
},
"include": ["src", "test"],
"exclude": ["dist"]
}