diff --git a/README.md b/README.md index eaf8f71d3..e09f738b6 100644 --- a/README.md +++ b/README.md @@ -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; diff --git a/packages/mcp-client/src/index.ts b/packages/mcp-client/src/index.ts index 1803f7bec..c582e121d 100644 --- a/packages/mcp-client/src/index.ts +++ b/packages/mcp-client/src/index.ts @@ -1,3 +1,4 @@ export * from "./McpClient"; export * from "./Agent"; export type { ChatCompletionInputMessageTool } from "./McpClient"; +export type { ServerConfig } from "./types"; diff --git a/packages/tiny-agents/README.md b/packages/tiny-agents/README.md new file mode 100644 index 000000000..231972999 --- /dev/null +++ b/packages/tiny-agents/README.md @@ -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 +``` + + +## 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 diff --git a/packages/tiny-agents/package.json b/packages/tiny-agents/package.json new file mode 100644 index 000000000..60fdc107c --- /dev/null +++ b/packages/tiny-agents/package.json @@ -0,0 +1,58 @@ +{ + "name": "@huggingface/tiny-agents", + "packageManager": "pnpm@10.10.0", + "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" + } +} diff --git a/packages/tiny-agents/pnpm-lock.yaml b/packages/tiny-agents/pnpm-lock.yaml new file mode 100644 index 000000000..627fc108a --- /dev/null +++ b/packages/tiny-agents/pnpm-lock.yaml @@ -0,0 +1,28 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@huggingface/inference': + specifier: workspace:^ + version: link:../inference + '@huggingface/mcp-client': + specifier: workspace:^ + version: link:../mcp-client + zod: + specifier: ^3.24.4 + version: 3.24.4 + +packages: + + zod@3.24.4: + resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} + +snapshots: + + zod@3.24.4: {} diff --git a/packages/tiny-agents/src/agents/julien-c/flux-schnell-generator/agent.json b/packages/tiny-agents/src/agents/julien-c/flux-schnell-generator/agent.json new file mode 100644 index 000000000..ba78c614d --- /dev/null +++ b/packages/tiny-agents/src/agents/julien-c/flux-schnell-generator/agent.json @@ -0,0 +1,12 @@ +{ + "model": "Qwen/Qwen3-32B", + "provider": "novita", + "servers": [ + { + "type": "sse", + "config": { + "url": "https://evalstate-flux1-schnell.hf.space/gradio_api/mcp/sse" + } + } + ] +} diff --git a/packages/tiny-agents/src/agents/julien-c/local-coder/PROMPT.md b/packages/tiny-agents/src/agents/julien-c/local-coder/PROMPT.md new file mode 100644 index 000000000..e880614bf --- /dev/null +++ b/packages/tiny-agents/src/agents/julien-c/local-coder/PROMPT.md @@ -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. diff --git a/packages/tiny-agents/src/agents/julien-c/local-coder/agent.json b/packages/tiny-agents/src/agents/julien-c/local-coder/agent.json new file mode 100644 index 000000000..31a926839 --- /dev/null +++ b/packages/tiny-agents/src/agents/julien-c/local-coder/agent.json @@ -0,0 +1,13 @@ +{ + "model": "Qwen/Qwen2.5-72B-Instruct", + "provider": "nebius", + "servers": [ + { + "type": "stdio", + "config": { + "command": "npx", + "args": ["@playwright/mcp@latest"] + } + } + ] +} diff --git a/packages/tiny-agents/src/cli.ts b/packages/tiny-agents/src/cli.ts new file mode 100644 index 000000000..8e8f826a6 --- /dev/null +++ b/packages/tiny-agents/src/cli.ts @@ -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 { + 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()), + }); + + let config: z.infer; + 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(); diff --git a/packages/tiny-agents/src/index.ts b/packages/tiny-agents/src/index.ts new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/tiny-agents/src/index.ts @@ -0,0 +1 @@ + diff --git a/packages/tiny-agents/tsconfig.json b/packages/tiny-agents/tsconfig.json new file mode 100644 index 000000000..8274efe5c --- /dev/null +++ b/packages/tiny-agents/tsconfig.json @@ -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"] +}