auto: 2026-02-20 - update email processor, add ollama-local skill
This commit is contained in:
@@ -16,6 +16,10 @@
|
|||||||
"agent-browser": {
|
"agent-browser": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"installedAt": 1771517234848
|
"installedAt": 1771517234848
|
||||||
|
},
|
||||||
|
"ollama-local": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"installedAt": 1771569100806
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,3 +18,6 @@ Check: 2026-02-19 17:23:21
|
|||||||
[2026-02-19T18:23:19-08:00] Heartbeat email check
|
[2026-02-19T18:23:19-08:00] Heartbeat email check
|
||||||
[2026-02-19T19:23:21-08:00] Heartbeat email check
|
[2026-02-19T19:23:21-08:00] Heartbeat email check
|
||||||
[2026-02-19T20:23:19-08:00] Heartbeat email check
|
[2026-02-19T20:23:19-08:00] Heartbeat email check
|
||||||
|
[2026-02-19T22:23:19-08:00] Heartbeat email check
|
||||||
|
[2026-02-19T22:33:24-08:00] Heartbeat email check
|
||||||
|
[2026-02-19T23:33:27-08:00] Heartbeat email check
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ def analyze_with_qwen3(email_data, config):
|
|||||||
import ollama
|
import ollama
|
||||||
import time
|
import time
|
||||||
|
|
||||||
prompt = f"""Analyze this email and provide two pieces of information:
|
prompt = f"""/no_think
|
||||||
|
|
||||||
|
Analyze this email and provide two pieces of information:
|
||||||
|
|
||||||
1. Is this an advertisement/promotional email?
|
1. Is this an advertisement/promotional email?
|
||||||
2. Summarize the email in one sentence
|
2. Summarize the email in one sentence
|
||||||
|
|||||||
7
skills/ollama-local/.clawhub/origin.json
Normal file
7
skills/ollama-local/.clawhub/origin.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"registry": "https://clawhub.ai",
|
||||||
|
"slug": "ollama-local",
|
||||||
|
"installedVersion": "1.1.0",
|
||||||
|
"installedAt": 1771569100805
|
||||||
|
}
|
||||||
148
skills/ollama-local/SKILL.md
Normal file
148
skills/ollama-local/SKILL.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
---
|
||||||
|
name: ollama-local
|
||||||
|
description: Manage and use local Ollama models. Use for model management (list/pull/remove), chat/completions, embeddings, and tool-use with local LLMs. Covers OpenClaw sub-agent integration and model selection guidance.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ollama Local
|
||||||
|
|
||||||
|
Work with local Ollama models for inference, embeddings, and tool use.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Set your Ollama host (defaults to `http://localhost:11434`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export OLLAMA_HOST="http://localhost:11434"
|
||||||
|
# Or for remote server:
|
||||||
|
export OLLAMA_HOST="http://192.168.1.100:11434"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List models
|
||||||
|
python3 scripts/ollama.py list
|
||||||
|
|
||||||
|
# Pull a model
|
||||||
|
python3 scripts/ollama.py pull llama3.1:8b
|
||||||
|
|
||||||
|
# Remove a model
|
||||||
|
python3 scripts/ollama.py rm modelname
|
||||||
|
|
||||||
|
# Show model details
|
||||||
|
python3 scripts/ollama.py show qwen3:4b
|
||||||
|
|
||||||
|
# Chat with a model
|
||||||
|
python3 scripts/ollama.py chat qwen3:4b "What is the capital of France?"
|
||||||
|
|
||||||
|
# Chat with system prompt
|
||||||
|
python3 scripts/ollama.py chat llama3.1:8b "Review this code" -s "You are a code reviewer"
|
||||||
|
|
||||||
|
# Generate completion (non-chat)
|
||||||
|
python3 scripts/ollama.py generate qwen3:4b "Once upon a time"
|
||||||
|
|
||||||
|
# Get embeddings
|
||||||
|
python3 scripts/ollama.py embed bge-m3 "Text to embed"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model Selection
|
||||||
|
|
||||||
|
See [references/models.md](references/models.md) for full model list and selection guide.
|
||||||
|
|
||||||
|
**Quick picks:**
|
||||||
|
- Fast answers: `qwen3:4b`
|
||||||
|
- Coding: `qwen2.5-coder:7b`
|
||||||
|
- General: `llama3.1:8b`
|
||||||
|
- Reasoning: `deepseek-r1:8b`
|
||||||
|
|
||||||
|
## Tool Use
|
||||||
|
|
||||||
|
Some local models support function calling. Use `ollama_tools.py`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Single request with tools
|
||||||
|
python3 scripts/ollama_tools.py single qwen2.5-coder:7b "What's the weather in Amsterdam?"
|
||||||
|
|
||||||
|
# Full tool loop (model calls tools, gets results, responds)
|
||||||
|
python3 scripts/ollama_tools.py loop qwen3:4b "Search for Python tutorials and summarize"
|
||||||
|
|
||||||
|
# Show available example tools
|
||||||
|
python3 scripts/ollama_tools.py tools
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tool-capable models:** qwen2.5-coder, qwen3, llama3.1, mistral
|
||||||
|
|
||||||
|
## OpenClaw Sub-Agents
|
||||||
|
|
||||||
|
Spawn local model sub-agents with `sessions_spawn`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Example: spawn a coding agent
|
||||||
|
sessions_spawn(
|
||||||
|
task="Review this Python code for bugs",
|
||||||
|
model="ollama/qwen2.5-coder:7b",
|
||||||
|
label="code-review"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Model path format: `ollama/<model-name>`
|
||||||
|
|
||||||
|
### Parallel Agents (Think Tank Pattern)
|
||||||
|
|
||||||
|
Spawn multiple local agents for collaborative tasks:
|
||||||
|
|
||||||
|
```python
|
||||||
|
agents = [
|
||||||
|
{"label": "architect", "model": "ollama/gemma3:12b", "task": "Design the system architecture"},
|
||||||
|
{"label": "coder", "model": "ollama/qwen2.5-coder:7b", "task": "Implement the core logic"},
|
||||||
|
{"label": "reviewer", "model": "ollama/llama3.1:8b", "task": "Review for bugs and improvements"},
|
||||||
|
]
|
||||||
|
|
||||||
|
for a in agents:
|
||||||
|
sessions_spawn(task=a["task"], model=a["model"], label=a["label"])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Direct API
|
||||||
|
|
||||||
|
For custom integrations, use the Ollama API directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Chat
|
||||||
|
curl $OLLAMA_HOST/api/chat -d '{
|
||||||
|
"model": "qwen3:4b",
|
||||||
|
"messages": [{"role": "user", "content": "Hello"}],
|
||||||
|
"stream": false
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Generate
|
||||||
|
curl $OLLAMA_HOST/api/generate -d '{
|
||||||
|
"model": "qwen3:4b",
|
||||||
|
"prompt": "Why is the sky blue?",
|
||||||
|
"stream": false
|
||||||
|
}'
|
||||||
|
|
||||||
|
# List models
|
||||||
|
curl $OLLAMA_HOST/api/tags
|
||||||
|
|
||||||
|
# Pull model
|
||||||
|
curl $OLLAMA_HOST/api/pull -d '{"name": "phi3:mini"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Connection refused?**
|
||||||
|
- Check Ollama is running: `ollama serve`
|
||||||
|
- Verify OLLAMA_HOST is correct
|
||||||
|
- For remote servers, ensure firewall allows port 11434
|
||||||
|
|
||||||
|
**Model not loading?**
|
||||||
|
- Check VRAM: larger models may need CPU offload
|
||||||
|
- Try a smaller model first
|
||||||
|
|
||||||
|
**Slow responses?**
|
||||||
|
- Model may be running on CPU
|
||||||
|
- Use smaller quantization (e.g., `:7b` instead of `:30b`)
|
||||||
|
|
||||||
|
**OpenClaw sub-agent falls back to default model?**
|
||||||
|
- Ensure `ollama:default` auth profile exists in OpenClaw config
|
||||||
|
- Check model path format: `ollama/modelname:tag`
|
||||||
6
skills/ollama-local/_meta.json
Normal file
6
skills/ollama-local/_meta.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"ownerId": "kn7abphxkmfwh2z22jbs72ga7d80dyvd",
|
||||||
|
"slug": "ollama-local",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"publishedAt": 1770105930012
|
||||||
|
}
|
||||||
105
skills/ollama-local/references/models.md
Normal file
105
skills/ollama-local/references/models.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Model Guide
|
||||||
|
|
||||||
|
This reference covers common Ollama models and selection guidance.
|
||||||
|
|
||||||
|
## Popular Models
|
||||||
|
|
||||||
|
### Chat/General Models
|
||||||
|
|
||||||
|
| Model | Params | Best For | Notes |
|
||||||
|
|-------|--------|----------|-------|
|
||||||
|
| `qwen3:4b` | 4B | Fast tasks, quick answers | Thinking-enabled, very fast |
|
||||||
|
| `llama3.1:8b` | 8B | General chat, reasoning | Good all-rounder |
|
||||||
|
| `gemma3:12b` | 12.2B | Creative, design tasks | Google model, good quality |
|
||||||
|
| `phi4-reasoning:latest` | 14.7B | Complex reasoning | Thinking-enabled |
|
||||||
|
| `mistral-small3.1:latest` | 24B | Technical tasks | May need CPU offload |
|
||||||
|
| `deepseek-r1:8b` | 8.2B | Deep reasoning | Thinking-enabled, chain-of-thought |
|
||||||
|
|
||||||
|
### Coding Models
|
||||||
|
|
||||||
|
| Model | Params | Best For | Notes |
|
||||||
|
|-------|--------|----------|-------|
|
||||||
|
| `qwen2.5-coder:7b` | 7.6B | Code generation, review | Best local coding model |
|
||||||
|
| `codellama:7b` | 7B | Code completion | Meta's code model |
|
||||||
|
| `deepseek-coder:6.7b` | 6.7B | Code tasks | Good alternative |
|
||||||
|
|
||||||
|
### Embedding Models
|
||||||
|
|
||||||
|
| Model | Params | Dimensions | Notes |
|
||||||
|
|-------|--------|------------|-------|
|
||||||
|
| `bge-m3:latest` | 567M | 1024 | Multilingual, good quality |
|
||||||
|
| `nomic-embed-text` | 137M | 768 | Fast, English-focused |
|
||||||
|
| `mxbai-embed-large` | 335M | 1024 | High quality embeddings |
|
||||||
|
|
||||||
|
## Model Selection Guide
|
||||||
|
|
||||||
|
### By Task Type
|
||||||
|
|
||||||
|
- **Quick questions**: `qwen3:4b` (fastest)
|
||||||
|
- **General chat**: `llama3.1:8b`
|
||||||
|
- **Coding**: `qwen2.5-coder:7b`
|
||||||
|
- **Complex reasoning**: `phi4-reasoning` or `deepseek-r1:8b`
|
||||||
|
- **Creative/design**: `gemma3:12b`
|
||||||
|
- **Embeddings**: `bge-m3:latest`
|
||||||
|
|
||||||
|
### By Speed vs Quality
|
||||||
|
|
||||||
|
```
|
||||||
|
Fastest ←──────────────────────────────→ Best Quality
|
||||||
|
qwen3:4b → llama3.1:8b → gemma3:12b → mistral-small3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tool Use Support
|
||||||
|
|
||||||
|
Models with good tool/function calling support:
|
||||||
|
- ✅ `qwen2.5-coder:7b` - Excellent
|
||||||
|
- ✅ `qwen3:4b` - Good
|
||||||
|
- ✅ `llama3.1:8b` - Basic
|
||||||
|
- ✅ `mistral` models - Good
|
||||||
|
- ⚠️ Others - May not support tools natively
|
||||||
|
|
||||||
|
## OpenClaw Integration
|
||||||
|
|
||||||
|
To use Ollama models in OpenClaw sub-agents, use these model paths:
|
||||||
|
|
||||||
|
```
|
||||||
|
ollama/qwen3:4b
|
||||||
|
ollama/llama3.1:8b
|
||||||
|
ollama/qwen2.5-coder:7b
|
||||||
|
ollama/gemma3:12b
|
||||||
|
ollama/mistral-small3.1:latest
|
||||||
|
ollama/phi4-reasoning:latest
|
||||||
|
ollama/deepseek-r1:8b
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auth Profile Required
|
||||||
|
|
||||||
|
OpenClaw requires an auth profile even for Ollama (no actual auth needed). Add to `auth-profiles.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"ollama:default": {
|
||||||
|
"type": "api_key",
|
||||||
|
"provider": "ollama",
|
||||||
|
"key": "ollama"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hardware Considerations
|
||||||
|
|
||||||
|
- **8GB VRAM**: Can run models up to ~13B comfortably
|
||||||
|
- **16GB VRAM**: Can run most models including 24B+
|
||||||
|
- **CPU offload**: Ollama automatically offloads to CPU/RAM for larger models
|
||||||
|
- **Larger models** may be slower due to partial CPU inference
|
||||||
|
|
||||||
|
## Installing Models
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull a model
|
||||||
|
ollama pull llama3.1:8b
|
||||||
|
|
||||||
|
# Or via the skill script
|
||||||
|
python3 scripts/ollama.py pull llama3.1:8b
|
||||||
|
|
||||||
|
# List installed models
|
||||||
|
python3 scripts/ollama.py list
|
||||||
|
```
|
||||||
232
skills/ollama-local/scripts/ollama.py
Normal file
232
skills/ollama-local/scripts/ollama.py
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Ollama CLI helper for local Ollama servers.
|
||||||
|
Configure host via OLLAMA_HOST env var (default: http://localhost:11434)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
|
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
|
||||||
|
|
||||||
|
def api_request(endpoint, method="GET", data=None):
|
||||||
|
"""Make request to Ollama API (non-streaming)."""
|
||||||
|
url = f"{OLLAMA_HOST}{endpoint}"
|
||||||
|
headers = {"Content-Type": "application/json"} if data else {}
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=json.dumps(data).encode() if data else None,
|
||||||
|
headers=headers,
|
||||||
|
method=method
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=300) as resp:
|
||||||
|
return json.loads(resp.read())
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def api_stream(endpoint, data):
|
||||||
|
"""Make streaming request to Ollama API."""
|
||||||
|
url = f"{OLLAMA_HOST}{endpoint}"
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=json.dumps(data).encode(),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method="POST"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=300) as resp:
|
||||||
|
for line in resp:
|
||||||
|
if line.strip():
|
||||||
|
yield json.loads(line)
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def list_models():
|
||||||
|
"""List all available models."""
|
||||||
|
result = api_request("/api/tags")
|
||||||
|
models = result.get("models", [])
|
||||||
|
|
||||||
|
if not models:
|
||||||
|
print("No models installed.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"{'Model':<35} {'Size':<10} {'Params':<12} {'Family'}")
|
||||||
|
print("-" * 70)
|
||||||
|
for m in models:
|
||||||
|
name = m["name"]
|
||||||
|
size_gb = m["size"] / (1024**3)
|
||||||
|
params = m["details"].get("parameter_size", "?")
|
||||||
|
family = m["details"].get("family", "?")
|
||||||
|
print(f"{name:<35} {size_gb:>6.1f} GB {params:<12} {family}")
|
||||||
|
|
||||||
|
def pull_model(model_name):
|
||||||
|
"""Pull a model from Ollama registry."""
|
||||||
|
print(f"Pulling {model_name}...")
|
||||||
|
|
||||||
|
for chunk in api_stream("/api/pull", {"name": model_name}):
|
||||||
|
status = chunk.get("status", "")
|
||||||
|
if "pulling" in status:
|
||||||
|
completed = chunk.get("completed", 0)
|
||||||
|
total = chunk.get("total", 0)
|
||||||
|
if total:
|
||||||
|
pct = (completed / total) * 100
|
||||||
|
print(f"\r{status}: {pct:.1f}%", end="", flush=True)
|
||||||
|
else:
|
||||||
|
print(f"\r{status}", end="", flush=True)
|
||||||
|
elif status:
|
||||||
|
print(f"\n{status}")
|
||||||
|
|
||||||
|
print(f"\n✅ {model_name} pulled successfully")
|
||||||
|
|
||||||
|
def remove_model(model_name):
|
||||||
|
"""Remove a model."""
|
||||||
|
api_request("/api/delete", method="DELETE", data={"name": model_name})
|
||||||
|
print(f"✅ {model_name} removed")
|
||||||
|
|
||||||
|
def show_model(model_name):
|
||||||
|
"""Show model details."""
|
||||||
|
result = api_request("/api/show", method="POST", data={"name": model_name})
|
||||||
|
|
||||||
|
print(f"Model: {model_name}")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
if "details" in result:
|
||||||
|
d = result["details"]
|
||||||
|
print(f"Family: {d.get('family', '?')}")
|
||||||
|
print(f"Parameters: {d.get('parameter_size', '?')}")
|
||||||
|
print(f"Quantization: {d.get('quantization_level', '?')}")
|
||||||
|
|
||||||
|
if "modelfile" in result:
|
||||||
|
print(f"\nModelfile:\n{result['modelfile'][:500]}...")
|
||||||
|
|
||||||
|
def chat(model_name, message, system=None, stream=True):
|
||||||
|
"""Chat with a model."""
|
||||||
|
messages = []
|
||||||
|
if system:
|
||||||
|
messages.append({"role": "system", "content": system})
|
||||||
|
messages.append({"role": "user", "content": message})
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"model": model_name,
|
||||||
|
"messages": messages,
|
||||||
|
"stream": stream
|
||||||
|
}
|
||||||
|
|
||||||
|
if stream:
|
||||||
|
for chunk in api_stream("/api/chat", data):
|
||||||
|
content = chunk.get("message", {}).get("content", "")
|
||||||
|
if content:
|
||||||
|
print(content, end="", flush=True)
|
||||||
|
if chunk.get("done"):
|
||||||
|
print() # newline at end
|
||||||
|
# Print stats
|
||||||
|
if "eval_count" in chunk:
|
||||||
|
tokens = chunk["eval_count"]
|
||||||
|
duration = chunk.get("eval_duration", 0) / 1e9
|
||||||
|
tps = tokens / duration if duration else 0
|
||||||
|
print(f"\n[{tokens} tokens, {tps:.1f} tok/s]")
|
||||||
|
else:
|
||||||
|
data["stream"] = False
|
||||||
|
result = api_request("/api/chat", method="POST", data=data)
|
||||||
|
print(result.get("message", {}).get("content", ""))
|
||||||
|
|
||||||
|
def generate(model_name, prompt, system=None, stream=True):
|
||||||
|
"""Generate completion (non-chat mode)."""
|
||||||
|
data = {
|
||||||
|
"model": model_name,
|
||||||
|
"prompt": prompt,
|
||||||
|
"stream": stream
|
||||||
|
}
|
||||||
|
if system:
|
||||||
|
data["system"] = system
|
||||||
|
|
||||||
|
if stream:
|
||||||
|
for chunk in api_stream("/api/generate", data):
|
||||||
|
response = chunk.get("response", "")
|
||||||
|
if response:
|
||||||
|
print(response, end="", flush=True)
|
||||||
|
if chunk.get("done"):
|
||||||
|
print()
|
||||||
|
else:
|
||||||
|
data["stream"] = False
|
||||||
|
result = api_request("/api/generate", method="POST", data=data)
|
||||||
|
print(result.get("response", ""))
|
||||||
|
|
||||||
|
def embeddings(model_name, text):
|
||||||
|
"""Get embeddings for text."""
|
||||||
|
result = api_request("/api/embeddings", method="POST", data={
|
||||||
|
"model": model_name,
|
||||||
|
"prompt": text
|
||||||
|
})
|
||||||
|
emb = result.get("embedding", [])
|
||||||
|
print(f"Embedding dimensions: {len(emb)}")
|
||||||
|
print(f"First 10 values: {emb[:10]}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Ollama CLI helper")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
# list
|
||||||
|
subparsers.add_parser("list", help="List all models")
|
||||||
|
|
||||||
|
# pull
|
||||||
|
pull_parser = subparsers.add_parser("pull", help="Pull a model")
|
||||||
|
pull_parser.add_argument("model", help="Model name (e.g., llama3.1:8b)")
|
||||||
|
|
||||||
|
# rm
|
||||||
|
rm_parser = subparsers.add_parser("rm", help="Remove a model")
|
||||||
|
rm_parser.add_argument("model", help="Model name")
|
||||||
|
|
||||||
|
# show
|
||||||
|
show_parser = subparsers.add_parser("show", help="Show model details")
|
||||||
|
show_parser.add_argument("model", help="Model name")
|
||||||
|
|
||||||
|
# chat
|
||||||
|
chat_parser = subparsers.add_parser("chat", help="Chat with a model")
|
||||||
|
chat_parser.add_argument("model", help="Model name")
|
||||||
|
chat_parser.add_argument("message", help="User message")
|
||||||
|
chat_parser.add_argument("-s", "--system", help="System prompt")
|
||||||
|
chat_parser.add_argument("--no-stream", action="store_true", help="Disable streaming")
|
||||||
|
|
||||||
|
# generate
|
||||||
|
gen_parser = subparsers.add_parser("generate", help="Generate completion")
|
||||||
|
gen_parser.add_argument("model", help="Model name")
|
||||||
|
gen_parser.add_argument("prompt", help="Prompt text")
|
||||||
|
gen_parser.add_argument("-s", "--system", help="System prompt")
|
||||||
|
gen_parser.add_argument("--no-stream", action="store_true", help="Disable streaming")
|
||||||
|
|
||||||
|
# embeddings
|
||||||
|
emb_parser = subparsers.add_parser("embed", help="Get embeddings")
|
||||||
|
emb_parser.add_argument("model", help="Model name (e.g., bge-m3)")
|
||||||
|
emb_parser.add_argument("text", help="Text to embed")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "list":
|
||||||
|
list_models()
|
||||||
|
elif args.command == "pull":
|
||||||
|
pull_model(args.model)
|
||||||
|
elif args.command == "rm":
|
||||||
|
remove_model(args.model)
|
||||||
|
elif args.command == "show":
|
||||||
|
show_model(args.model)
|
||||||
|
elif args.command == "chat":
|
||||||
|
chat(args.model, args.message, args.system, stream=not args.no_stream)
|
||||||
|
elif args.command == "generate":
|
||||||
|
generate(args.model, args.prompt, args.system, stream=not args.no_stream)
|
||||||
|
elif args.command == "embed":
|
||||||
|
embeddings(args.model, args.text)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
237
skills/ollama-local/scripts/ollama_tools.py
Normal file
237
skills/ollama-local/scripts/ollama_tools.py
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Ollama tool-use helper for local models.
|
||||||
|
Implements function calling pattern for models that support it.
|
||||||
|
Configure host via OLLAMA_HOST env var (default: http://localhost:11434)
|
||||||
|
|
||||||
|
Note: Tool use support varies by model:
|
||||||
|
- qwen2.5-coder, qwen3: Good tool support
|
||||||
|
- llama3.1: Basic tool support
|
||||||
|
- mistral: Good tool support
|
||||||
|
- Others: May not support tools natively
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
|
||||||
|
|
||||||
|
# Example tools for demonstration
|
||||||
|
EXAMPLE_TOOLS = [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_weather",
|
||||||
|
"description": "Get current weather for a location",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"location": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "City name, e.g., 'Amsterdam'"
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["celsius", "fahrenheit"],
|
||||||
|
"description": "Temperature unit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["location"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "search_web",
|
||||||
|
"description": "Search the web for information",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search query"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["query"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "run_code",
|
||||||
|
"description": "Execute Python code and return the result",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Python code to execute"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["code"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def chat_with_tools(model, message, tools=None, system=None):
|
||||||
|
"""
|
||||||
|
Send a chat request with tool definitions.
|
||||||
|
Returns the model's response, including any tool calls.
|
||||||
|
"""
|
||||||
|
tools = tools or EXAMPLE_TOOLS
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
if system:
|
||||||
|
messages.append({"role": "system", "content": system})
|
||||||
|
messages.append({"role": "user", "content": message})
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"model": model,
|
||||||
|
"messages": messages,
|
||||||
|
"tools": tools,
|
||||||
|
"stream": False
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{OLLAMA_HOST}/api/chat",
|
||||||
|
data=json.dumps(data).encode(),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method="POST"
|
||||||
|
)
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||||
|
result = json.loads(resp.read())
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def execute_tool_call(tool_call):
|
||||||
|
"""
|
||||||
|
Execute a tool call (mock implementation).
|
||||||
|
In production, replace with actual implementations.
|
||||||
|
"""
|
||||||
|
name = tool_call.get("function", {}).get("name", "")
|
||||||
|
args = tool_call.get("function", {}).get("arguments", {})
|
||||||
|
|
||||||
|
# Parse arguments if string
|
||||||
|
if isinstance(args, str):
|
||||||
|
try:
|
||||||
|
args = json.loads(args)
|
||||||
|
except:
|
||||||
|
args = {}
|
||||||
|
|
||||||
|
print(f" 🔧 Tool: {name}")
|
||||||
|
print(f" 📥 Args: {json.dumps(args, indent=2)}")
|
||||||
|
|
||||||
|
# Mock responses
|
||||||
|
if name == "get_weather":
|
||||||
|
return {"temperature": 12, "condition": "cloudy", "location": args.get("location", "Unknown")}
|
||||||
|
elif name == "search_web":
|
||||||
|
return {"results": [f"Result for: {args.get('query', '')}"]}
|
||||||
|
elif name == "run_code":
|
||||||
|
return {"output": "Code execution simulated", "code": args.get("code", "")}
|
||||||
|
else:
|
||||||
|
return {"error": f"Unknown tool: {name}"}
|
||||||
|
|
||||||
|
def tool_loop(model, message, max_iterations=5):
|
||||||
|
"""
|
||||||
|
Run a full tool-use loop:
|
||||||
|
1. Send message with tools
|
||||||
|
2. If model requests tool call, execute it
|
||||||
|
3. Send tool result back
|
||||||
|
4. Repeat until model gives final answer
|
||||||
|
"""
|
||||||
|
tools = EXAMPLE_TOOLS
|
||||||
|
messages = [{"role": "user", "content": message}]
|
||||||
|
|
||||||
|
system = """You are a helpful assistant with access to tools.
|
||||||
|
When you need to use a tool, respond with a tool call.
|
||||||
|
After receiving tool results, provide a helpful answer to the user."""
|
||||||
|
|
||||||
|
for i in range(max_iterations):
|
||||||
|
print(f"\n--- Iteration {i+1} ---")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"model": model,
|
||||||
|
"messages": [{"role": "system", "content": system}] + messages,
|
||||||
|
"tools": tools,
|
||||||
|
"stream": False
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{OLLAMA_HOST}/api/chat",
|
||||||
|
data=json.dumps(data).encode(),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method="POST"
|
||||||
|
)
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||||
|
result = json.loads(resp.read())
|
||||||
|
|
||||||
|
msg = result.get("message", {})
|
||||||
|
content = msg.get("content", "")
|
||||||
|
tool_calls = msg.get("tool_calls", [])
|
||||||
|
|
||||||
|
if content:
|
||||||
|
print(f"🤖 Model: {content}")
|
||||||
|
|
||||||
|
if not tool_calls:
|
||||||
|
print("\n✅ Final response (no more tool calls)")
|
||||||
|
return content
|
||||||
|
|
||||||
|
# Process tool calls
|
||||||
|
messages.append({"role": "assistant", "content": content, "tool_calls": tool_calls})
|
||||||
|
|
||||||
|
for tc in tool_calls:
|
||||||
|
print(f"\n📞 Tool call requested:")
|
||||||
|
result = execute_tool_call(tc)
|
||||||
|
print(f" 📤 Result: {json.dumps(result)}")
|
||||||
|
|
||||||
|
messages.append({
|
||||||
|
"role": "tool",
|
||||||
|
"content": json.dumps(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
print("\n⚠️ Max iterations reached")
|
||||||
|
return content
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Ollama tool-use helper")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
# single: One-shot tool call
|
||||||
|
single_parser = subparsers.add_parser("single", help="Single tool-enabled request")
|
||||||
|
single_parser.add_argument("model", help="Model name")
|
||||||
|
single_parser.add_argument("message", help="User message")
|
||||||
|
|
||||||
|
# loop: Full tool loop
|
||||||
|
loop_parser = subparsers.add_parser("loop", help="Full tool-use loop")
|
||||||
|
loop_parser.add_argument("model", help="Model name")
|
||||||
|
loop_parser.add_argument("message", help="User message")
|
||||||
|
loop_parser.add_argument("-n", "--max-iter", type=int, default=5, help="Max iterations")
|
||||||
|
|
||||||
|
# tools: Show available tools
|
||||||
|
subparsers.add_parser("tools", help="Show example tools")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "single":
|
||||||
|
result = chat_with_tools(args.model, args.message)
|
||||||
|
msg = result.get("message", {})
|
||||||
|
print(f"Content: {msg.get('content', '')}")
|
||||||
|
if msg.get("tool_calls"):
|
||||||
|
print(f"Tool calls: {json.dumps(msg['tool_calls'], indent=2)}")
|
||||||
|
|
||||||
|
elif args.command == "loop":
|
||||||
|
tool_loop(args.model, args.message, args.max_iter)
|
||||||
|
|
||||||
|
elif args.command == "tools":
|
||||||
|
print(json.dumps(EXAMPLE_TOOLS, indent=2))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user