feat: add Gemma 4 local model example with tool-calling
Add examples/08-gemma4-local.ts demonstrating a pure-local multi-agent team using Gemma 4 via Ollama — zero API cost. Two agents (researcher + summarizer) collaborate through a task pipeline with bash, file_write, and file_read tools. Verified on gemma4:e2b with Ollama 0.20.0-rc1. Update both READMEs: add example 08 to the examples table and note Gemma 4 as a verified local model with tool-calling support.
This commit is contained in:
parent
d8a217106f
commit
f9fcac0c38
|
|
@ -121,6 +121,7 @@ npx tsx examples/01-single-agent.ts
|
|||
| [05 — Copilot](examples/05-copilot-test.ts) | GitHub Copilot as an LLM provider |
|
||||
| [06 — Local Model](examples/06-local-model.ts) | Ollama + Claude in one pipeline via `baseURL` (works with vLLM, LM Studio, etc.) |
|
||||
| [07 — Fan-Out / Aggregate](examples/07-fan-out-aggregate.ts) | `runParallel()` MapReduce — 3 analysts in parallel, then synthesize |
|
||||
| [08 — Gemma 4 Local](examples/08-gemma4-local.ts) | Pure-local Gemma 4 agent team with tool-calling — zero API cost |
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
@ -182,6 +183,8 @@ npx tsx examples/01-single-agent.ts
|
|||
| GitHub Copilot | `provider: 'copilot'` | `GITHUB_TOKEN` | Verified |
|
||||
| Ollama / vLLM / LM Studio | `provider: 'openai'` + `baseURL` | — | Verified |
|
||||
|
||||
Verified local models with tool-calling: **Gemma 4** (see [example 08](examples/08-gemma4-local.ts)).
|
||||
|
||||
Any OpenAI-compatible API should work via `provider: 'openai'` + `baseURL` (DeepSeek, Groq, Mistral, Qwen, MiniMax, etc.). These providers have not been fully verified yet — contributions welcome via [#25](https://github.com/JackChen-me/open-multi-agent/issues/25).
|
||||
|
||||
## Contributing
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ npx tsx examples/01-single-agent.ts
|
|||
| [05 — Copilot](examples/05-copilot-test.ts) | GitHub Copilot 作为 LLM 提供者 |
|
||||
| [06 — 本地模型](examples/06-local-model.ts) | Ollama + Claude 混合流水线,通过 `baseURL` 接入(兼容 vLLM、LM Studio 等) |
|
||||
| [07 — 扇出聚合](examples/07-fan-out-aggregate.ts) | `runParallel()` MapReduce — 3 个分析师并行,然后综合 |
|
||||
| [08 — Gemma 4 本地](examples/08-gemma4-local.ts) | 纯本地 Gemma 4 智能体团队 + tool-calling — 零 API 费用 |
|
||||
|
||||
## 架构
|
||||
|
||||
|
|
@ -186,6 +187,8 @@ npx tsx examples/01-single-agent.ts
|
|||
| GitHub Copilot | `provider: 'copilot'` | `GITHUB_TOKEN` | 已验证 |
|
||||
| Ollama / vLLM / LM Studio | `provider: 'openai'` + `baseURL` | — | 已验证 |
|
||||
|
||||
已验证支持 tool-calling 的本地模型:**Gemma 4**(见[示例 08](examples/08-gemma4-local.ts))。
|
||||
|
||||
任何 OpenAI 兼容 API 均可通过 `provider: 'openai'` + `baseURL` 接入(DeepSeek、Groq、Mistral、Qwen、MiniMax 等)。这些 Provider 尚未完整验证——欢迎通过 [#25](https://github.com/JackChen-me/open-multi-agent/issues/25) 贡献验证。
|
||||
|
||||
## 参与贡献
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
* Example 08 — Gemma 4 Local Agent Team (100% Local, Zero API Cost)
|
||||
*
|
||||
* Demonstrates a fully local multi-agent team using Google's Gemma 4 via
|
||||
* Ollama. No cloud API keys needed — everything runs on your machine.
|
||||
*
|
||||
* Two agents collaborate through a task pipeline:
|
||||
* - researcher: uses bash + file_write to gather system info and write a report
|
||||
* - summarizer: uses file_read to read the report and produce a concise summary
|
||||
*
|
||||
* This pattern works with any Ollama model that supports tool-calling.
|
||||
* Gemma 4 (released 2026-04-02) has native tool-calling support.
|
||||
*
|
||||
* Run:
|
||||
* no_proxy=localhost npx tsx examples/08-gemma4-local.ts
|
||||
*
|
||||
* Prerequisites:
|
||||
* 1. Ollama >= 0.20.0 installed and running: https://ollama.com
|
||||
* 2. Pull the model: ollama pull gemma4:e2b
|
||||
* (or gemma4:e4b for better quality on machines with more RAM)
|
||||
* 3. No API keys needed!
|
||||
*
|
||||
* Note: The no_proxy=localhost prefix is needed if you have an HTTP proxy
|
||||
* configured, since the OpenAI SDK would otherwise route Ollama requests
|
||||
* through the proxy.
|
||||
*/
|
||||
|
||||
import { OpenMultiAgent } from '../src/index.js'
|
||||
import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Configuration — change this to match your Ollama setup
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// See available tags at https://ollama.com/library/gemma4
|
||||
const OLLAMA_MODEL = 'gemma4:e2b' // or 'gemma4:e4b', 'gemma4:26b'
|
||||
const OLLAMA_BASE_URL = 'http://localhost:11434/v1'
|
||||
const OUTPUT_DIR = '/tmp/gemma4-demo'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agents — both use Gemma 4 locally
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Researcher — gathers system information using shell commands.
|
||||
*/
|
||||
const researcher: AgentConfig = {
|
||||
name: 'researcher',
|
||||
model: OLLAMA_MODEL,
|
||||
provider: 'openai',
|
||||
baseURL: OLLAMA_BASE_URL,
|
||||
apiKey: 'ollama', // placeholder — Ollama ignores this, but the OpenAI SDK requires a non-empty value
|
||||
systemPrompt: `You are a system researcher. Your job is to gather information
|
||||
about the current machine using shell commands and write a structured report.
|
||||
|
||||
Use the bash tool to run commands like: uname -a, df -h, uptime, and similar
|
||||
non-destructive read-only commands.
|
||||
On macOS you can also use: sw_vers, sysctl -n hw.memsize.
|
||||
On Linux you can also use: cat /etc/os-release, free -h.
|
||||
|
||||
Then use file_write to save a Markdown report to ${OUTPUT_DIR}/system-report.md.
|
||||
The report should have sections: OS, Hardware, Disk, and Uptime.
|
||||
Be concise — one or two lines per section is enough.`,
|
||||
tools: ['bash', 'file_write'],
|
||||
maxTurns: 8,
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarizer — reads the report and writes a one-paragraph executive summary.
|
||||
*/
|
||||
const summarizer: AgentConfig = {
|
||||
name: 'summarizer',
|
||||
model: OLLAMA_MODEL,
|
||||
provider: 'openai',
|
||||
baseURL: OLLAMA_BASE_URL,
|
||||
apiKey: 'ollama',
|
||||
systemPrompt: `You are a technical writer. Read the system report file provided,
|
||||
then produce a concise one-paragraph executive summary (3-5 sentences).
|
||||
Focus on the key highlights: what OS, how much RAM, disk status, and uptime.`,
|
||||
tools: ['file_read'],
|
||||
maxTurns: 4,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Progress handler
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const taskTimes = new Map<string, number>()
|
||||
|
||||
function handleProgress(event: OrchestratorEvent): void {
|
||||
const ts = new Date().toISOString().slice(11, 23)
|
||||
|
||||
switch (event.type) {
|
||||
case 'task_start': {
|
||||
taskTimes.set(event.task ?? '', Date.now())
|
||||
const task = event.data as Task | undefined
|
||||
console.log(`[${ts}] TASK START "${task?.title ?? event.task}" → ${task?.assignee ?? '?'}`)
|
||||
break
|
||||
}
|
||||
case 'task_complete': {
|
||||
const elapsed = Date.now() - (taskTimes.get(event.task ?? '') ?? Date.now())
|
||||
console.log(`[${ts}] TASK DONE "${event.task}" in ${(elapsed / 1000).toFixed(1)}s`)
|
||||
break
|
||||
}
|
||||
case 'agent_start':
|
||||
console.log(`[${ts}] AGENT START ${event.agent}`)
|
||||
break
|
||||
case 'agent_complete':
|
||||
console.log(`[${ts}] AGENT DONE ${event.agent}`)
|
||||
break
|
||||
case 'error':
|
||||
console.error(`[${ts}] ERROR ${event.agent ?? ''} task=${event.task ?? '?'}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Orchestrator + Team
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const orchestrator = new OpenMultiAgent({
|
||||
defaultModel: OLLAMA_MODEL,
|
||||
maxConcurrency: 1, // run agents sequentially — local model can only serve one at a time
|
||||
onProgress: handleProgress,
|
||||
})
|
||||
|
||||
const team = orchestrator.createTeam('gemma4-team', {
|
||||
name: 'gemma4-team',
|
||||
agents: [researcher, summarizer],
|
||||
sharedMemory: true,
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Task pipeline: research → summarize
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const tasks: Array<{
|
||||
title: string
|
||||
description: string
|
||||
assignee?: string
|
||||
dependsOn?: string[]
|
||||
}> = [
|
||||
{
|
||||
title: 'Gather system information',
|
||||
description: `Use bash to run system info commands (uname -a, sw_vers, sysctl, df -h, uptime).
|
||||
Then write a structured Markdown report to ${OUTPUT_DIR}/system-report.md with sections:
|
||||
OS, Hardware, Disk, and Uptime.`,
|
||||
assignee: 'researcher',
|
||||
},
|
||||
{
|
||||
title: 'Summarize the report',
|
||||
description: `Read the file at ${OUTPUT_DIR}/system-report.md.
|
||||
Produce a concise one-paragraph executive summary of the system information.`,
|
||||
assignee: 'summarizer',
|
||||
dependsOn: ['Gather system information'],
|
||||
},
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
console.log('Gemma 4 Local Agent Team — Zero API Cost')
|
||||
console.log('='.repeat(60))
|
||||
console.log(` model → ${OLLAMA_MODEL} via Ollama`)
|
||||
console.log(` researcher → bash + file_write`)
|
||||
console.log(` summarizer → file_read`)
|
||||
console.log(` output dir → ${OUTPUT_DIR}`)
|
||||
console.log()
|
||||
console.log('Pipeline: researcher gathers info → summarizer writes summary')
|
||||
console.log('='.repeat(60))
|
||||
|
||||
const start = Date.now()
|
||||
const result = await orchestrator.runTasks(team, tasks)
|
||||
const totalTime = Date.now() - start
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Summary
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('Pipeline complete.\n')
|
||||
console.log(`Overall success: ${result.success}`)
|
||||
console.log(`Total time: ${(totalTime / 1000).toFixed(1)}s`)
|
||||
console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
|
||||
|
||||
console.log('\nPer-agent results:')
|
||||
for (const [name, r] of result.agentResults) {
|
||||
const icon = r.success ? 'OK ' : 'FAIL'
|
||||
const tools = r.toolCalls.map(c => c.toolName).join(', ')
|
||||
console.log(` [${icon}] ${name.padEnd(12)} tools: ${tools || '(none)'}`)
|
||||
}
|
||||
|
||||
// Print the summarizer's output
|
||||
const summary = result.agentResults.get('summarizer')
|
||||
if (summary?.success) {
|
||||
console.log('\nExecutive Summary (from local Gemma 4):')
|
||||
console.log('-'.repeat(60))
|
||||
console.log(summary.output)
|
||||
console.log('-'.repeat(60))
|
||||
}
|
||||
|
||||
console.log('\nAll processing done locally. $0 API cost.')
|
||||
Loading…
Reference in New Issue