feat: add 11 Venice AI skills as bundled defaults
Some checks are pending
CI / build-check-test (push) Waiting to run

Skills included:
- venice-chat: Chat with Venice LLM models, vision, reasoning
- venice-chat-benchmark: Benchmark chat models with infographics
- venice-image-gen: Generate images via Venice API
- venice-list-image-models: List available image models
- venice-list-text-models: List available text models
- venice-list-video-models: List available video models
- venice-tts: Text-to-speech via Venice API
- venice-video-generate: Generate videos from text/images
- venice-video-queue: Queue video generation jobs
- venice-video-quote: Get video generation cost quotes
- venice-video-retrieve: Retrieve completed videos

All rebranded from Agent Zero paths to Agent JAE (~/.jae/agent/skills/).
Requires VENICE_API_KEY environment variable.
This commit is contained in:
jae 2026-03-23 18:45:51 +01:00
parent f2c4bdbb27
commit 7fe886fea5
34 changed files with 5089 additions and 0 deletions

86
default-skills/README.md Normal file
View file

@ -0,0 +1,86 @@
# Venice Skills
A collection of skills that wrap the [Venice.ai](https://venice.ai/) API for chat, image generation, video generation, text-to-speech, and model discovery. All skills require the `VENICE_API_KEY` environment variable.
## Skills
| Skill | Description |
|-------|-------------|
| [venice-chat](./venice-chat/) | Chat with Venice.ai LLMs with vision, reasoning mode, and web search |
| [venice-chat-benchmark](./venice-chat-benchmark/) | Benchmark Venice.ai chat models with tool_choice stress testing, timing stats, and 4K infographic |
| [venice-image-gen](./venice-image-gen/) | Generate images from text prompts (1K/2K/4K, multiple formats and aspect ratios) |
| [venice-tts](./venice-tts/) | Text-to-speech with 50+ voices across 9 languages |
| [venice-video-generate](./venice-video-generate/) | Full-lifecycle video generation (queue + poll + retrieve + save) |
| [venice-video-queue](./venice-video-queue/) | Queue a video for generation (text/image/video-to-video) |
| [venice-video-retrieve](./venice-video-retrieve/) | Retrieve and download a queued video by polling until complete |
| [venice-video-quote](./venice-video-quote/) | Get cost estimates for video generation with parameter validation |
| [venice-list-text-models](./venice-list-text-models/) | List available LLM models with capabilities, context windows, and pricing |
| [venice-list-image-models](./venice-list-image-models/) | List available image generation models with pricing and constraints |
| [venice-list-video-models](./venice-list-video-models/) | List available video models with durations, resolutions, and audio capabilities |
## Prerequisites
- Python 3.10+
- `requests` (`pip install requests`)
- `pydantic` (required by video and model-listing skills)
- `VENICE_API_KEY` environment variable set to your Venice.ai API key
```bash
pip install requests pydantic
export VENICE_API_KEY="your_venice_api_key"
```
## Structure
Each skill follows a consistent layout:
```
skill-name/
SKILL.md # Agent-facing documentation (frontmatter + usage)
README.md # Human-facing GitHub documentation
scripts/ # Executable Python scripts
```
- **`SKILL.md`** -- YAML frontmatter (name, description, version, tags, trigger patterns) plus agent-facing usage instructions.
- **`README.md`** -- Human-readable documentation with examples.
- **`scripts/`** -- Python scripts that work both as CLI tools (via `argparse`) and as importable modules.
## Quick Start
### Chat
```bash
python venice-chat/scripts/chat.py "What is the capital of France?"
```
### Generate an image
```bash
python venice-image-gen/scripts/generate_image.py "A sunset over mountains"
```
### Generate a video
```bash
python venice-video-generate/scripts/generate_video.py "A timelapse of a blooming flower"
```
### Text-to-speech
```bash
python venice-tts/scripts/text_to_speech.py "Hello, world!"
```
### List available models
```bash
python venice-list-text-models/scripts/list_text_models.py
python venice-list-image-models/scripts/list_image_models.py
python venice-list-video-models/scripts/list_video_models.py
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,124 @@
# Venice Chat Benchmark
Benchmark [Venice.ai](https://venice.ai/) chat completion models with complex `tool_choice` payloads. Runs N iterations, captures detailed timing and reliability metrics, and optionally generates a 4K infographic summary.
## Features
- **Stress testing** -- run configurable iterations against any Venice chat model
- **Tool choice analysis** -- measures tool call rate, distribution across 7 defined tools, and JSON argument validity
- **Timing statistics** -- average, median, min, max, standard deviation, P90, P95, and P99
- **Error categorization** -- groups failures by type (HTTP, timeout, connection, JSON decode)
- **Token tracking** -- per-run and aggregate prompt, completion, and total token usage
- **Finish reason tracking** -- counts of `tool_calls`, `stop`, and other finish reasons
- **4K infographic** -- optional visual summary generated via the `venice-image-gen` skill
- **Intermediate saves** -- results are written to disk after every run so data is preserved if interrupted
## Prerequisites
```bash
pip install requests
export VENICE_API_KEY="your_venice_api_key"
```
For infographic generation, the `venice-image-gen` skill must be available.
## Usage
### Basic benchmark (50 runs, default model)
```bash
python scripts/benchmark.py --model minimax-m27 --runs 50 --output ./chat_benchmark
```
### Custom run count and timeout
```bash
python scripts/benchmark.py --model minimax-m27 --runs 100 --timeout 60 --output ./chat_benchmark
```
### With infographic generation
```bash
python scripts/benchmark.py --model minimax-m27 --runs 50 --output ./chat_benchmark --infographic
```
## Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `--model` | -- | `minimax-m27` | Model ID to benchmark |
| `--runs` | -- | `50` | Number of test iterations |
| `--timeout` | -- | `120` | Request timeout in seconds |
| `--output` | -- | `~/chat_benchmark` | Output directory for results |
| `--infographic` | -- | off | Generate a 4K infographic summary when done |
## Test Payload
The benchmark sends a fixed travel planning scenario to every run:
- **System prompt** enforces tool-only responses (no plain text)
- **7 function tools** defined: `set_travel_dates`, `set_secondary_destinations`, `set_traveler_info`, `set_travel_priorities`, `set_budget`, `present_choices`, `suggest_primary_destinations`
- **User message** contains multiple extractable data points (dates, destinations, interests, budget)
- **`tool_choice: auto`** lets the model decide which tool(s) to call
## Python Import
```python
from benchmark import run_benchmark
results = run_benchmark(
api_key="your_key",
model="minimax-m27",
num_runs=10,
output_dir="./benchmark_output",
timeout=120
)
print(results["stats"]["success_rate"])
```
## Response Format
The benchmark writes `benchmark_results.json` to the output directory:
```json
{
"metadata": {
"model": "minimax-m27",
"num_runs": 50,
"timeout": 120,
"num_tools": 7,
"tool_names": ["set_travel_dates", "..."],
"tool_choice": "auto",
"start_time": "2026-03-20T12:00:00",
"end_time": "2026-03-20T12:15:00"
},
"runs": [
{
"run": 1,
"success": true,
"duration_seconds": 2.451,
"finish_reason": "tool_calls",
"has_tool_calls": true,
"tool_calls": [{"name": "set_travel_dates", "args_valid_json": true}],
"usage": {"prompt_tokens": 850, "completion_tokens": 120, "total_tokens": 970}
}
],
"stats": {
"total_runs": 50,
"success_rate": 98.0,
"tool_call_rate": 95.0,
"json_validity_rate": 100.0,
"timing": {"avg": 2.5, "median": 2.3, "min": 1.1, "max": 5.2, "stdev": 0.8},
"tool_call_distribution": {"set_travel_dates": 40, "set_budget": 8},
"token_usage": {"avg_total_tokens": 970, "total_all_tokens": 48500}
}
}
```
With the `--infographic` flag, a `benchmark_infographic.png` file is also generated.
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,83 @@
---
name: "venice-chat-benchmark"
description: "Benchmark Venice.ai chat models with complex tool_choice payloads. Runs N iterations, captures timing, tool call distribution, JSON validity, errors, token usage, and generates a 4K infographic."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- benchmark
- chat
- tool_choice
- testing
trigger_patterns:
- "benchmark chat"
- "test model"
- "venice benchmark"
- "tool choice test"
---
# Venice Chat Model Benchmark
Benchmark Venice.ai chat completion models with complex tool_choice payloads.
## When to Use
Use this skill when you need to:
- Stress test a Venice chat model with tool calling
- Measure response time, reliability, and tool call accuracy
- Compare model behavior across many runs
- Generate visual benchmark reports
## Usage
### Basic (50 runs, minimax-m27)
```bash
export VENICE_API_KEY="your-key"
python ~/.jae/agent/skills/venice-chat-benchmark/scripts/benchmark.py --model minimax-m27 --runs 50 --output ~/chat_benchmark
```
### With Infographic
```bash
python ~/.jae/agent/skills/venice-chat-benchmark/scripts/benchmark.py --model minimax-m27 --runs 50 --output ~/chat_benchmark --infographic
```
## Options
| Option | Default | Description |
|--------|---------|-------------|
| --model | minimax-m27 | Model ID to benchmark |
| --runs | 50 | Number of test iterations |
| --timeout | 120 | Request timeout in seconds |
| --output | ~/chat_benchmark | Output directory |
| --infographic | off | Generate 4K infographic when done |
## What It Measures
- **Response time** (avg, median, min, max, stdev, P90, P95)
- **Success rate** (HTTP errors, timeouts, connection errors)
- **Tool call rate** (% of responses that include tool calls)
- **Tool call distribution** (which tools get selected)
- **JSON validity** (whether tool call arguments parse correctly)
- **Token usage** (prompt, completion, total)
- **Finish reasons** (tool_calls vs stop vs other)
- **Error categorization** (by type, with details)
## Test Payload
The benchmark uses a complex travel planning scenario with:
- Detailed system prompt enforcing tool-only responses
- 7 function tools defined (dates, destinations, traveler info, priorities, budget, choices, suggestions)
- A rich user message with multiple extractable data points
- `tool_choice: auto`
## Output
- `benchmark_results.json` — Full results with all run data and computed stats
- `benchmark_infographic.png` — 4K visual summary (with --infographic flag)
## Requirements
- `VENICE_API_KEY` environment variable
- `requests` Python package
- `venice-image-gen` skill (for infographic generation, optional)

View file

@ -0,0 +1,618 @@
#!/usr/bin/env python3
"""Venice Chat Model Benchmark - Tests chat completions with tool_choice.
Usage:
python benchmark.py --model minimax-m27 --runs 50 --output /path/to/output_dir
python benchmark.py --model minimax-m27 --runs 50 --output /path/to/output_dir --infographic
"""
import argparse
import json
import os
import subprocess
import sys
import time
import statistics
from datetime import datetime
import requests
API_URL = "https://api.venice.ai/api/v1/chat/completions"
# === COMPLEX TOOL_CHOICE PAYLOAD (Travel Planning) ===
SYSTEM_PROMPT = """You are an expert travel planning assistant. You MUST call exactly ONE tool on every response. Never respond with plain text. Your response IS the tool call.
Available tools:
- set_travel_dates: Record travel dates
- set_secondary_destinations: Record destinations
- set_traveler_info: Record traveler details
- set_travel_priorities: Record priorities
- set_budget: Record budget
- present_choices: Show clickable choices
- suggest_primary_destinations: Show destination cards
Collect dates first, then travelers, then destinations. Pre-fill from conversation context.
Current itinerary context:
No itinerary data yet."""
USER_MESSAGE = "My wife and I want to plan a 2-week trip to Japan this October. We love food, temples, and hiking. Mid-range budget around $6000."
TOOLS = [
{
"type": "function",
"function": {
"name": "set_travel_dates",
"description": "Set the travel dates for the trip. Opens an interactive date picker.",
"parameters": {
"type": "object",
"properties": {
"start_date": {"type": "string", "description": "Trip start date YYYY-MM-DD"},
"end_date": {"type": "string", "description": "Trip end date YYYY-MM-DD"},
"flexible": {"type": "boolean", "description": "Whether dates are flexible"}
},
"required": ["start_date", "end_date"]
}
}
},
{
"type": "function",
"function": {
"name": "set_secondary_destinations",
"description": "Set trip destinations with secondary options.",
"parameters": {
"type": "object",
"properties": {
"description": {"type": "string", "description": "Overview of why these destinations fit"},
"primary": {"type": "string", "description": "Primary destination"},
"secondary": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"transit": {"type": "string"}
},
"required": ["name", "transit"]
},
"description": "4-5 nearby destinations"
}
},
"required": ["description", "primary", "secondary"]
}
}
},
{
"type": "function",
"function": {
"name": "set_traveler_info",
"description": "Capture traveler information.",
"parameters": {
"type": "object",
"properties": {
"description": {"type": "string", "description": "Trip vibe and goals"},
"count": {"type": "integer", "description": "Number of travelers"},
"interests": {
"type": "array",
"items": {"type": "string"},
"description": "Interest IDs: adventure, hiking, culture, food, street_food, fine_dining, nature, romantic, etc."
}
},
"required": ["count"]
}
}
},
{
"type": "function",
"function": {
"name": "set_travel_priorities",
"description": "Set what matters most for this trip.",
"parameters": {
"type": "object",
"properties": {
"ranked": {
"type": "array",
"items": {"type": "string"},
"description": "Priorities in order: comfort, budget, adventure, culture, food, nature, romantic"
}
},
"required": ["ranked"]
}
}
},
{
"type": "function",
"function": {
"name": "set_budget",
"description": "Set the trip budget.",
"parameters": {
"type": "object",
"properties": {
"total": {"type": "number", "description": "Total budget"},
"currency": {"type": "string", "description": "Currency code"}
},
"required": ["total", "currency"]
}
}
},
{
"type": "function",
"function": {
"name": "present_choices",
"description": "Present clickable choices to the user.",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "Question to display"},
"choices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {"type": "string"},
"description": {"type": "string"}
},
"required": ["label"]
}
}
},
"required": ["message", "choices"]
}
}
},
{
"type": "function",
"function": {
"name": "suggest_primary_destinations",
"description": "Present rich destination suggestions.",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "Heading above cards"},
"destinations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"tagline": {"type": "string"}
},
"required": ["name", "tagline"]
}
}
},
"required": ["message", "destinations"]
}
}
}
]
def make_request(api_key, model, timeout=120):
"""Make a single chat completion request with tools."""
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_MESSAGE}
],
"tools": TOOLS,
"tool_choice": "auto",
"temperature": 0.7,
"stream": False
}
resp = requests.post(API_URL, headers=headers, json=payload, timeout=timeout)
resp.raise_for_status()
return resp.json()
def parse_response(data):
"""Parse the API response and extract key info."""
choice = data.get("choices", [{}])[0]
msg = choice.get("message", {})
finish_reason = choice.get("finish_reason") or "unknown"
usage = data.get("usage", {})
result = {
"finish_reason": finish_reason,
"has_tool_calls": bool(msg.get("tool_calls")),
"tool_calls": [],
"content": msg.get("content"),
"usage": usage,
}
if msg.get("tool_calls"):
for tc in msg["tool_calls"]:
tool_info = {
"id": tc.get("id", ""),
"name": tc["function"]["name"],
"arguments_raw": tc["function"]["arguments"],
}
try:
tool_info["arguments_parsed"] = json.loads(tc["function"]["arguments"])
tool_info["args_valid_json"] = True
except (json.JSONDecodeError, TypeError) as e:
tool_info["arguments_parsed"] = None
tool_info["args_valid_json"] = False
tool_info["json_error"] = str(e)
result["tool_calls"].append(tool_info)
return result
def run_benchmark(api_key, model, num_runs, output_dir, timeout=120):
"""Run the full benchmark."""
os.makedirs(output_dir, exist_ok=True)
print(f"{'='*70}")
print(f"VENICE CHAT BENCHMARK — Tool Choice Stress Test")
print(f"{'='*70}")
print(f"Model: {model}")
print(f"Runs: {num_runs}")
print(f"Timeout: {timeout}s per request")
print(f"Tools: {len(TOOLS)} tools defined")
print(f"Tool choice: auto")
print(f"Started: {datetime.now().isoformat()}")
print(f"{'='*70}\n")
results = {
"metadata": {
"model": model,
"num_runs": num_runs,
"timeout": timeout,
"num_tools": len(TOOLS),
"tool_names": [t["function"]["name"] for t in TOOLS],
"tool_choice": "auto",
"system_prompt": SYSTEM_PROMPT,
"user_message": USER_MESSAGE,
"start_time": datetime.now().isoformat(),
},
"runs": [],
"stats": {},
}
successful_times = []
tool_call_counts = {} # which tools get called
finish_reasons = {}
errors_list = []
for run_num in range(1, num_runs + 1):
run_data = {
"run": run_num,
"start_time": datetime.now().isoformat(),
"success": False,
"duration_seconds": None,
"error": None,
"error_type": None,
"http_status": None,
"finish_reason": None,
"has_tool_calls": False,
"tool_calls": [],
"content": None,
"usage": {},
"args_valid_json": True,
}
try:
start = time.time()
raw_response = make_request(api_key, model, timeout=timeout)
elapsed = time.time() - start
parsed = parse_response(raw_response)
run_data["success"] = True
run_data["duration_seconds"] = round(elapsed, 3)
run_data["http_status"] = 200
run_data["finish_reason"] = parsed["finish_reason"] or "none"
run_data["has_tool_calls"] = parsed["has_tool_calls"]
run_data["tool_calls"] = parsed["tool_calls"]
run_data["content"] = parsed["content"]
run_data["usage"] = parsed["usage"]
# Check if all tool call args are valid JSON
all_valid = all(tc.get("args_valid_json", False) for tc in parsed["tool_calls"]) if parsed["tool_calls"] else True
run_data["args_valid_json"] = all_valid
successful_times.append(elapsed)
# Track tool call distribution
fr = parsed["finish_reason"] or "none"
finish_reasons[fr] = finish_reasons.get(fr, 0) + 1
for tc in parsed["tool_calls"]:
tn = tc["name"]
tool_call_counts[tn] = tool_call_counts.get(tn, 0) + 1
# Display
tool_names = ", ".join(tc["name"] for tc in parsed["tool_calls"]) if parsed["tool_calls"] else "NONE"
json_ok = "" if all_valid else "✗ BAD JSON"
content_flag = " +content" if parsed["content"] else ""
print(f" ✅ Run {run_num:3d}/{num_runs}: {elapsed:6.2f}s | {str(fr):<12} | tools: {tool_names} | json: {json_ok}{content_flag}")
except requests.exceptions.HTTPError as e:
elapsed = time.time() - start
run_data["duration_seconds"] = round(elapsed, 3)
run_data["error"] = str(e)[:500]
run_data["error_type"] = "http_error"
status = None
try:
status = e.response.status_code if e.response is not None else None
except:
pass
run_data["http_status"] = status
try:
err_body = e.response.json() if e.response is not None else {}
run_data["error_body"] = err_body
run_data["error"] = json.dumps(err_body)[:500]
except:
run_data["error_body"] = {}
errors_list.append({"run": run_num, "type": "http_error", "status": status, "error": run_data["error"][:200]})
print(f" ❌ Run {run_num:3d}/{num_runs}: {elapsed:6.2f}s | HTTP {status or "???"} - {run_data['error'][:100]}")
except requests.exceptions.Timeout as e:
elapsed = time.time() - start
run_data["duration_seconds"] = round(elapsed, 3)
run_data["error"] = f"Request timed out after {timeout}s"
run_data["error_type"] = "timeout"
errors_list.append({"run": run_num, "type": "timeout", "error": run_data["error"]})
print(f" ❌ Run {run_num:3d}/{num_runs}: {elapsed:6.2f}s | TIMEOUT")
except requests.exceptions.ConnectionError as e:
elapsed = time.time() - start
run_data["duration_seconds"] = round(elapsed, 3)
run_data["error"] = str(e)[:500]
run_data["error_type"] = "connection_error"
errors_list.append({"run": run_num, "type": "connection_error", "error": str(e)[:200]})
print(f" ❌ Run {run_num:3d}/{num_runs}: {elapsed:6.2f}s | CONNECTION ERROR - {str(e)[:80]}")
except json.JSONDecodeError as e:
elapsed = time.time() - start
run_data["duration_seconds"] = round(elapsed, 3)
run_data["error"] = f"Invalid JSON response: {str(e)[:200]}"
run_data["error_type"] = "json_decode_error"
errors_list.append({"run": run_num, "type": "json_decode_error", "error": str(e)[:200]})
print(f" ❌ Run {run_num:3d}/{num_runs}: {elapsed:6.2f}s | JSON DECODE ERROR")
except Exception as e:
elapsed = time.time() - start
run_data["duration_seconds"] = round(elapsed, 3)
run_data["error"] = str(e)[:500]
run_data["error_type"] = type(e).__name__
errors_list.append({"run": run_num, "type": type(e).__name__, "error": str(e)[:200]})
print(f" ❌ Run {run_num:3d}/{num_runs}: {elapsed:6.2f}s | {type(e).__name__}: {str(e)[:80]}")
run_data["end_time"] = datetime.now().isoformat()
results["runs"].append(run_data)
# Save intermediate results
with open(f"{output_dir}/benchmark_results.json", "w") as f:
json.dump(results, f, indent=2)
# === COMPUTE STATS ===
successful_runs = [r for r in results["runs"] if r["success"]]
failed_runs = [r for r in results["runs"] if not r["success"]]
tool_call_runs = [r for r in successful_runs if r["has_tool_calls"]]
no_tool_runs = [r for r in successful_runs if not r["has_tool_calls"]]
bad_json_runs = [r for r in successful_runs if not r["args_valid_json"]]
content_runs = [r for r in successful_runs if r["content"]]
stats = {
"total_runs": num_runs,
"successful_runs": len(successful_runs),
"failed_runs": len(failed_runs),
"success_rate": round(len(successful_runs) / num_runs * 100, 1),
"tool_call_runs": len(tool_call_runs),
"tool_call_rate": round(len(tool_call_runs) / len(successful_runs) * 100, 1) if successful_runs else 0,
"no_tool_runs": len(no_tool_runs),
"bad_json_runs": len(bad_json_runs),
"json_validity_rate": round((len(tool_call_runs) - len(bad_json_runs)) / len(tool_call_runs) * 100, 1) if tool_call_runs else 0,
"content_with_tool_calls": len([r for r in tool_call_runs if r["content"]]),
"tool_call_distribution": tool_call_counts,
"finish_reasons": finish_reasons,
"errors": errors_list,
}
if successful_times:
stats["timing"] = {
"avg": round(statistics.mean(successful_times), 3),
"median": round(statistics.median(successful_times), 3),
"min": round(min(successful_times), 3),
"max": round(max(successful_times), 3),
"stdev": round(statistics.stdev(successful_times), 3) if len(successful_times) > 1 else 0,
"p90": round(sorted(successful_times)[int(len(successful_times) * 0.9)], 3) if len(successful_times) >= 10 else None,
"p95": round(sorted(successful_times)[int(len(successful_times) * 0.95)], 3) if len(successful_times) >= 20 else None,
"p99": round(sorted(successful_times)[int(len(successful_times) * 0.99)], 3) if len(successful_times) >= 100 else None,
}
# Usage stats
if successful_runs:
prompt_tokens = [r["usage"].get("prompt_tokens", 0) for r in successful_runs if r["usage"]]
completion_tokens = [r["usage"].get("completion_tokens", 0) for r in successful_runs if r["usage"]]
total_tokens = [r["usage"].get("total_tokens", 0) for r in successful_runs if r["usage"]]
if prompt_tokens:
stats["token_usage"] = {
"avg_prompt_tokens": round(statistics.mean(prompt_tokens)),
"avg_completion_tokens": round(statistics.mean(completion_tokens)),
"avg_total_tokens": round(statistics.mean(total_tokens)),
"total_prompt_tokens": sum(prompt_tokens),
"total_completion_tokens": sum(completion_tokens),
"total_all_tokens": sum(total_tokens),
}
results["stats"] = stats
results["metadata"]["end_time"] = datetime.now().isoformat()
# Save final results
with open(f"{output_dir}/benchmark_results.json", "w") as f:
json.dump(results, f, indent=2)
# === PRINT SUMMARY ===
print(f"\n{'='*70}")
print(f"BENCHMARK COMPLETE — {model}")
print(f"{'='*70}")
print(f"\n📊 Results Summary:")
print(f" Total runs: {num_runs}")
print(f" Successful: {stats['successful_runs']} ({stats['success_rate']}%)")
print(f" Failed: {stats['failed_runs']}")
print(f" Tool call rate: {stats['tool_call_rate']}% of successful runs")
print(f" JSON validity: {stats['json_validity_rate']}% of tool calls")
print(f" Bad JSON args: {stats['bad_json_runs']}")
print(f" Content + tool call: {stats['content_with_tool_calls']} (ideally 0)")
if "timing" in stats:
t = stats["timing"]
print(f"\n⏱️ Timing:")
print(f" Average: {t['avg']}s")
print(f" Median: {t['median']}s")
print(f" Min: {t['min']}s")
print(f" Max: {t['max']}s")
print(f" Std Dev: {t['stdev']}s")
if t.get("p90"): print(f" P90: {t['p90']}s")
if t.get("p95"): print(f" P95: {t['p95']}s")
if tool_call_counts:
print(f"\n🔧 Tool Call Distribution:")
for tn, count in sorted(tool_call_counts.items(), key=lambda x: x[1], reverse=True):
pct = round(count / sum(tool_call_counts.values()) * 100, 1)
bar = "" * int(pct / 2)
print(f" {tn:<35} {count:3d} ({pct:5.1f}%) {bar}")
if finish_reasons:
print(f"\n🏁 Finish Reasons:")
for fr, count in sorted(finish_reasons.items(), key=lambda x: x[1], reverse=True):
print(f" {str(fr or "none"):<20} {count:3d}")
if errors_list:
print(f"\n⚠️ Errors ({len(errors_list)}):")
# Group by type
error_types = {}
for e in errors_list:
et = e["type"]
error_types[et] = error_types.get(et, 0) + 1
for et, count in sorted(error_types.items(), key=lambda x: x[1], reverse=True):
print(f" {et}: {count}")
# Show first 5 unique errors
seen = set()
for e in errors_list:
key = e["error"][:100]
if key not in seen:
seen.add(key)
print(f" Run {e['run']}: [{e['type']}] {e['error'][:150]}")
if len(seen) >= 5:
break
if "token_usage" in stats:
tu = stats["token_usage"]
print(f"\n🪙 Token Usage:")
print(f" Avg prompt: {tu['avg_prompt_tokens']}")
print(f" Avg completion: {tu['avg_completion_tokens']}")
print(f" Avg total: {tu['avg_total_tokens']}")
print(f" Grand total: {tu['total_all_tokens']}")
print(f"\n📁 Results: {output_dir}/benchmark_results.json")
return results
def generate_infographic(output_dir, api_key):
"""Generate a 4K infographic from benchmark results."""
with open(f"{output_dir}/benchmark_results.json") as f:
data = json.load(f)
stats = data["stats"]
meta = data["metadata"]
timing = stats.get("timing", {})
tool_dist = stats.get("tool_call_distribution", {})
token_usage = stats.get("token_usage", {})
errors = stats.get("errors", [])
finish_reasons = stats.get("finish_reasons", {})
# Build tool distribution text
tool_lines = []
if tool_dist:
total_calls = sum(tool_dist.values())
for tn, count in sorted(tool_dist.items(), key=lambda x: x[1], reverse=True):
pct = round(count / total_calls * 100, 1)
tool_lines.append(f"{tn}: {count} calls ({pct}%)")
tool_text = ", ".join(tool_lines) if tool_lines else "No tool calls"
# Finish reasons text
fr_text = ", ".join(f"{k}: {v}" for k, v in sorted(finish_reasons.items(), key=lambda x: x[1], reverse=True))
# Error summary
error_types = {}
for e in errors:
error_types[e["type"]] = error_types.get(e["type"], 0) + 1
error_text = ", ".join(f"{k}: {v}" for k, v in error_types.items()) if error_types else "No errors"
prompt = f"""Premium dark-themed data infographic titled 'VENICE AI CHAT BENCHMARK' with subtitle 'Tool Choice Stress Test — {meta["model"]}{stats["total_runs"]} Runs — {meta.get("start_time","")[:10]}'. Sleek modern design with dark navy-black background, neon green and electric cyan accent colors, glowing AI circuit patterns.
Layout: TOP SECTION: Large glowing title banner with AI brain icon. Key stats row: '{stats["total_runs"]} Total Runs' '{stats["success_rate"]}% Success Rate' '{stats["tool_call_rate"]}% Tool Call Rate' '{stats["json_validity_rate"]}% JSON Valid' '{len(meta.get("tool_names",[]))} Tools Defined'.
MIDDLE LEFT: Performance gauge showing Average Response Time {timing.get("avg","N/A")}s, Median {timing.get("median","N/A")}s, Min {timing.get("min","N/A")}s, Max {timing.get("max","N/A")}s, StdDev {timing.get("stdev","N/A")}s, P90 {timing.get("p90","N/A")}s.
MIDDLE RIGHT: Horizontal bar chart of Tool Call Distribution: {tool_text}. Bars in gradient neon colors.
BOTTOM LEFT: Reliability metrics: {stats["successful_runs"]} successful, {stats["failed_runs"]} failed, {stats["bad_json_runs"]} bad JSON responses, {stats["content_with_tool_calls"]} responses had content alongside tool calls. Finish reasons: {fr_text}.
BOTTOM CENTER: Token usage stats: Avg prompt {token_usage.get("avg_prompt_tokens","N/A")} tokens, Avg completion {token_usage.get("avg_completion_tokens","N/A")} tokens, Total {token_usage.get("total_all_tokens","N/A")} tokens across all runs.
BOTTOM RIGHT: Error breakdown: {error_text}.
All text crisp and legible, professional data dashboard style, glowing neon data points, subtle encryption circuit patterns in background. Model name '{meta["model"]}' prominently displayed."""
print(f"\n🎨 Generating 4K infographic...")
img_output = f"{output_dir}/benchmark_infographic"
cmd = [
"python", "~/.jae/agent/skills/venice-image-gen/scripts/generate_image.py",
prompt,
"--resolution", "4K",
"--aspect_ratio", "16:9",
"--format", "png",
"--output", img_output
]
env = os.environ.copy()
env["VENICE_API_KEY"] = api_key
result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=120)
print(result.stdout)
if result.stderr:
print(result.stderr)
if result.returncode == 0:
print(f"✅ Infographic saved to: {img_output}.png")
else:
print(f"❌ Infographic generation failed (exit code {result.returncode})")
return result.returncode == 0
def main():
parser = argparse.ArgumentParser(description="Venice Chat Model Benchmark")
parser.add_argument("--model", default="minimax-m27", help="Model ID to test")
parser.add_argument("--runs", type=int, default=50, help="Number of runs")
parser.add_argument("--timeout", type=int, default=120, help="Request timeout in seconds")
parser.add_argument("--output", default="~/chat_benchmark", help="Output directory")
parser.add_argument("--infographic", action="store_true", help="Generate 4K infographic")
args = parser.parse_args()
api_key = os.environ.get("VENICE_API_KEY", "")
if not api_key:
print("ERROR: VENICE_API_KEY environment variable not set")
sys.exit(1)
results = run_benchmark(api_key, args.model, args.runs, args.output, args.timeout)
if args.infographic:
generate_infographic(args.output, api_key)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,105 @@
# Venice Chat
Chat with [Venice.ai](https://venice.ai/) LLM models. Supports system prompts, image analysis (vision), reasoning mode, and web search. Auto-selects the best model based on the task.
## Features
- **Text chat** with any Venice.ai LLM model
- **Vision/image analysis** -- describe, analyze, or ask questions about images
- **Reasoning mode** -- extended thinking for complex problems
- **Web search** -- augment responses with live web results
- **Auto model selection** -- picks the optimal model based on task type
## Prerequisites
```bash
pip install requests
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### Simple chat
```bash
python scripts/chat.py "What is the capital of France?"
```
### With system prompt
```bash
python scripts/chat.py "Write a haiku" --system "You are a poet"
```
### Image analysis
```bash
python scripts/chat.py "What's in this image?" --image /path/to/image.png
```
### Reasoning mode
```bash
python scripts/chat.py "Solve this complex math problem" --reasoning
```
### Web search
```bash
python scripts/chat.py "What happened in tech news today?" --web_search
```
## Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `message` | -- | *(required)* | Your message |
| `--system` | `-s` | None | System prompt |
| `--model` | `-m` | auto | Model ID (auto-selected if not provided) |
| `--image` | `-i` | None | Image path for vision analysis |
| `--reasoning` | `-r` | off | Enable reasoning mode |
| `--temperature` | `-t` | 0.7 | Temperature (0.0-2.0) |
| `--max_tokens` | -- | None | Max response tokens |
| `--web_search` | `-w` | off | Enable web search |
## Default Models
| Task | Model | Notes |
|------|-------|-------|
| General chat | `zai-org-glm-4.7` | GLM 4.7 -- most intelligent |
| Vision/image | `qwen3-vl-235b-a22b` | Qwen3 VL 235B -- 250K context |
| Reasoning | `qwen3-235b-a22b-thinking-2507` | Extended thinking |
## Python Import
```python
from chat import chat
result = chat(
message="Explain quantum computing",
system="You are a physics professor",
temperature=0.5
)
print(result["response"])
```
## Response Format
```python
{
"success": True,
"model": "zai-org-glm-4.7",
"response": "The capital of France is Paris.",
"usage": {
"prompt_tokens": 12,
"completion_tokens": 8,
"total_tokens": 20
}
}
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,76 @@
---
name: "venice-chat"
description: "Chat with Venice.ai LLM models, analyze images/videos. Supports reasoning mode and web search."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- chat
- llm
- vision
- reasoning
trigger_patterns:
- "venice chat"
- "chat with venice"
- "analyze image with venice"
- "venice reasoning"
---
# Venice Chat
Chat with Venice.ai LLM models with optional image analysis and reasoning mode.
## When to Use
Use this skill when you need to:
- Chat with Venice.ai models directly
- Analyze images using vision models
- Use reasoning mode for complex problems
- Enable web search for current information
## Usage
### Simple Chat
```bash
python ~/.jae/agent/skills/venice-chat/scripts/chat.py "What is the capital of France?"
```
### With System Prompt
```bash
python ~/.jae/agent/skills/venice-chat/scripts/chat.py "Write a haiku" --system "You are a poet"
```
### Image Analysis
```bash
python ~/.jae/agent/skills/venice-chat/scripts/chat.py "What's in this image?" --image /path/to/image.png
```
### Reasoning Mode
```bash
python ~/.jae/agent/skills/venice-chat/scripts/chat.py "Solve this math problem" --reasoning
```
## Default Models
| Mode | Model | Notes |
|------|-------|-------|
| Default | zai-org-glm-4.7 | GLM 4.7 - most intelligent |
| Vision | qwen3-vl-235b-a22b | Qwen3 VL 235B - 250K context |
| Reasoning | qwen3-235b-a22b-thinking-2507 | Extended thinking |
## Options
| Option | Description |
|--------|-------------|
| --model | Override model ID |
| --system | System prompt |
| --image | Path to image for analysis |
| --reasoning | Enable reasoning mode |
| --temperature | 0.0-2.0 (default: 0.7) |
| --max_tokens | Max response tokens |
| --web_search | Enable web search |
## Requirements
- `VENICE_API_KEY` environment variable

View file

@ -0,0 +1,190 @@
"""# Venice.ai Chat Instrument
Chat with Venice.ai LLM models, analyze images.
Usage: chat(message, system=None, image=None, reasoning=False, ...)
NOTE: Most parameters are NOT needed for typical use.
Just provide a message and let defaults handle the rest.
VISION SUPPORT:
Models with vision capability (can analyze images):
- qwen3-vl-235b-a22b (Qwen3 VL 235B) - RECOMMENDED, best value, 250K context
- mistral-31-24b (Venice Medium) - reliable alternative
Other models like Claude, Gemini, GPT may also support vision.
"""
import os
import sys
import base64
import argparse
import requests
from pathlib import Path
# API Configuration
VENICE_API_URL = "https://api.venice.ai/api/v1/chat/completions"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
# Default Models
DEFAULT_MODEL = "zai-org-glm-4.7" # GLM 4.7 - most intelligent
DEFAULT_VISION_MODEL = "qwen3-vl-235b-a22b" # Qwen3 VL 235B - best value vision model, 250K context
DEFAULT_REASONING_MODEL = "qwen3-235b-a22b-thinking-2507" # Reasoning default
def encode_image(image_path: str) -> tuple[str, str]:
"""Encode image to base64 with mime type."""
path = Path(image_path)
suffix = path.suffix.lower()
mime_types = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".webp": "image/webp",
}
mime_type = mime_types.get(suffix, "image/png")
with open(path, "rb") as f:
data = base64.b64encode(f.read()).decode("utf-8")
return data, mime_type
def chat(
message: str,
system: str = None,
model: str = None,
image: str = None,
reasoning: bool = False,
temperature: float = 0.7,
max_tokens: int = None,
web_search: bool = False,
) -> dict:
"""
Chat with Venice.ai LLM.
Args:
message: User message (required)
system: System prompt
model: Model ID (auto-selected based on task if not provided)
image: Path to image for vision analysis
reasoning: Enable reasoning mode
temperature: 0.0-2.0 (default: 0.7)
max_tokens: Max response tokens
web_search: Enable web search
Returns:
dict with response text and metadata
"""
if not VENICE_API_KEY:
raise ValueError("VENICE_API_KEY environment variable not set")
# Auto-select model based on task
if model is None:
if image:
model = DEFAULT_VISION_MODEL
elif reasoning:
model = DEFAULT_REASONING_MODEL
else:
model = DEFAULT_MODEL
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
# Build messages
messages = []
if system:
messages.append({"role": "system", "content": system})
# Build user message content
if image:
img_data, mime_type = encode_image(image)
content = [
{"type": "text", "text": message},
{
"type": "image_url",
"image_url": {
"url": f"data:{mime_type};base64,{img_data}"
}
}
]
messages.append({"role": "user", "content": content})
else:
messages.append({"role": "user", "content": message})
# Build payload
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"stream": False,
}
if max_tokens:
payload["max_tokens"] = max_tokens
if reasoning:
payload["reasoning"] = {"effort": "medium"}
if web_search:
payload["venice_parameters"] = {"enable_web_search": "on"}
print(f"Chatting with {model}...")
response = requests.post(VENICE_API_URL, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
# Extract response
choices = data.get("choices", [])
if not choices:
return {"success": False, "error": "No response from model"}
reply = choices[0].get("message", {}).get("content", "")
usage = data.get("usage", {})
return {
"success": True,
"model": model,
"response": reply,
"usage": {
"prompt_tokens": usage.get("prompt_tokens", 0),
"completion_tokens": usage.get("completion_tokens", 0),
"total_tokens": usage.get("total_tokens", 0),
}
}
def main():
parser = argparse.ArgumentParser(description="Chat with Venice.ai LLM")
parser.add_argument("message", help="Your message")
parser.add_argument("--system", "-s", help="System prompt")
parser.add_argument("--model", "-m", help="Model ID")
parser.add_argument("--image", "-i", help="Image path for vision analysis")
parser.add_argument("--reasoning", "-r", action="store_true", help="Enable reasoning mode")
parser.add_argument("--temperature", "-t", type=float, default=0.7, help="Temperature (0.0-2.0)")
parser.add_argument("--max_tokens", type=int, help="Max response tokens")
parser.add_argument("--web_search", "-w", action="store_true", help="Enable web search")
args = parser.parse_args()
result = chat(
message=args.message,
system=args.system,
model=args.model,
image=args.image,
reasoning=args.reasoning,
temperature=args.temperature,
max_tokens=args.max_tokens,
web_search=args.web_search,
)
if result["success"]:
print(f"\n--- Response from {result['model']} ---\n")
print(result["response"])
print(f"\n--- Tokens: {result['usage']['total_tokens']} ---")
else:
print(f"\nError: {result.get('error')}")
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,89 @@
# Venice Image Generation
Generate images from text prompts using the [Venice.ai](https://venice.ai/) image generation API. Supports multiple resolutions, aspect ratios, formats, and up to 4 variants per request.
## Features
- **Text-to-image** generation with customizable prompts
- **Multiple resolutions** -- 1K, 2K, 4K
- **Aspect ratios** -- 1:1, 16:9, 9:16, 4:3, 3:4
- **Negative prompts** -- specify what to avoid
- **Multiple variants** -- generate 1-4 images per request
- **Output formats** -- webp, png, jpeg
- **Reproducible results** with seed parameter
## Prerequisites
```bash
pip install requests
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### Basic
```bash
python scripts/generate_image.py "A beautiful sunset over mountains"
```
### With options
```bash
python scripts/generate_image.py "A futuristic cityscape at night" \
--resolution 2K \
--aspect_ratio 16:9 \
--negative_prompt "blurry, low quality" \
--variants 2 \
--format png \
--output cityscape.png
```
## Options
| Option | Default | Values | Description |
|--------|---------|--------|-------------|
| `prompt` | *(required)* | text | Image description |
| `--model` | `nano-banana-2` | model ID | Generation model |
| `--resolution` | `1K` | `1K`, `2K`, `4K` | Image resolution |
| `--aspect_ratio` | `1:1` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4` | Aspect ratio |
| `--negative_prompt` | None | text | What to avoid |
| `--variants` | `1` | `1`-`4` | Number of images |
| `--format` | `webp` | `webp`, `png`, `jpeg` | Output format |
| `--seed` | None | integer | Random seed for reproducibility |
| `--no-safe-mode` | off | flag | Disable safe mode |
| `--output` / `-o` | auto | path | Output file path |
## Python Import
```python
from generate_image import generate_image
result = generate_image(
prompt="A cat wearing a top hat",
resolution="2K",
aspect_ratio="1:1",
variants=2
)
for path in result["images"]:
print(f"Saved: {path}")
```
## Response Format
```python
{
"success": True,
"model": "nano-banana-2",
"prompt": "A cat wearing a top hat",
"images": ["/path/to/generated_20260305_143200.webp"],
"count": 1
}
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,63 @@
---
name: "venice-image-gen"
description: "Generate images using Venice.ai API with customizable resolution, aspect ratio, and variants."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- image
- generation
- ai-art
trigger_patterns:
- "generate image"
- "venice image"
- "create image"
- "ai image"
- "make image"
---
# Venice Image Generation
Generate images using Venice.ai API.
## When to Use
Use this skill when you need to:
- Generate AI images from text prompts
- Create images with specific resolutions or aspect ratios
- Generate multiple image variants
## Usage
### Basic
```bash
python ~/.jae/agent/skills/venice-image-gen/scripts/generate_image.py "A beautiful sunset over mountains"
```
### With Options
```bash
python ~/.jae/agent/skills/venice-image-gen/scripts/generate_image.py "prompt" --resolution 2K --aspect_ratio 16:9 --variants 2
```
## Options
| Option | Values | Default | Notes |
|--------|--------|---------|-------|
| --resolution | 1K, 2K, 4K | 1K | Higher = more credits |
| --aspect_ratio | 1:1, 16:9, 9:16, 4:3, 3:4 | 1:1 | |
| --variants | 1-4 | 1 | Generate multiple images |
| --negative_prompt | text | None | What to avoid |
| --format | webp, png, jpeg | webp | webp is smallest |
| --output | path | ./generated_image | Output filename |
## Defaults
- **Model:** nano-banana-2 (Google Nano Banana)
- **Resolution:** 1K
- **Aspect Ratio:** 1:1
- **Format:** webp
## Requirements
- `VENICE_API_KEY` environment variable

View file

@ -0,0 +1,165 @@
"""# Venice.ai Image Generation Instrument
Generate images using Venice.ai API.
Usage: generate_image(prompt, model="nano-banana-2", resolution="1K", ...)
NOTE: Most parameters are NOT needed for typical use.
Just provide a good prompt and let defaults handle the rest.
"""
import os
import sys
import base64
import argparse
import requests
from pathlib import Path
from datetime import datetime
# API Configuration
VENICE_API_URL = "https://api.venice.ai/api/v1/image/generate"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
# Defaults
DEFAULT_MODEL = "nano-banana-2" # Google Nano Banana - fast & good quality
DEFAULT_RESOLUTION = "1K"
DEFAULT_ASPECT_RATIO = "1:1"
DEFAULT_FORMAT = "webp"
def generate_image(
prompt: str,
model: str = DEFAULT_MODEL,
resolution: str = DEFAULT_RESOLUTION,
aspect_ratio: str = DEFAULT_ASPECT_RATIO,
negative_prompt: str = None,
variants: int = 1,
format: str = DEFAULT_FORMAT,
seed: int = None,
safe_mode: bool = True,
output_path: str = None,
) -> dict:
"""
Generate image(s) using Venice.ai API.
Args:
prompt: Image description (required)
model: Model ID (default: nano-banana-2)
resolution: 1K, 2K, or 4K (default: 1K)
aspect_ratio: e.g. 1:1, 16:9, 9:16 (default: 1:1)
negative_prompt: What to avoid in the image
variants: Number of images 1-4 (default: 1)
format: webp, png, or jpeg (default: webp)
seed: Random seed for reproducibility
safe_mode: Blur adult content (default: True)
output_path: Save path (auto-generated if not provided)
Returns:
dict with image paths and generation info
"""
if not VENICE_API_KEY:
raise ValueError("VENICE_API_KEY environment variable not set")
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
# Build request payload - only include non-default/set values
payload = {
"model": model,
"prompt": prompt,
"resolution": resolution,
"aspect_ratio": aspect_ratio,
"format": format,
"safe_mode": safe_mode,
"return_binary": False, # Get base64 for easier handling
}
if negative_prompt:
payload["negative_prompt"] = negative_prompt
if variants > 1:
payload["variants"] = variants
if seed is not None:
payload["seed"] = seed
print(f"Generating image with {model}...")
print(f"Prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}")
response = requests.post(VENICE_API_URL, headers=headers, json=payload)
response.raise_for_status()
data = response.json()
# Save images
saved_files = []
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
images = data.get("images", [])
if not images:
print("No images returned")
return {"success": False, "error": "No images in response"}
for i, img_data in enumerate(images):
# Determine output filename
if output_path:
if len(images) > 1:
base = Path(output_path)
filepath = base.parent / f"{base.stem}_{i+1}{base.suffix or '.' + format}"
else:
filepath = Path(output_path)
if not filepath.suffix:
filepath = Path(f"{output_path}.{format}")
else:
suffix = f"_{i+1}" if len(images) > 1 else ""
filepath = Path(f"generated_{timestamp}{suffix}.{format}")
# Decode and save
img_bytes = base64.b64decode(img_data)
filepath.write_bytes(img_bytes)
saved_files.append(str(filepath.absolute()))
print(f"Saved: {filepath.absolute()}")
return {
"success": True,
"model": model,
"prompt": prompt,
"images": saved_files,
"count": len(saved_files),
}
def main():
parser = argparse.ArgumentParser(description="Generate images with Venice.ai")
parser.add_argument("prompt", help="Image description")
parser.add_argument("--model", default=DEFAULT_MODEL, help=f"Model ID (default: {DEFAULT_MODEL})")
parser.add_argument("--resolution", default=DEFAULT_RESOLUTION, choices=["1K", "2K", "4K"], help="Resolution")
parser.add_argument("--aspect_ratio", default=DEFAULT_ASPECT_RATIO, help="Aspect ratio (e.g. 1:1, 16:9)")
parser.add_argument("--negative_prompt", help="What to avoid")
parser.add_argument("--variants", type=int, default=1, choices=[1,2,3,4], help="Number of images")
parser.add_argument("--format", default=DEFAULT_FORMAT, choices=["webp", "png", "jpeg"], help="Image format")
parser.add_argument("--seed", type=int, help="Random seed")
parser.add_argument("--no-safe-mode", action="store_true", help="Disable safe mode")
parser.add_argument("--output", "-o", help="Output path")
args = parser.parse_args()
result = generate_image(
prompt=args.prompt,
model=args.model,
resolution=args.resolution,
aspect_ratio=args.aspect_ratio,
negative_prompt=args.negative_prompt,
variants=args.variants,
format=args.format,
seed=args.seed,
safe_mode=not args.no_safe_mode,
output_path=args.output,
)
if result["success"]:
print(f"\nGenerated {result['count']} image(s)")
else:
print(f"\nFailed: {result.get('error')}")
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,69 @@
# Venice List Image Models
List all available image generation models from the [Venice.ai](https://venice.ai/) API with pricing, constraints, and capabilities.
## Features
- Lists all available image generation models
- Shows per-generation and upscale pricing (USD)
- Displays constraints (steps, prompt character limits, resolutions)
- Summary statistics (price range, averages)
- Structured Pydantic models for programmatic use
## Prerequisites
```bash
pip install requests pydantic
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
```bash
python scripts/list_image_models.py
```
## Output
Displays a formatted table:
```
Model ID Name Gen $ 2x Up $ 4x Up $ Steps Prompt Limit
-------------------------------------------------------------------------------------------------------------------
nano-banana-2 Nano Banana 2 0.01 0.02 0.04 30/50 1500
...
```
Plus summary statistics:
```
=== Summary ===
Total models: 8
Price range: $0.01 - $0.05
Average price: $0.025
```
## Python Import
```python
from list_image_models import list_image_models, format_models_table, get_models_summary
models = list_image_models()
# Formatted table
print(format_models_table(models))
# Summary stats
summary = get_models_summary(models)
print(f"Total: {summary['total_models']}, cheapest: ${summary['min_price']:.2f}")
# Access individual models
for m in models.data:
print(f"{m.id}: {m.model_spec.name}")
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,47 @@
---
name: "venice-list-image-models"
description: "List available image generation models from Venice.ai API with pricing and constraints."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- image
- models
- generation
trigger_patterns:
- "list image models"
- "venice image models"
- "image generation models"
- "available image models"
---
# Venice List Image Models
List available image generation models from Venice.ai API.
## When to Use
Use this skill when you need to:
- List available image generation models from Venice.ai
- Check model pricing and constraints
- Find suitable models for image generation tasks
## Usage
```bash
python ~/.jae/agent/skills/venice-list-image-models/scripts/list_image_models.py
```
## Output
Returns for each model:
- Model ID and display name
- Pricing per image
- Resolution constraints
- Supported aspect ratios
- Capabilities
## Requirements
- `VENICE_API_KEY` environment variable (configured in Agent JAE secrets)

View file

@ -0,0 +1,156 @@
"""# Venice.ai List Image Models Instrument
List available image generation models from Venice.ai API.
Returns model names, pricing, constraints, and capabilities.
Usage: list_image_models() - returns all image models with pricing and constraints
"""
import os
import requests
from typing import Optional
from pydantic import BaseModel, Field
# API Configuration
VENICE_API_URL = "https://api.venice.ai/api/v1/models"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
# Pydantic Models
class PricePoint(BaseModel):
usd: float
diem: float
class UpscalePricing(BaseModel):
x2: Optional[PricePoint] = Field(default=None, alias="2x")
x4: Optional[PricePoint] = Field(default=None, alias="4x")
class ResolutionPricing(BaseModel):
r1k: Optional[PricePoint] = Field(default=None, alias="1K")
r2k: Optional[PricePoint] = Field(default=None, alias="2K")
r4k: Optional[PricePoint] = Field(default=None, alias="4K")
class ImagePricing(BaseModel):
generation: Optional[PricePoint] = None
resolutions: Optional[ResolutionPricing] = None
upscale: Optional[UpscalePricing] = None
class StepsConstraint(BaseModel):
default: int
max: int
class ImageConstraints(BaseModel):
promptCharacterLimit: int = 1500
steps: Optional[StepsConstraint] = None
widthHeightDivisor: int = 1
defaultResolution: Optional[str] = None
resolutions: Optional[list[str]] = None
class ImageModelSpec(BaseModel):
pricing: ImagePricing
constraints: ImageConstraints
supportsWebSearch: bool = False
name: str
modelSource: Optional[str] = None
offline: bool = False
privacy: str = "private"
traits: list[str] = Field(default_factory=list)
class ImageModel(BaseModel):
created: int
id: str
model_spec: ImageModelSpec
object: str = "model"
owned_by: str = ""
type: str = "image"
class ImageModelsResponse(BaseModel):
data: list[ImageModel]
object: str = "list"
type: str = "image"
def list_image_models() -> ImageModelsResponse:
"""
Fetch image generation models from Venice.ai API.
Returns:
ImageModelsResponse with list of available image models
"""
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
params = {"type": "image"}
response = requests.get(VENICE_API_URL, headers=headers, params=params, timeout=30)
response.raise_for_status()
data = response.json()
return ImageModelsResponse(**data)
def get_generation_price(spec: ImageModelSpec) -> float:
"""Get the base generation price (handles both flat and resolution-based pricing)."""
if spec.pricing.generation:
return spec.pricing.generation.usd
elif spec.pricing.resolutions and spec.pricing.resolutions.r1k:
return spec.pricing.resolutions.r1k.usd
return 0.0
def format_models_table(models: ImageModelsResponse) -> str:
"""Format models as a readable table."""
lines = []
lines.append(f"{'Model ID':<25} {'Name':<25} {'Gen $':<10} {'2x Up $':<8} {'4x Up $':<8} {'Steps':<12} {'Prompt Limit'}")
lines.append("-" * 115)
for m in sorted(models.data, key=lambda x: get_generation_price(x.model_spec)):
spec = m.model_spec
# Handle both pricing formats
if spec.pricing.generation:
gen_price = f"{spec.pricing.generation.usd:.2f}"
elif spec.pricing.resolutions:
prices = []
if spec.pricing.resolutions.r1k: prices.append(f"1K:{spec.pricing.resolutions.r1k.usd:.2f}")
if spec.pricing.resolutions.r2k: prices.append(f"2K:{spec.pricing.resolutions.r2k.usd:.2f}")
if spec.pricing.resolutions.r4k: prices.append(f"4K:{spec.pricing.resolutions.r4k.usd:.2f}")
gen_price = ",".join(prices) if prices else "-"
else:
gen_price = "-"
up2x = f"{spec.pricing.upscale.x2.usd:.2f}" if spec.pricing.upscale and spec.pricing.upscale.x2 else "-"
up4x = f"{spec.pricing.upscale.x4.usd:.2f}" if spec.pricing.upscale and spec.pricing.upscale.x4 else "-"
steps = f"{spec.constraints.steps.default}/{spec.constraints.steps.max}" if spec.constraints.steps else "-"
prompt_limit = spec.constraints.promptCharacterLimit
lines.append(
f"{m.id:<25} {spec.name:<25} {gen_price:<10} {up2x:<8} {up4x:<8} {steps:<12} {prompt_limit}"
)
return "\n".join(lines)
def get_models_summary(models: ImageModelsResponse) -> dict:
"""Get summary statistics for image models."""
prices = [get_generation_price(m.model_spec) for m in models.data]
return {
"total_models": len(models.data),
"min_price": min(prices),
"max_price": max(prices),
"avg_price": sum(prices) / len(prices),
"with_web_search": sum(1 for m in models.data if m.model_spec.supportsWebSearch),
"offline_models": sum(1 for m in models.data if m.model_spec.offline)
}
if __name__ == "__main__":
print("Fetching Venice.ai image models...\n")
models = list_image_models()
print(f"Total image models available: {len(models.data)}\n")
print(format_models_table(models))
print("\n=== Summary ===")
summary = get_models_summary(models)
print(f" Total models: {summary['total_models']}")
print(f" Price range: ${summary['min_price']:.2f} - ${summary['max_price']:.2f}")
print(f" Average price: ${summary['avg_price']:.3f}")

View file

@ -0,0 +1,84 @@
# Venice List Text Models
List all available text/LLM models from the [Venice.ai](https://venice.ai/) API with context windows, pricing, capabilities, and traits.
## Features
- Lists all available LLM/text models
- Shows context window sizes, input/output pricing per million tokens
- Displays capabilities (vision, reasoning, function calling, code optimization, web search)
- Filter by trait (e.g., `most_intelligent`, `default`, `most_uncensored`)
- Capabilities summary across all models
- Structured Pydantic models for programmatic use
## Prerequisites
```bash
pip install requests pydantic
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### List all models
```bash
python scripts/list_text_models.py
```
### Filter by trait
```bash
python scripts/list_text_models.py most_intelligent
python scripts/list_text_models.py default
```
## Output
Displays a formatted table sorted by context window size:
```
Model ID Name Context In $/M Out $/M Traits
------------------------------------------------------------------------------------------------------------------------
qwen3-235b-a22b-thinking-2507 Qwen3 235B Thinking 250K 0.50 2.00 most_intelligent
...
```
Plus capabilities summary:
```
=== Capabilities Summary ===
total: 15
with_reasoning: 4
with_vision: 6
with_function_calling: 8
with_web_search: 10
optimized_for_code: 3
```
## Python Import
```python
from list_text_models import list_text_models, get_capabilities_summary
# All models
models = list_text_models()
# Filtered by trait
intelligent = list_text_models(filter_trait="most_intelligent")
# Capabilities summary
summary = get_capabilities_summary(models)
print(f"Models with vision: {summary['with_vision']}")
# Access individual models
for m in models.data:
cap = m.model_spec.capabilities
print(f"{m.id}: vision={cap.supportsVision}, reasoning={cap.supportsReasoning}")
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,67 @@
---
name: "venice-list-text-models"
description: "List available text/LLM models from Venice.ai API with capabilities, context windows, and pricing."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- llm
- models
- text
trigger_patterns:
- "list text models"
- "venice text models"
- "llm models"
- "available llm models"
- "venice models"
---
# Venice List Text Models
List available text/LLM models from Venice.ai API.
## When to Use
Use this skill when you need to:
- List available LLM/text models from Venice.ai
- Check model capabilities and context windows
- Compare model pricing
- Find models with specific traits (most_intelligent, default, etc.)
## Usage
### Basic - List All Models
```bash
python ~/.jae/agent/skills/venice-list-text-models/scripts/list_text_models.py
```
### Filter by Trait
```bash
python ~/.jae/agent/skills/venice-list-text-models/scripts/list_text_models.py most_intelligent
python ~/.jae/agent/skills/venice-list-text-models/scripts/list_text_models.py default
```
## Output
Returns for each model:
- Model ID and display name
- Context window size
- Capabilities (vision, reasoning, etc.)
- Pricing (input/output per million tokens)
- Model traits
## Requirements
- `VENICE_API_KEY` environment variable (configured in Agent JAE secrets)
## Example Output
```
Model ID: claude-opus-45
Display Name: Claude Opus 4.5
Context Window: 200000 tokens
Capabilities: vision, reasoning
Pricing: $15.00/$75.00 per 1M tokens
Traits: most_intelligent
```

View file

@ -0,0 +1,146 @@
"""# Venice.ai List Text Models Instrument
List available text/LLM models from Venice.ai API.
Returns model names, capabilities, context windows, and pricing.
Usage: list_text_models(filter_trait=None) - optionally filter by trait like "most_intelligent", "default"
"""
import os
import requests
from typing import Optional
from pydantic import BaseModel, Field
# API Configuration
VENICE_API_URL = "https://api.venice.ai/api/v1/models"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
# Pydantic Models
class PricePoint(BaseModel):
usd: float
diem: float
class Pricing(BaseModel):
input: PricePoint
output: PricePoint
cache_input: Optional[PricePoint] = None
class Capabilities(BaseModel):
optimizedForCode: bool = False
quantization: Optional[str] = None
supportsAudioInput: bool = False
supportsFunctionCalling: bool = False
supportsLogProbs: bool = False
supportsReasoning: bool = False
supportsResponseSchema: bool = False
supportsVideoInput: bool = False
supportsVision: bool = False
supportsWebSearch: bool = False
class ParameterConstraint(BaseModel):
default: float
class Constraints(BaseModel):
temperature: Optional[ParameterConstraint] = None
top_p: Optional[ParameterConstraint] = None
class TextModelSpec(BaseModel):
pricing: Pricing
availableContextTokens: int
capabilities: Capabilities
constraints: Optional[Constraints] = None
description: str = ""
name: str
modelSource: Optional[str] = None
offline: bool = False
privacy: str = "private"
traits: list[str] = Field(default_factory=list)
class TextModel(BaseModel):
created: int
id: str
model_spec: TextModelSpec
object: str = "model"
owned_by: str = ""
type: str = "text"
class TextModelsResponse(BaseModel):
data: list[TextModel]
object: str = "list"
type: str = "text"
def list_text_models(filter_trait: Optional[str] = None) -> TextModelsResponse:
"""
Fetch text models from Venice.ai API.
Args:
filter_trait: Optional trait to filter by (e.g., "most_intelligent", "default", "most_uncensored")
Returns:
TextModelsResponse with list of available text models
"""
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
params = {"type": "text"}
response = requests.get(VENICE_API_URL, headers=headers, params=params, timeout=30)
response.raise_for_status()
data = response.json()
result = TextModelsResponse(**data)
if filter_trait:
result.data = [m for m in result.data if filter_trait in m.model_spec.traits]
return result
def format_models_table(models: TextModelsResponse) -> str:
"""Format models as a readable table."""
lines = []
lines.append(f"{'Model ID':<35} {'Name':<30} {'Context':<10} {'In $/M':<8} {'Out $/M':<8} {'Traits'}")
lines.append("-" * 120)
for m in sorted(models.data, key=lambda x: x.model_spec.availableContextTokens, reverse=True):
spec = m.model_spec
ctx = f"{spec.availableContextTokens // 1024}K"
traits = ", ".join(spec.traits) if spec.traits else "-"
lines.append(
f"{m.id:<35} {spec.name:<30} {ctx:<10} {spec.pricing.input.usd:<8.2f} {spec.pricing.output.usd:<8.2f} {traits}"
)
return "\n".join(lines)
def get_capabilities_summary(models: TextModelsResponse) -> dict:
"""Summarize capabilities across all models."""
summary = {
"total": len(models.data),
"with_reasoning": 0,
"with_vision": 0,
"with_function_calling": 0,
"with_web_search": 0,
"optimized_for_code": 0
}
for m in models.data:
cap = m.model_spec.capabilities
if cap.supportsReasoning: summary["with_reasoning"] += 1
if cap.supportsVision: summary["with_vision"] += 1
if cap.supportsFunctionCalling: summary["with_function_calling"] += 1
if cap.supportsWebSearch: summary["with_web_search"] += 1
if cap.optimizedForCode: summary["optimized_for_code"] += 1
return summary
if __name__ == "__main__":
print("Fetching Venice.ai text models...\n")
models = list_text_models()
print(f"Total text models available: {len(models.data)}\n")
print(format_models_table(models))
print("\n=== Capabilities Summary ===")
summary = get_capabilities_summary(models)
for key, val in summary.items():
print(f" {key}: {val}")

View file

@ -0,0 +1,80 @@
# Venice List Video Models
List all available video generation models from the [Venice.ai](https://venice.ai/) API with complete specifications including durations, resolutions, aspect ratios, audio capabilities, and input requirements.
## Features
- Lists all video models grouped by type (text-to-video, image-to-video, video)
- Shows supported durations, resolutions, aspect ratios
- Displays audio capabilities (generation, configurable, input)
- Detailed per-model specification view
- Example API request generation
- JSON output for programmatic use
## Prerequisites
```bash
pip install requests
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### Summary table (default)
```bash
python scripts/list_video_models.py
```
### Detailed specs for a specific model
```bash
python scripts/list_video_models.py --model kling-2.6-pro-text-to-video
```
### All models detailed
```bash
python scripts/list_video_models.py --detailed
```
### JSON output
```bash
python scripts/list_video_models.py --json
```
## Output Modes
| Flag | Description |
|------|-------------|
| *(default)* | Summary table grouped by model type |
| `--model <id>` | Detailed specs + example API request for one model |
| `--detailed` | Detailed specs for all models |
| `--json` | Full specs as JSON array |
## Important Notes
When generating videos, each model has strict parameter requirements:
- **`duration`** -- Use ONLY the values listed for that model
- **`aspect_ratio`** -- Only include if the model lists supported ratios (causes 400 errors otherwise)
- **`audio`** -- Check `audio_configurable` before including
- **`image_url`** -- REQUIRED for image-to-video models
## Python Import
```python
from list_video_models import fetch_video_models, format_summary_table
models = fetch_video_models()
for m in models:
print(f"{m.id}: type={m.model_type}, durations={m.durations}")
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,65 @@
---
name: "venice-list-video-models"
description: "List available video generation models from Venice.ai API with complete specifications for successful generation."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- video
- models
- generation
trigger_patterns:
- "list video models"
- "venice video models"
- "video generation models"
- "available video models"
---
# Venice List Video Models
List available video generation models with complete specifications.
## When to Use
Use this skill when you need to:
- List available video generation models
- Check model specifications (durations, resolutions, aspect ratios)
- Find the right model for your video generation task
- Get example API requests
## Usage
### Default - Summary Table
```bash
python ~/.jae/agent/skills/venice-list-video-models/scripts/list_video_models.py
```
### Detailed Model Specs
```bash
python ~/.jae/agent/skills/venice-list-video-models/scripts/list_video_models.py --model kling-2.6-pro-text-to-video
```
### All Models Detailed
```bash
python ~/.jae/agent/skills/venice-list-video-models/scripts/list_video_models.py --detailed
```
### JSON Output
```bash
python ~/.jae/agent/skills/venice-list-video-models/scripts/list_video_models.py --json
```
## Critical Parameters
| Parameter | Guidance |
|-----------|----------|
| `duration` | Use ONLY values listed for the model |
| `resolution` | Use listed values or omit for default |
| `aspect_ratio` | **ONLY include if model lists ratios** - causes 400 errors otherwise! |
| `audio` | Check `audio_configurable` |
| `image_url` | **REQUIRED** for image-to-video models |
## Requirements
- `VENICE_API_KEY` environment variable

View file

@ -0,0 +1,293 @@
"""List Venice.ai Video Models
This instrument fetches and displays all available video generation models from Venice.ai API
with comprehensive specifications for each model including:
- Supported durations
- Valid resolutions
- Aspect ratios (if configurable)
- Audio capabilities (generation, input, configurable)
- Video input support
- Beta/offline status
This information is CRITICAL for video generation - each model has different valid
parameter combinations that must be respected.
Usage:
python list_video_models.py # List all models
python list_video_models.py --detailed # Show full specs for each model
python list_video_models.py --model <id> # Show specs for specific model
python list_video_models.py --json # Output as JSON
"""
import os
import sys
import json
import requests
from typing import Optional
from dataclasses import dataclass, field, asdict
@dataclass
class VideoModelSpec:
"""Complete specification for a video generation model."""
id: str
name: str
model_type: str # text-to-video, image-to-video, video
# Generation constraints
durations: list = field(default_factory=list)
resolutions: list = field(default_factory=list)
aspect_ratios: list = field(default_factory=list)
# Audio capabilities
audio: bool = False
audio_configurable: bool = False
audio_input: bool = False
# Input requirements
video_input: bool = False
requires_image: bool = False
# Status
beta: bool = False
offline: bool = False
privacy: str = "anonymized"
VENICE_API_URL = "https://api.venice.ai/api/v1/models"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
def fetch_video_models():
"""Fetch all video models from Venice.ai API."""
if not VENICE_API_KEY:
raise ValueError("VENICE_API_KEY environment variable not set")
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
response = requests.get(VENICE_API_URL, headers=headers, params={"type": "video"}, timeout=30)
response.raise_for_status()
data = response.json()
models = []
for item in data.get("data", []):
spec = item.get("model_spec", {})
constraints = spec.get("constraints", {})
model_type = constraints.get("model_type", "unknown")
models.append(VideoModelSpec(
id=item.get("id", ""),
name=spec.get("name", item.get("id", "Unknown")),
model_type=model_type,
durations=constraints.get("durations", []),
resolutions=constraints.get("resolutions", []),
aspect_ratios=constraints.get("aspect_ratios", []),
audio=constraints.get("audio", False),
audio_configurable=constraints.get("audio_configurable", False),
audio_input=constraints.get("audio_input", False),
video_input=constraints.get("video_input", False),
requires_image=(model_type == "image-to-video"),
beta=spec.get("beta", False),
offline=spec.get("offline", False),
privacy=spec.get("privacy", "anonymized")
))
return models
def format_summary_table(models):
"""Format models as summary table grouped by type."""
lines = []
by_type = {}
for m in models:
by_type.setdefault(m.model_type, []).append(m)
for mtype in ["text-to-video", "image-to-video", "video"]:
if mtype not in by_type:
continue
type_models = by_type[mtype]
lines.append("")
lines.append("=" * 115)
lines.append(f" {mtype.upper()} MODELS ({len(type_models)})")
lines.append("=" * 115)
lines.append("")
lines.append(f" {'Model ID':<40} {'Durations':<25} {'Res':<12} {'Audio':<12} {'Audio In'}")
lines.append(f" {'-'*40} {'-'*25} {'-'*12} {'-'*12} {'-'*10}")
for m in sorted(type_models, key=lambda x: x.name):
durations = ", ".join(m.durations) if m.durations else "N/A"
resolutions = ", ".join(m.resolutions) if m.resolutions else "default"
audio = "Yes" if m.audio else "No"
if m.audio_configurable:
audio += " (cfg)"
audio_in = "Yes" if m.audio_input else "No"
status = ""
if m.beta:
status = " [BETA]"
if m.offline:
status = " [OFFLINE]"
lines.append(f" {m.id:<40} {durations:<25} {resolutions:<12} {audio:<12} {audio_in}{status}")
return "\n".join(lines)
def format_detailed_spec(model):
"""Format detailed specs for a single model."""
lines = []
lines.append("")
lines.append("=" * 75)
lines.append(f" {model.name}")
lines.append(f" ID: {model.id}")
lines.append("=" * 75)
lines.append("")
lines.append(f" Type: {model.model_type}")
status = []
if model.beta:
status.append("BETA")
if model.offline:
status.append("OFFLINE")
if status:
lines.append(f" Status: {', '.join(status)}")
lines.append("")
lines.append(" GENERATION PARAMETERS:")
lines.append(" " + "-" * 40)
if model.durations:
lines.append(f" duration: {' | '.join(model.durations)}")
else:
lines.append(f" duration: (not configurable)")
if model.resolutions:
lines.append(f" resolution: {' | '.join(model.resolutions)}")
else:
lines.append(f" resolution: (model default)")
if model.aspect_ratios:
lines.append(f" aspect_ratio: {' | '.join(model.aspect_ratios)}")
else:
lines.append(f" aspect_ratio: (NOT SUPPORTED - do not include in request)")
lines.append("")
lines.append(" AUDIO CAPABILITIES:")
lines.append(" " + "-" * 40)
lines.append(f" audio: {'Yes - generates audio' if model.audio else 'No'}")
lines.append(f" audio_configurable: {'Yes - can toggle on/off' if model.audio_configurable else 'No'}")
lines.append(f" audio_input: {'Yes - accepts audio' if model.audio_input else 'No'}")
lines.append("")
lines.append(" INPUT REQUIREMENTS:")
lines.append(" " + "-" * 40)
if model.model_type == "text-to-video":
lines.append(f" prompt: Required (text description)")
lines.append(f" image_url: Not used")
elif model.model_type == "image-to-video":
lines.append(f" prompt: Required (text description)")
lines.append(f" image_url: REQUIRED (base64 data URL or HTTP URL)")
elif model.model_type == "video":
lines.append(f" prompt: Required (text description)")
lines.append(f" video_url: {'Required' if model.video_input else 'Optional'}")
return "\n".join(lines)
def format_generation_example(model):
"""Generate example API call for this model."""
example = {
"model": model.id,
"prompt": "Your detailed prompt here"
}
if model.durations:
example["duration"] = model.durations[0]
if model.resolutions:
example["resolution"] = model.resolutions[0]
if model.aspect_ratios:
example["aspect_ratio"] = model.aspect_ratios[0]
if model.audio_configurable:
example["audio"] = True
if model.model_type == "image-to-video":
example["image_url"] = "data:image/jpeg;base64,... OR https://..."
return json.dumps(example, indent=2)
def output_json(models):
"""Output all models as JSON."""
return json.dumps([asdict(m) for m in models], indent=2)
def main():
args = sys.argv[1:]
print("Fetching Venice.ai video models...")
print()
try:
models = fetch_video_models()
if "--json" in args:
print(output_json(models))
return
if "--model" in args:
idx = args.index("--model")
if idx + 1 < len(args):
model_id = args[idx + 1]
matching = [m for m in models if m.id == model_id or model_id in m.id]
if matching:
for m in matching:
print(format_detailed_spec(m))
print("\n EXAMPLE API REQUEST:")
print(" " + "-" * 40)
for line in format_generation_example(m).split("\n"):
print(f" {line}")
else:
print(f"No model found matching: {model_id}")
return
if "--detailed" in args:
for m in models:
print(format_detailed_spec(m))
return
print(f"Total video models available: {len(models)}")
print(format_summary_table(models))
print()
print("=" * 100)
print(" USAGE")
print("=" * 100)
print("""
--model <id> Show detailed specs for a specific model
--detailed Show specs for all models
--json Output as JSON
CRITICAL FOR VIDEO GENERATION:
* Each model has specific valid durations - use ONLY listed values
* aspect_ratio: ONLY include if model lists them (otherwise causes 400 errors)
* audio: check audio_configurable before including
* image-to-video models REQUIRE image_url parameter
""")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,95 @@
# Venice Text-to-Speech
Convert text to speech using the [Venice.ai](https://venice.ai/) TTS API. Supports 50+ voices across 9 languages with multiple audio formats and adjustable speed.
## Features
- **50+ voices** across American English, British English, Chinese, French, Hindi, Italian, Japanese, Portuguese
- **Multiple formats** -- mp3, opus, aac, flac, wav, pcm
- **Adjustable speed** -- 0.25x to 4.0x
- **Max 4096 characters** per request
- Model: `tts-kokoro`
## Prerequisites
```bash
pip install requests
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### Basic
```bash
python scripts/text_to_speech.py "Hello, welcome to Venice Voice."
```
### With voice selection
```bash
python scripts/text_to_speech.py "Hello world" --voice am_adam
```
### All options
```bash
python scripts/text_to_speech.py "Your text here" \
--voice af_bella \
--speed 1.2 \
--format wav \
--output greeting.wav
```
### List all voices
```bash
python scripts/text_to_speech.py "" --list-voices
```
## Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `text` | -- | *(required)* | Text to convert (max 4096 chars) |
| `--voice` | `-v` | `af_sky` | Voice ID |
| `--format` | `-f` | `mp3` | Audio format |
| `--speed` | `-s` | `1.0` | Speed (0.25-4.0) |
| `--output` | `-o` | auto | Output file path |
| `--list-voices` | -- | -- | List all available voices |
## Available Voices
| Prefix | Language | Voices |
|--------|----------|--------|
| `af_` | American Female | alloy, aoede, bella, heart, jadzia, jessica, kore, nicole, nova, river, sarah, sky |
| `am_` | American Male | adam, echo, eric, fenrir, liam, michael, onyx, puck, santa |
| `bf_` | British Female | alice, emma, lily |
| `bm_` | British Male | daniel, fable, george, lewis |
| `zf_` | Chinese Female | xiaobei, xiaoni, xiaoxiao, xiaoyi |
| `zm_` | Chinese Male | yunjian, yunxi, yunxia, yunyang |
| `ff_` | French Female | siwis |
| `hf_`/`hm_` | Hindi | alpha, beta, omega, psi |
| `if_`/`im_` | Italian | sara, nicola |
| `jf_`/`jm_` | Japanese | alpha, gongitsune, nezumi, tebukuro, kumo |
| `pf_`/`pm_` | Portuguese | dora, alex, santa |
## Python Import
```python
from text_to_speech import text_to_speech
result = text_to_speech(
text="Hello, this is a test.",
voice="am_adam",
format="mp3",
speed=1.0
)
print(f"Audio saved to: {result['output']}")
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,73 @@
---
name: "venice-tts"
description: "Convert text to speech using Venice.ai TTS API with multiple voices and languages."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- tts
- speech
- audio
- voice
trigger_patterns:
- "text to speech"
- "tts"
- "venice voice"
- "generate speech"
- "speak text"
- "convert to audio"
---
# Venice Text-to-Speech
Convert text to speech using Venice.ai TTS API.
## When to Use
Use this skill when you need to:
- Convert text to audio/speech
- Generate voiceovers
- Create audio content in multiple languages
## Usage
### Basic
```bash
python ~/.jae/agent/skills/venice-tts/scripts/text_to_speech.py "Hello, welcome to Venice Voice."
```
### With Voice Selection
```bash
python ~/.jae/agent/skills/venice-tts/scripts/text_to_speech.py "Hello world" --voice am_adam
```
### All Options
```bash
python ~/.jae/agent/skills/venice-tts/scripts/text_to_speech.py "Your text" --voice af_bella --speed 1.2 --format wav --output greeting.wav
```
## Available Voices
| Prefix | Language | Example Voices |
|--------|----------|----------------|
| af_ | American Female | alloy, bella, sky, nova, sarah |
| am_ | American Male | adam, echo, eric, liam, michael |
| bf_ | British Female | alice, emma, lily |
| bm_ | British Male | daniel, fable, george |
| jf_ | Japanese Female | alpha, gongitsune, nezumi |
| zf_ | Chinese Female | xiaobei, xiaoni, xiaoxiao |
## Options
| Option | Values | Default |
|--------|--------|--------|
| --voice | See table above | af_sky |
| --format | mp3, opus, aac, flac, wav, pcm | mp3 |
| --speed | 0.25 - 4.0 | 1.0 |
| --output | filename | auto-generated |
## Notes
- Max input: 4096 characters
- Requires `VENICE_API_KEY` environment variable

View file

@ -0,0 +1,176 @@
"""# Venice.ai Text-to-Speech Instrument
Convert text to speech using Venice.ai TTS API.
Usage: text_to_speech(text, voice="af_sky", format="mp3", speed=1.0)
NOTE: Max input 4096 characters.
"""
import os
import sys
import argparse
import requests
from pathlib import Path
from datetime import datetime
# API Configuration
VENICE_API_URL = "https://api.venice.ai/api/v1/audio/speech"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
# Defaults
DEFAULT_MODEL = "tts-kokoro" # Only option currently
DEFAULT_VOICE = "af_sky" # American female
DEFAULT_FORMAT = "mp3"
DEFAULT_SPEED = 1.0
# All available voices
VOICES = [
# American Female
"af_alloy", "af_aoede", "af_bella", "af_heart", "af_jadzia", "af_jessica",
"af_kore", "af_nicole", "af_nova", "af_river", "af_sarah", "af_sky",
# American Male
"am_adam", "am_echo", "am_eric", "am_fenrir", "am_liam", "am_michael",
"am_onyx", "am_puck", "am_santa",
# British Female
"bf_alice", "bf_emma", "bf_lily",
# British Male
"bm_daniel", "bm_fable", "bm_george", "bm_lewis",
# Chinese Female
"zf_xiaobei", "zf_xiaoni", "zf_xiaoxiao", "zf_xiaoyi",
# Chinese Male
"zm_yunjian", "zm_yunxi", "zm_yunxia", "zm_yunyang",
# French Female
"ff_siwis",
# Hindi
"hf_alpha", "hf_beta", "hm_omega", "hm_psi",
# Italian
"if_sara", "im_nicola",
# Japanese
"jf_alpha", "jf_gongitsune", "jf_nezumi", "jf_tebukuro", "jm_kumo",
# Portuguese
"pf_dora", "pm_alex", "pm_santa",
# English (generic)
"ef_dora", "em_alex", "em_santa",
]
FORMATS = ["mp3", "opus", "aac", "flac", "wav", "pcm"]
def text_to_speech(
text: str,
voice: str = DEFAULT_VOICE,
format: str = DEFAULT_FORMAT,
speed: float = DEFAULT_SPEED,
output_path: str = None,
) -> dict:
"""
Convert text to speech using Venice.ai TTS.
Args:
text: Text to convert (max 4096 characters)
voice: Voice ID (default: af_sky)
format: mp3, opus, aac, flac, wav, pcm (default: mp3)
speed: 0.25-4.0 (default: 1.0)
output_path: Save path (auto-generated if not provided)
Returns:
dict with audio path and metadata
"""
if not VENICE_API_KEY:
raise ValueError("VENICE_API_KEY environment variable not set")
if len(text) > 4096:
raise ValueError(f"Text too long: {len(text)} chars (max 4096)")
if voice not in VOICES:
print(f"Warning: Unknown voice '{voice}', using {DEFAULT_VOICE}")
voice = DEFAULT_VOICE
if format not in FORMATS:
print(f"Warning: Unknown format '{format}', using {DEFAULT_FORMAT}")
format = DEFAULT_FORMAT
if not (0.25 <= speed <= 4.0):
print(f"Warning: Speed {speed} out of range, clamping to [0.25, 4.0]")
speed = max(0.25, min(4.0, speed))
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
payload = {
"input": text,
"model": DEFAULT_MODEL,
"voice": voice,
"response_format": format,
"speed": speed,
"streaming": False,
}
print(f"Generating speech with voice '{voice}'...")
print(f"Text: {text[:80]}{'...' if len(text) > 80 else ''}")
response = requests.post(VENICE_API_URL, headers=headers, json=payload)
response.raise_for_status()
# Response is binary audio data
audio_data = response.content
# Determine output path
if output_path:
filepath = Path(output_path)
if not filepath.suffix:
filepath = Path(f"{output_path}.{format}")
else:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filepath = Path(f"speech_{timestamp}.{format}")
# Save audio
filepath.write_bytes(audio_data)
print(f"Saved: {filepath.absolute()}")
return {
"success": True,
"voice": voice,
"format": format,
"speed": speed,
"text_length": len(text),
"audio_size": len(audio_data),
"output": str(filepath.absolute()),
}
def main():
parser = argparse.ArgumentParser(description="Venice.ai Text-to-Speech")
parser.add_argument("text", help="Text to convert (max 4096 chars)")
parser.add_argument("--voice", "-v", default=DEFAULT_VOICE, help=f"Voice (default: {DEFAULT_VOICE})")
parser.add_argument("--format", "-f", default=DEFAULT_FORMAT, choices=FORMATS, help="Audio format")
parser.add_argument("--speed", "-s", type=float, default=DEFAULT_SPEED, help="Speed 0.25-4.0")
parser.add_argument("--output", "-o", help="Output path")
parser.add_argument("--list-voices", action="store_true", help="List all voices")
args = parser.parse_args()
if args.list_voices:
print("Available voices:")
for v in VOICES:
print(f" {v}")
return
result = text_to_speech(
text=args.text,
voice=args.voice,
format=args.format,
speed=args.speed,
output_path=args.output,
)
if result["success"]:
print(f"\nGenerated {result['audio_size']} bytes of audio")
else:
print(f"\nError: {result.get('error')}")
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,111 @@
# Venice Video Generate
Full-lifecycle video generation using [Venice.ai](https://venice.ai/). Combines queue, poll, retrieve, and save into a single operation with progress logging. This is the **recommended approach** for video generation.
## Features
- **Single-call generation** -- queue + poll + retrieve + save in one operation
- **Progress logging** every 20 seconds with progress bars and ETAs
- **Text-to-video** and **image-to-video** support
- **Auto-save** to disk with configurable output paths
- **Timeout protection** -- configurable max wait (default: 15 minutes)
- Handles both JSON status responses and binary video data
## Prerequisites
```bash
pip install requests
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### Basic text-to-video
```bash
python scripts/generate_video.py "A cat playing piano"
```
### With options
```bash
python scripts/generate_video.py "Ocean waves at sunset" \
--model kling-2.6-pro-text-to-video \
--duration 10s \
--resolution 1080p \
--aspect-ratio 16:9
```
### Image-to-video
```bash
python scripts/generate_video.py "Make this image come alive" \
--image /path/to/image.png \
--model wan-2.5-preview-image-to-video
```
### Custom output
```bash
python scripts/generate_video.py "Dancing robot" --output ./my_video.mp4
```
## Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `prompt` | -- | *(required)* | Text description of the video |
| `--model` | `-m` | `wan-2.5-preview-text-to-video` | Model ID |
| `--duration` | `-d` | `5s` | Duration (e.g., `5s`, `10s`) |
| `--resolution` | `-r` | `720p` | Resolution (e.g., `720p`, `1080p`) |
| `--aspect-ratio` | `-a` | `16:9` | Aspect ratio (e.g., `16:9`, `9:16`, `1:1`) |
| `--audio` | -- | off | Enable audio generation |
| `--no-audio` | -- | -- | Explicitly disable audio |
| `--negative-prompt` | `-n` | None | What to avoid |
| `--image` | `-i` | None | Input image for image-to-video |
| `--output` | `-o` | auto | Output file path |
| `--output-dir` | -- | `/root/venice_videos` | Output directory |
| `--max-wait` | -- | `900` | Max wait seconds (15 min) |
| `--quiet` | `-q` | off | Suppress progress output |
## Common Models
### Text-to-Video
| Model | Quality | Speed |
|-------|---------|-------|
| `wan-2.5-preview-text-to-video` | Good | Fast (30-60s for 5s video) |
| `kling-2.6-pro-text-to-video` | Higher | Slower (60-120s) |
| `veo3.1-full-text-to-video` | Excellent | Slowest (90-180s) |
### Image-to-Video
| Model | Notes |
|-------|-------|
| `wan-2.5-preview-image-to-video` | Fast, reliable |
| `veo3.1-full-image-to-video` | Premium quality |
## Python Import
```python
from generate_video import generate_video
result = generate_video(
prompt="A beautiful sunset over mountains",
model="wan-2.5-preview-text-to-video",
duration="5s",
verbose=True
)
if result.success:
print(f"Video saved: {result.video_path}")
print(f"Took: {result.elapsed_seconds:.1f}s")
else:
print(f"Failed: {result.error}")
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,140 @@
---
name: "venice-video-generate"
description: "Generate complete video from prompt to saved file in single operation. Handles queue, poll, retrieve, save with progress logging."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- video
- generation
- complete
- text-to-video
- image-to-video
trigger_patterns:
- "generate video"
- "create video"
- "make video"
- "video from prompt"
- "text to video"
- "image to video"
---
# Venice Video Generate (Full Lifecycle)
Generate complete video from prompt to saved file in a single operation.
## When to Use
Use this skill when you need to:
- Generate video from text prompt (text-to-video)
- Generate video from image (image-to-video)
- Complete video generation with automatic polling and saving
**This is the recommended approach** - it combines queue + poll + retrieve + save into one call.
## Usage
### Quick Start (Text-to-Video)
```bash
python ~/.jae/agent/skills/venice-video-generate/scripts/generate_video.py "A cat playing piano"
```
### With Options
```bash
python ~/.jae/agent/skills/venice-video-generate/scripts/generate_video.py "Ocean waves at sunset" \
--model kling-2.6-pro-text-to-video \
--duration 10s \
--resolution 1080p
```
### Image-to-Video
```bash
python ~/.jae/agent/skills/venice-video-generate/scripts/generate_video.py "Make this image come alive" \
--image /path/to/image.png \
--model wan-2.5-preview-image-to-video
```
## Progress Logging (Every 20 Seconds)
```
[14:32:15] START: Queueing video generation with model: wan-2.5-preview-text-to-video
[14:32:15] CONFIG: duration=5s, resolution=720p, aspect_ratio=16:9
[14:32:16] QUEUED: queue_id=abc123-def456
[14:32:36] PROGRESS: 20s elapsed | [====----------------] 25% | ETA: 45s | status: processing
[14:32:56] PROGRESS: 40s elapsed | [==========----------] 55% | ETA: 25s | status: processing
[14:33:16] PROGRESS: 60s elapsed | [================----] 85% | ETA: 8s | status: processing
[14:33:21] COMPLETE: Video saved to /root/venice_videos/video_20260131_143321_abc123.mp4
```
## Result Output
```
======================================================================
RESULT: SUCCESS
VIDEO_PATH: /root/venice_videos/video_20260131_143321_abc123.mp4
ELAPSED_SECONDS: 65.3
QUEUE_ID: abc123-def456-...
MODEL: wan-2.5-preview-text-to-video
======================================================================
```
## Common Options
| Option | Default | Description |
|--------|---------|-------------|
| --model, -m | wan-2.5-preview-text-to-video | Venice model ID |
| --duration, -d | 5s | Video duration (5s, 10s, etc.) |
| --resolution, -r | 720p | Video resolution |
| --aspect-ratio, -a | 16:9 | Aspect ratio (16:9, 9:16, 1:1) |
| --audio | off | Enable audio generation |
| --image, -i | none | Input image (for image-to-video) |
| --output, -o | auto | Custom output path |
| --max-wait | 900 | Max wait seconds (15 min) |
## Model Selection
### Text-to-Video
- `wan-2.5-preview-text-to-video` - Default, fast, good quality
- `kling-2.6-pro-text-to-video` - Higher quality, slower
- `veo3.1-full-text-to-video` - Google Veo, excellent but expensive
### Image-to-Video
- `wan-2.5-preview-image-to-video` - Fast, reliable
- `veo3.1-full-image-to-video` - Premium quality
## Expected Generation Times
| Model Type | 5s Video | 10s Video |
|------------|----------|----------|
| Wan 2.5 | 30-60s | 60-120s |
| Kling 2.6 Pro | 60-120s | 120-240s |
| Veo 3.1 | 90-180s | 180-360s |
## Python Import
```python
import sys
sys.path.insert(0, '~/.jae/agent/skills/venice-video-generate/scripts')
from generate_video import generate_video, GenerationResult
result = generate_video(
prompt="A beautiful sunset over mountains",
model="wan-2.5-preview-text-to-video",
duration="5s",
verbose=True
)
if result.success:
print(f"Video saved: {result.video_path}")
else:
print(f"Failed: {result.error}")
```
## Default Output Directory
`/root/venice_videos/` (auto-created)
## Requirements
- `VENICE_API_KEY` environment variable

View file

@ -0,0 +1,550 @@
"""Venice.ai Full Lifecycle Video Generation
Combines video queue and retrieve into a single operation with progress logging.
Optimized for Agent JAE environment - clear output, efficient polling, agent-friendly responses.
Usage:
# CLI
python generate_video.py "A cat playing piano" --model wan-2.5-preview-text-to-video --duration 5s
# Python import
from generate_video import generate_video
result = generate_video(prompt="A cat playing piano", model="wan-2.5-preview-text-to-video")
"""
import os
import sys
import time
import argparse
import base64
import requests
from pathlib import Path
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from dataclasses import dataclass, field
# ============================================================================
# CONFIGURATION
# ============================================================================
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
VENICE_QUEUE_URL = "https://api.venice.ai/api/v1/video/queue"
VENICE_RETRIEVE_URL = "https://api.venice.ai/api/v1/video/retrieve"
DEFAULT_OUTPUT_DIR = "/root/venice_videos"
DEFAULT_MODEL = "wan-2.5-preview-text-to-video"
PROGRESS_LOG_INTERVAL = 20 # seconds between progress logs
DEFAULT_POLL_INTERVAL = 5 # seconds between API polls
DEFAULT_MAX_WAIT = 900 # 15 minutes max wait
# ============================================================================
# DATA CLASSES
# ============================================================================
@dataclass
class GenerationResult:
"""Result of a full video generation lifecycle."""
success: bool
video_path: Optional[str] = None
queue_id: Optional[str] = None
model: Optional[str] = None
elapsed_seconds: float = 0.0
error: Optional[str] = None
# Timing statistics from API
api_eta_seconds: Optional[int] = None
api_progress: Optional[float] = None
def __str__(self):
if self.success:
return f"SUCCESS: Video saved to {self.video_path} (took {self.elapsed_seconds:.1f}s)"
return f"FAILED: {self.error}"
@dataclass
class ProgressInfo:
"""Progress information for logging."""
elapsed_seconds: float
status: str
api_progress: Optional[float] = None
api_eta_seconds: Optional[int] = None
poll_count: int = 0
def format_progress_bar(self, width: int = 20) -> str:
"""Generate a text progress bar."""
if self.api_progress is None:
return "[" + "?" * width + "]"
pct = min(100, max(0, self.api_progress))
filled = int(width * pct / 100)
return "[" + "=" * filled + "-" * (width - filled) + "]"
def format_eta(self) -> str:
"""Format ETA as human-readable string."""
if self.api_eta_seconds is None:
return "ETA: unknown"
if self.api_eta_seconds <= 0:
return "ETA: completing..."
mins, secs = divmod(self.api_eta_seconds, 60)
if mins > 0:
return f"ETA: {mins}m {secs}s"
return f"ETA: {secs}s"
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
def encode_file_to_base64(file_path: str) -> str:
"""Read a file and return base64-encoded data URI."""
path = Path(file_path)
if not path.exists():
raise FileNotFoundError(f"File not found: {file_path}")
suffix = path.suffix.lower()
mime_types = {
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
'.gif': 'image/gif', '.webp': 'image/webp', '.mp4': 'video/mp4',
'.webm': 'video/webm', '.mp3': 'audio/mpeg', '.wav': 'audio/wav',
}
mime = mime_types.get(suffix, 'application/octet-stream')
with open(path, 'rb') as f:
data = base64.b64encode(f.read()).decode('utf-8')
return f"data:{mime};base64,{data}"
def log_progress(info: ProgressInfo) -> None:
"""Log progress in agent-friendly format."""
progress_str = f"{info.api_progress:.0f}%" if info.api_progress is not None else "---%"
bar = info.format_progress_bar()
eta = info.format_eta()
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] PROGRESS: {info.elapsed_seconds:>6.0f}s elapsed | "
f"{bar} {progress_str:>4} | {eta} | status: {info.status}")
sys.stdout.flush()
def log_event(event_type: str, message: str) -> None:
"""Log an event in agent-friendly format."""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {event_type}: {message}")
sys.stdout.flush()
# ============================================================================
# CORE API FUNCTIONS
# ============================================================================
def queue_video(
model: str,
prompt: str,
duration: str = "5s",
aspect_ratio: Optional[str] = None,
resolution: str = "720p",
audio: Optional[bool] = None,
negative_prompt: Optional[str] = None,
image_path: Optional[str] = None,
image_url: Optional[str] = None,
) -> Dict[str, Any]:
"""Queue a video for generation. Returns dict with model and queue_id."""
if not VENICE_API_KEY:
raise ValueError("VENICE_API_KEY environment variable not set")
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
# Handle image input
if image_path and not image_url:
image_url = encode_file_to_base64(image_path)
# Build request with only provided fields
request_data = {
"model": model,
"prompt": prompt,
"duration": duration,
"resolution": resolution,
}
if aspect_ratio is not None:
request_data["aspect_ratio"] = aspect_ratio
if audio is not None:
request_data["audio"] = audio
if negative_prompt:
request_data["negative_prompt"] = negative_prompt
if image_url:
request_data["image_url"] = image_url
response = requests.post(
VENICE_QUEUE_URL,
headers=headers,
json=request_data,
timeout=60
)
if not response.ok:
try:
error_detail = response.json()
except:
error_detail = response.text
raise RuntimeError(f"Queue API Error {response.status_code}: {error_detail}")
return response.json()
def retrieve_video_status(
model: str,
queue_id: str,
delete_on_completion: bool = False
) -> Dict[str, Any]:
"""Single retrieve request. Returns status dict or video bytes."""
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
request_data = {
"model": model,
"queue_id": queue_id,
"delete_media_on_completion": delete_on_completion
}
response = requests.post(
VENICE_RETRIEVE_URL,
headers=headers,
json=request_data,
timeout=120
)
if not response.ok:
return {
"status": "error",
"error": f"HTTP {response.status_code}: {response.text[:200]}"
}
content_type = response.headers.get("Content-Type", "")
# Check if response is video data (binary)
if "video" in content_type or response.content[:4] == b'\x00\x00\x00' or b'ftyp' in response.content[:20]:
return {
"status": "completed",
"video_data": response.content
}
# Try to parse as JSON
try:
data = response.json()
# Normalize status to lowercase
if "status" in data:
data["status"] = data["status"].lower()
return data
except:
# Might be binary video without proper content-type
if len(response.content) > 1000:
return {
"status": "completed",
"video_data": response.content
}
return {
"status": "error",
"error": "Failed to parse response"
}
# ============================================================================
# MAIN GENERATION FUNCTION
# ============================================================================
def generate_video(
prompt: str,
model: str = DEFAULT_MODEL,
duration: str = "5s",
aspect_ratio: Optional[str] = "16:9",
resolution: str = "720p",
audio: Optional[bool] = None,
negative_prompt: Optional[str] = None,
image_path: Optional[str] = None,
image_url: Optional[str] = None,
output_path: Optional[str] = None,
output_dir: str = DEFAULT_OUTPUT_DIR,
max_wait: int = DEFAULT_MAX_WAIT,
poll_interval: int = DEFAULT_POLL_INTERVAL,
progress_interval: int = PROGRESS_LOG_INTERVAL,
verbose: bool = True,
delete_on_completion: bool = False,
) -> GenerationResult:
"""
Full lifecycle video generation: queue, poll with progress, retrieve, save.
Args:
prompt: Text description of the video to generate
model: Venice model ID (default: wan-2.5-preview-text-to-video)
duration: Video duration (e.g., "5s", "10s")
aspect_ratio: Aspect ratio (e.g., "16:9", "9:16", "1:1") - omit for some models
resolution: Video resolution (e.g., "720p", "1080p")
audio: Enable audio generation (model-dependent)
negative_prompt: What to avoid in generation
image_path: Local path to input image (for image-to-video)
image_url: URL/base64 of input image (for image-to-video)
output_path: Full path for output video (auto-generated if not provided)
output_dir: Directory for output (default: /root/venice_videos)
max_wait: Maximum seconds to wait for completion (default: 900)
poll_interval: Seconds between API polls (default: 5)
progress_interval: Seconds between progress logs (default: 20)
verbose: Print progress logs (default: True)
delete_on_completion: Delete from Venice servers after download
Returns:
GenerationResult with success status, video path, timing info
"""
start_time = time.time()
result = GenerationResult(success=False, model=model)
# ========== PHASE 1: QUEUE ==========
if verbose:
log_event("START", f"Queueing video generation with model: {model}")
log_event("CONFIG", f"duration={duration}, resolution={resolution}, aspect_ratio={aspect_ratio}")
try:
queue_response = queue_video(
model=model,
prompt=prompt,
duration=duration,
aspect_ratio=aspect_ratio,
resolution=resolution,
audio=audio,
negative_prompt=negative_prompt,
image_path=image_path,
image_url=image_url,
)
queue_id = queue_response.get("queue_id")
if not queue_id:
result.error = f"No queue_id in response: {queue_response}"
return result
result.queue_id = queue_id
if verbose:
log_event("QUEUED", f"queue_id={queue_id}")
except Exception as e:
result.error = f"Queue failed: {e}"
result.elapsed_seconds = time.time() - start_time
if verbose:
log_event("ERROR", result.error)
return result
# ========== PHASE 2: POLL WITH PROGRESS ==========
poll_count = 0
error_count = 0
last_progress_log = 0
if verbose:
log_event("POLLING", f"Waiting for video completion (max {max_wait}s, logging every {progress_interval}s)")
while True:
elapsed = time.time() - start_time
# Check timeout
if elapsed > max_wait:
result.error = f"Timeout after {max_wait}s"
result.elapsed_seconds = elapsed
if verbose:
log_event("TIMEOUT", result.error)
return result
# Poll API
poll_count += 1
status_response = retrieve_video_status(model, queue_id, delete_on_completion)
status = status_response.get("status", "unknown").lower()
api_progress = status_response.get("progress")
api_eta = status_response.get("eta")
# Update result with latest API timing info
result.api_progress = api_progress
result.api_eta_seconds = api_eta
# Log progress at intervals
if verbose and (elapsed - last_progress_log >= progress_interval):
info = ProgressInfo(
elapsed_seconds=elapsed,
status=status,
api_progress=api_progress,
api_eta_seconds=api_eta,
poll_count=poll_count
)
log_progress(info)
last_progress_log = elapsed
# Check completion
if status in ["completed", "complete"]:
video_data = status_response.get("video_data")
video_url = status_response.get("video_url")
if video_data or video_url:
# Save video
if output_path:
save_path = Path(output_path)
else:
Path(output_dir).mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = Path(output_dir) / f"video_{timestamp}_{queue_id[:8]}.mp4"
save_path.parent.mkdir(parents=True, exist_ok=True)
if video_data:
with open(save_path, "wb") as f:
f.write(video_data)
elif video_url:
if video_url.startswith("data:"):
header, encoded = video_url.split(",", 1)
with open(save_path, "wb") as f:
f.write(base64.b64decode(encoded))
else:
dl_response = requests.get(video_url, timeout=120)
dl_response.raise_for_status()
with open(save_path, "wb") as f:
f.write(dl_response.content)
result.success = True
result.video_path = str(save_path)
result.elapsed_seconds = time.time() - start_time
if verbose:
log_event("COMPLETE", f"Video saved to {save_path}")
log_event("TIMING", f"Total time: {result.elapsed_seconds:.1f}s")
return result
else:
result.error = "Completed but no video data received"
result.elapsed_seconds = time.time() - start_time
return result
# Check failure
if status == "failed":
result.error = f"Generation failed: {status_response.get('error', 'unknown')}"
result.elapsed_seconds = time.time() - start_time
if verbose:
log_event("FAILED", result.error)
return result
# Handle transient errors
if status == "error":
error_count += 1
if error_count > 10:
result.error = f"Too many API errors: {status_response.get('error')}"
result.elapsed_seconds = time.time() - start_time
return result
if verbose and error_count == 1:
log_event("RETRY", f"API error, retrying... ({status_response.get('error', '')})")
else:
error_count = 0
# Wait before next poll
time.sleep(poll_interval)
# ============================================================================
# CLI INTERFACE
# ============================================================================
def main():
parser = argparse.ArgumentParser(
description="Venice.ai Full Lifecycle Video Generation",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Basic text-to-video
python generate_video.py "A cat playing piano"
# With specific model and duration
python generate_video.py "Ocean waves at sunset" --model kling-2.6-pro-text-to-video --duration 10s
# Image-to-video
python generate_video.py "Make this image come alive" --image /path/to/image.png --model wan-2.5-preview-image-to-video
# Custom output path
python generate_video.py "Dancing robot" --output /root/my_video.mp4
"""
)
parser.add_argument("prompt", help="Text description of the video to generate")
parser.add_argument("--model", "-m", default=DEFAULT_MODEL,
help=f"Venice model ID (default: {DEFAULT_MODEL})")
parser.add_argument("--duration", "-d", default="5s",
help="Video duration, e.g., 5s, 10s (default: 5s)")
parser.add_argument("--resolution", "-r", default="720p",
help="Video resolution (default: 720p)")
parser.add_argument("--aspect-ratio", "-a", default="16:9",
help="Aspect ratio, e.g., 16:9, 9:16, 1:1 (default: 16:9)")
parser.add_argument("--audio", action="store_true", default=None,
help="Enable audio generation")
parser.add_argument("--no-audio", action="store_true",
help="Disable audio generation")
parser.add_argument("--negative-prompt", "-n",
help="What to avoid in generation")
parser.add_argument("--image", "-i",
help="Input image path (for image-to-video models)")
parser.add_argument("--output", "-o",
help="Output video file path")
parser.add_argument("--output-dir", default=DEFAULT_OUTPUT_DIR,
help=f"Output directory (default: {DEFAULT_OUTPUT_DIR})")
parser.add_argument("--max-wait", type=int, default=DEFAULT_MAX_WAIT,
help=f"Maximum wait time in seconds (default: {DEFAULT_MAX_WAIT})")
parser.add_argument("--quiet", "-q", action="store_true",
help="Suppress progress output")
args = parser.parse_args()
# Handle audio flag
audio = None
if args.audio:
audio = True
elif args.no_audio:
audio = False
# Run generation
result = generate_video(
prompt=args.prompt,
model=args.model,
duration=args.duration,
aspect_ratio=args.aspect_ratio,
resolution=args.resolution,
audio=audio,
negative_prompt=args.negative_prompt,
image_path=args.image,
output_path=args.output,
output_dir=args.output_dir,
max_wait=args.max_wait,
verbose=not args.quiet,
)
# Final output for agent parsing
print("")
print("=" * 70)
if result.success:
print(f"RESULT: SUCCESS")
print(f"VIDEO_PATH: {result.video_path}")
print(f"ELAPSED_SECONDS: {result.elapsed_seconds:.1f}")
print(f"QUEUE_ID: {result.queue_id}")
print(f"MODEL: {result.model}")
else:
print(f"RESULT: FAILED")
print(f"ERROR: {result.error}")
print(f"ELAPSED_SECONDS: {result.elapsed_seconds:.1f}")
print("=" * 70)
return 0 if result.success else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,88 @@
# Venice Video Queue
Queue videos for generation on [Venice.ai](https://venice.ai/). Supports text-to-video, image-to-video, and video-to-video. Returns a `queue_id` for later retrieval with `venice-video-retrieve`.
> For a simpler all-in-one workflow, use [venice-video-generate](../venice-video-generate/) instead.
## Features
- **Text-to-video**, **image-to-video**, and **video-to-video** support
- Automatic file-to-base64 encoding for local image/video/audio inputs
- CLI with full argparse support
- JSON output mode for scripting
- Convenience functions for common workflows
## Prerequisites
```bash
pip install requests pydantic
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### Text-to-video
```bash
python scripts/queue_video.py "A cat playing piano in a jazz club" \
--duration 5s --resolution 720p --aspect-ratio 16:9
```
### Image-to-video
```bash
python scripts/queue_video.py "Animate this scene with gentle motion" \
--image /path/to/image.png \
--model wan-2.5-preview-image-to-video
```
### JSON output (for scripting)
```bash
python scripts/queue_video.py "Cinematic sunset" --json
# Output: {"model": "wan-2.5-preview-text-to-video", "queue_id": "abc123..."}
```
## Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `prompt` | -- | *(required)* | Text description |
| `--model` | `-m` | `wan-2.5-preview-text-to-video` | Model ID |
| `--duration` | `-d` | `5s` | Duration (`5s`, `10s`) |
| `--resolution` | `-r` | `720p` | Resolution |
| `--aspect-ratio` | `-a` | None | Aspect ratio (omit if model doesn't support) |
| `--negative-prompt` | `-n` | None | What to avoid |
| `--image` | `-i` | None | Input image path |
| `--video` | `-v` | None | Input video path |
| `--audio` | -- | None | Audio file path |
| `--with-audio` | -- | off | Enable audio generation |
| `--json` | -- | off | JSON output |
## After Queuing
Use the returned `queue_id` with `venice-video-retrieve`:
```bash
python ../venice-video-retrieve/scripts/retrieve_video.py MODEL QUEUE_ID
```
## Python Import
```python
from queue_video import queue_video, queue_text_to_video, queue_image_to_video
# Text-to-video
result = queue_text_to_video(prompt="A cat playing piano", duration="5s")
print(f"Queue ID: {result.queue_id}")
# Image-to-video
result = queue_image_to_video(prompt="Animate this", image_path="/path/to/img.png")
print(f"Queue ID: {result.queue_id}")
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,116 @@
---
name: "venice-video-queue"
description: "Queue a video for generation on Venice.ai (text-to-video, image-to-video). Returns queue_id for retrieval."
version: "1.0.0"
author: "Agent JAE"
tags: ["venice", "video", "ai", "generation", "queue"]
trigger_patterns:
- "queue video"
- "start video generation"
- "venice video queue"
---
# Venice Video Queue
Queue videos for generation on Venice.ai. Supports text-to-video and image-to-video.
## Requirements
- `VENICE_API_KEY` environment variable
## CLI Usage
```bash
python ~/.jae/agent/skills/venice-video-queue/scripts/queue_video.py PROMPT [options]
```
### Arguments
| Argument | Required | Description |
|----------|----------|-------------|
| `PROMPT` | Yes | Text prompt describing the video |
### Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `--model` | `-m` | `wan-2.5-preview-text-to-video` | Model to use |
| `--duration` | `-d` | `5s` | Video duration: `5s` or `10s` |
| `--resolution` | `-r` | `720p` | Resolution: `480p`, `720p`, `1080p` |
| `--aspect-ratio` | `-a` | None | Aspect ratio e.g. `16:9`, `9:16`, `1:1` |
| `--negative-prompt` | `-n` | None | Things to avoid in the video |
| `--image` | `-i` | None | Input image path for image-to-video |
| `--video` | `-v` | None | Input video path for video-to-video |
| `--audio` | | None | Audio file path to include |
| `--with-audio` | | False | Enable audio generation |
| `--json` | | False | Output result as JSON |
## Examples
### Text-to-Video
```bash
python ~/.jae/agent/skills/venice-video-queue/scripts/queue_video.py "A cat playing piano in a jazz club" \
--duration 5s \
--resolution 720p \
--aspect-ratio 16:9
```
### Image-to-Video
```bash
python ~/.jae/agent/skills/venice-video-queue/scripts/queue_video.py "Animate this scene with gentle motion" \
--image /path/to/image.png \
--model wan-2.5-preview-image-to-video \
--duration 5s
```
### JSON Output
```bash
python ~/.jae/agent/skills/venice-video-queue/scripts/queue_video.py "Cinematic sunset" --json
# Output: {"model": "wan-2.5-preview-text-to-video", "queue_id": "abc123..."}
```
## Output
On success, displays:
```
✅ Video queued successfully!
Model: wan-2.5-preview-text-to-video
Queue ID: abc123-def456-...
To retrieve, run:
python retrieve_video.py wan-2.5-preview-text-to-video abc123-def456-...
```
## Retrieval
After queuing, use the `venice-video-retrieve` skill:
```bash
python ~/.jae/agent/skills/venice-video-retrieve/scripts/retrieve_video.py MODEL QUEUE_ID
```
## Common Models
| Model | Type | Notes |
|-------|------|-------|
| `wan-2.5-preview-text-to-video` | Text-to-video | Default, good quality |
| `wan-2.5-preview-image-to-video` | Image-to-video | Animate still images |
| `veo3.1-full-image-to-video` | Image-to-video | No aspect_ratio support |
## Programmatic Usage
```python
from queue_video import queue_video, queue_text_to_video, queue_image_to_video
# Text-to-video
result = queue_text_to_video(
prompt="A cat playing piano",
duration="5s",
resolution="720p"
)
print(f"Queue ID: {result.queue_id}")
# Image-to-video
result = queue_image_to_video(
prompt="Animate this scene",
image_path="/path/to/image.png"
)
```

View file

@ -0,0 +1,249 @@
"""# Venice.ai Video Queue Skill
Queue a video for generation (text-to-video, image-to-video, or video-to-video).
Usage:
python queue_video.py "prompt" [options]
Examples:
# Text-to-video
python queue_video.py "A cat playing piano" --model wan-2.5-preview-text-to-video --duration 5s
# Image-to-video
python queue_video.py "Make this image come alive" --image /path/to/image.png --model wan-2.5-preview-image-to-video
"""
import os
import sys
import json
import base64
import argparse
import requests
from pathlib import Path
from typing import Optional
from pydantic import BaseModel, Field
# API Configuration
VENICE_QUEUE_URL = "https://api.venice.ai/api/v1/video/queue"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
class VideoQueueResponse(BaseModel):
"""Response from video queue endpoint."""
model: str
queue_id: str
def encode_file_to_base64(file_path: str) -> str:
"""Read a file and return base64-encoded data URI."""
path = Path(file_path)
if not path.exists():
raise FileNotFoundError(f"File not found: {file_path}")
suffix = path.suffix.lower()
mime_types = {
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
'.gif': 'image/gif', '.webp': 'image/webp', '.mp4': 'video/mp4',
'.webm': 'video/webm', '.mp3': 'audio/mpeg', '.wav': 'audio/wav',
}
mime = mime_types.get(suffix, 'application/octet-stream')
with open(path, 'rb') as f:
data = base64.b64encode(f.read()).decode('utf-8')
return f"data:{mime};base64,{data}"
def queue_video(
model: str,
prompt: str,
duration: str = "5s",
aspect_ratio: Optional[str] = None,
resolution: str = "720p",
audio: Optional[bool] = None,
negative_prompt: Optional[str] = None,
image_path: Optional[str] = None,
video_path: Optional[str] = None,
audio_path: Optional[str] = None,
image_url: Optional[str] = None,
video_url: Optional[str] = None,
audio_url: Optional[str] = None,
) -> VideoQueueResponse:
"""
Queue a video for generation.
Only sends fields that are explicitly provided.
Note: aspect_ratio is only included if explicitly set.
Some models (e.g., veo3.1-full-image-to-video) do NOT support aspect_ratio.
"""
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
# Encode files if paths provided
if image_path and not image_url:
image_url = encode_file_to_base64(image_path)
if video_path and not video_url:
video_url = encode_file_to_base64(video_path)
if audio_path and not audio_url:
audio_url = encode_file_to_base64(audio_path)
# Build request - only include required and explicitly provided fields
request_data = {
"model": model,
"prompt": prompt,
"duration": duration,
"resolution": resolution,
}
# aspect_ratio - only add if explicitly provided (some models don't support it)
if aspect_ratio is not None:
request_data["aspect_ratio"] = aspect_ratio
# Optional fields - only add if explicitly set
if audio is not None:
request_data["audio"] = audio
if negative_prompt:
request_data["negative_prompt"] = negative_prompt
if image_url:
request_data["image_url"] = image_url
if video_url:
request_data["video_url"] = video_url
if audio_url:
request_data["audio_url"] = audio_url
response = requests.post(
VENICE_QUEUE_URL,
headers=headers,
json=request_data,
timeout=60
)
# Better error handling
if not response.ok:
try:
error_detail = response.json()
except:
error_detail = response.text
raise RuntimeError(f"API Error {response.status_code}: {error_detail}")
return VideoQueueResponse(**response.json())
def queue_text_to_video(
prompt: str,
model: str = "wan-2.5-preview-text-to-video",
duration: str = "5s",
resolution: str = "720p",
aspect_ratio: str = "16:9",
negative_prompt: Optional[str] = None,
) -> VideoQueueResponse:
"""Queue text-to-video generation."""
return queue_video(
model=model,
prompt=prompt,
duration=duration,
resolution=resolution,
aspect_ratio=aspect_ratio,
negative_prompt=negative_prompt,
)
def queue_image_to_video(
prompt: str,
image_path: str,
model: str = "wan-2.5-preview-image-to-video",
duration: str = "5s",
resolution: str = "720p",
aspect_ratio: Optional[str] = None,
negative_prompt: Optional[str] = None,
) -> VideoQueueResponse:
"""Queue image-to-video generation."""
return queue_video(
model=model,
prompt=prompt,
duration=duration,
resolution=resolution,
aspect_ratio=aspect_ratio,
image_path=image_path,
negative_prompt=negative_prompt,
)
def main():
"""CLI entry point."""
parser = argparse.ArgumentParser(
description="Queue a video for generation on Venice.ai",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""Examples:
# Text-to-video (default)
python queue_video.py "A cat playing piano" --duration 5s
# Image-to-video
python queue_video.py "Animate this scene" --image /path/to/image.png
# Custom model and settings
python queue_video.py "Cinematic landscape" --model wan-2.5-preview-text-to-video --duration 10s --resolution 1080p --aspect-ratio 16:9
"""
)
parser.add_argument("prompt", help="Text prompt describing the video to generate")
parser.add_argument("--model", "-m", default="wan-2.5-preview-text-to-video",
help="Model to use (default: wan-2.5-preview-text-to-video)")
parser.add_argument("--duration", "-d", default="5s",
help="Video duration: 5s or 10s (default: 5s)")
parser.add_argument("--resolution", "-r", default="720p",
help="Resolution: 480p, 720p, 1080p (default: 720p)")
parser.add_argument("--aspect-ratio", "-a", default=None,
help="Aspect ratio e.g. 16:9, 9:16, 1:1 (optional, not all models support)")
parser.add_argument("--negative-prompt", "-n", default=None,
help="Negative prompt - things to avoid")
parser.add_argument("--image", "-i", default=None,
help="Path to input image for image-to-video")
parser.add_argument("--video", "-v", default=None,
help="Path to input video for video-to-video")
parser.add_argument("--audio", default=None,
help="Path to audio file to include")
parser.add_argument("--with-audio", action="store_true",
help="Enable audio generation (if model supports)")
parser.add_argument("--json", action="store_true",
help="Output result as JSON")
args = parser.parse_args()
# Check API key
if not VENICE_API_KEY:
print("Error: VENICE_API_KEY environment variable not set", file=sys.stderr)
sys.exit(1)
try:
result = queue_video(
model=args.model,
prompt=args.prompt,
duration=args.duration,
resolution=args.resolution,
aspect_ratio=args.aspect_ratio,
negative_prompt=args.negative_prompt,
image_path=args.image,
video_path=args.video,
audio_path=args.audio,
audio=True if args.with_audio else None,
)
if args.json:
print(json.dumps({"model": result.model, "queue_id": result.queue_id}))
else:
print(f"✅ Video queued successfully!")
print(f" Model: {result.model}")
print(f" Queue ID: {result.queue_id}")
print(f"")
print(f"To retrieve, run:")
print(f" python retrieve_video.py {result.model} {result.queue_id}")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,79 @@
# Venice Video Quote
Get cost estimates for [Venice.ai](https://venice.ai/) video generation before creating. Validates parameters against model capabilities to prevent invalid requests.
## Features
- **Cost estimation** before committing to video generation
- **Parameter validation** -- checks duration, aspect ratio, resolution, and audio against model capabilities
- **Model inspection** -- view valid options for any video model
- Structured Pydantic models for programmatic use
## Prerequisites
```bash
pip install requests pydantic
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
```bash
python scripts/get_video_quote.py
```
The default script demonstrates valid and invalid quote requests with model validation.
## Python Import
```python
from get_video_quote import get_video_quote, show_model_options
# Get a cost quote
quote = get_video_quote(
model="wan-2.5-preview-text-to-video",
duration="10s",
aspect_ratio="16:9",
resolution="720p",
audio=True
)
print(f"Estimated cost: ${quote.quote:.2f}")
# View valid options for a model
show_model_options("wan-2.5-preview-text-to-video")
# Skip validation (use at your own risk)
quote = get_video_quote(
model="wan-2.5-preview-text-to-video",
duration="5s",
validate=False
)
```
## Parameters
| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `model` | Yes | -- | Video model ID |
| `duration` | Yes | -- | Duration (e.g., `5s`, `10s`) |
| `aspect_ratio` | No | `16:9` | Aspect ratio |
| `resolution` | No | `720p` | Resolution |
| `audio` | No | `False` | Include audio |
| `validate` | No | `True` | Validate params against model capabilities |
## Validation
When `validate=True` (default), the function fetches the model's capabilities from the API and checks:
- Duration is in the model's supported list
- Aspect ratio is supported
- Resolution is supported
- Audio is supported (if requested)
Invalid parameters raise a `ValueError` with details about valid options.
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,56 @@
---
name: "venice-video-quote"
description: "Get cost estimate for Venice.ai video generation before creating. Validates parameters against model capabilities."
version: "1.0.0"
author: "Agent JAE"
tags:
- venice
- api
- video
- cost
- quote
trigger_patterns:
- "video quote"
- "video cost"
- "estimate video"
- "video price"
- "how much video"
---
# Venice Video Quote
Get cost estimate for video generation before creating.
## When to Use
Use this skill when you need to:
- Get cost estimates before generating videos
- Validate parameters against model capabilities
- Compare costs between different configurations
## Usage
```bash
python ~/.jae/agent/skills/venice-video-quote/scripts/get_video_quote.py <model> <duration> [aspect_ratio] [resolution] [audio]
```
## Arguments
| Argument | Required | Default | Description |
|----------|----------|---------|-------------|
| model | ✅ | - | Model ID |
| duration | ✅ | - | Video duration (5s, 10s, etc.) |
| aspect_ratio | ❌ | 16:9 | Aspect ratio |
| resolution | ❌ | 720p | Resolution |
| audio | ❌ | False | Enable audio |
## Example
```bash
# Get quote for 5-second video
python ~/.jae/agent/skills/venice-video-quote/scripts/get_video_quote.py wan-2.5-preview-text-to-video 5s 16:9 720p false
```
## Requirements
- `VENICE_API_KEY` environment variable

View file

@ -0,0 +1,193 @@
"""# Venice.ai Video Quote Instrument
Get cost estimate for video generation before creating.
Validates parameters against model capabilities from the video models endpoint.
Usage: get_video_quote(model, duration, aspect_ratio="16:9", resolution="720p", audio=False)
"""
import os
import requests
from typing import Optional
from pydantic import BaseModel, Field
# API Configuration
VENICE_QUOTE_URL = "https://api.venice.ai/api/v1/video/quote"
VENICE_MODELS_URL = "https://api.venice.ai/api/v1/models"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
# Pydantic Models
class VideoQuoteRequest(BaseModel):
model: str
duration: str
aspect_ratio: str = "16:9"
resolution: str = "720p"
audio: bool = False
class VideoQuoteResponse(BaseModel):
quote: float
model: Optional[str] = None
config: Optional[dict] = None
class ModelCapabilities(BaseModel):
"""Extracted capabilities for a video model."""
model_id: str
name: str
model_type: str
durations: list[str]
aspect_ratios: list[str]
resolutions: list[str]
supports_audio: bool
def get_video_model_capabilities(model_id: str) -> Optional[ModelCapabilities]:
"""Fetch capabilities for a specific video model."""
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
response = requests.get(
VENICE_MODELS_URL,
headers=headers,
params={"type": "video"},
timeout=30
)
response.raise_for_status()
data = response.json()
for model in data.get("data", []):
if model["id"] == model_id:
spec = model.get("model_spec", {})
constraints = spec.get("constraints", {})
return ModelCapabilities(
model_id=model["id"],
name=spec.get("name", model["id"]),
model_type=constraints.get("model_type", "unknown"),
durations=constraints.get("durations", []),
aspect_ratios=constraints.get("aspect_ratios", []),
resolutions=constraints.get("resolutions", []),
supports_audio=constraints.get("supported_audio", {}).get("configurable", False)
)
return None
def validate_quote_params(caps: ModelCapabilities, duration: str, aspect_ratio: str, resolution: str, audio: bool) -> list[str]:
"""Validate quote parameters against model capabilities."""
errors = []
if duration not in caps.durations:
errors.append(f"Invalid duration '{duration}'. Valid: {caps.durations}")
if aspect_ratio not in caps.aspect_ratios:
errors.append(f"Invalid aspect_ratio '{aspect_ratio}'. Valid: {caps.aspect_ratios}")
if resolution not in caps.resolutions:
errors.append(f"Invalid resolution '{resolution}'. Valid: {caps.resolutions}")
if audio and not caps.supports_audio:
errors.append(f"Model '{caps.model_id}' does not support audio")
return errors
def get_video_quote(
model: str,
duration: str,
aspect_ratio: str = "16:9",
resolution: str = "720p",
audio: bool = False,
validate: bool = True
) -> VideoQuoteResponse:
"""Get price quote for video generation with optional validation."""
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
# Validate against model capabilities
if validate:
caps = get_video_model_capabilities(model)
if caps is None:
raise ValueError(f"Model '{model}' not found. Use list_video_models() to see available models.")
errors = validate_quote_params(caps, duration, aspect_ratio, resolution, audio)
if errors:
error_msg = f"Invalid parameters for model '{model}': " + "; ".join(errors)
raise ValueError(error_msg)
request = VideoQuoteRequest(
model=model,
duration=duration,
aspect_ratio=aspect_ratio,
resolution=resolution,
audio=audio
)
response = requests.post(
VENICE_QUOTE_URL,
headers=headers,
json=request.model_dump(),
timeout=30
)
response.raise_for_status()
result = VideoQuoteResponse(**response.json())
result.model = model
result.config = request.model_dump()
return result
def show_model_options(model_id: str) -> None:
"""Print valid options for a video model."""
caps = get_video_model_capabilities(model_id)
if caps is None:
print(f"Model '{model_id}' not found")
return
print(f"Model: {caps.name} ({caps.model_id})")
print(f"Type: {caps.model_type}")
print(f"Durations: {', '.join(caps.durations)}")
print(f"Aspect Ratios: {', '.join(caps.aspect_ratios)}")
print(f"Resolutions: {', '.join(caps.resolutions)}")
print(f"Audio: {'Yes' if caps.supports_audio else 'No'}")
if __name__ == "__main__":
print("=" * 70)
print("Venice.ai Video Quote - With Model Validation")
print("=" * 70)
# Show options for models
print("\n>>> Model: wan-2.5-preview-text-to-video")
show_model_options("wan-2.5-preview-text-to-video")
print("\n>>> Model: ltx-2-fast-text-to-video")
show_model_options("ltx-2-fast-text-to-video")
# Valid quote
print("\n" + "-" * 70)
print("Testing VALID quote request:")
try:
quote = get_video_quote(
model="wan-2.5-preview-text-to-video",
duration="10s",
aspect_ratio="16:9",
resolution="720p",
audio=True
)
print(f" ✅ Quote: ${quote.quote:.2f}")
except ValueError as e:
print(f"{e}")
# Invalid quote (wrong resolution for LTX)
print("\nTesting INVALID quote request (720p on LTX model):")
try:
quote = get_video_quote(
model="ltx-2-fast-text-to-video",
duration="10s",
aspect_ratio="16:9",
resolution="720p",
audio=True
)
print(f" Quote: ${quote.quote:.2f}")
except ValueError as e:
print(f" ✅ Caught validation error: {e}")

View file

@ -0,0 +1,111 @@
# Venice Video Retrieve
Retrieve and download queued videos from [Venice.ai](https://venice.ai/). Polls automatically until generation is complete, then saves the video to disk.
> For a simpler all-in-one workflow, use [venice-video-generate](../venice-video-generate/) instead.
## Features
- **Automatic polling** until video generation completes
- Handles both JSON status responses and binary video data
- Supports video URL download, base64 data URIs, and raw binary responses
- Configurable poll interval and timeout
- JSON output mode for scripting
- Optional server-side deletion after download
## Prerequisites
```bash
pip install requests pydantic
export VENICE_API_KEY="your_venice_api_key"
```
## Usage
### Basic retrieval
```bash
python scripts/retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id
```
### Save to specific path
```bash
python scripts/retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id \
--output /path/to/my_video.mp4
```
### Custom polling settings
```bash
python scripts/retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id \
--interval 10 --max-wait 900
```
### Quiet mode with JSON
```bash
python scripts/retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id --quiet --json
# Output: {"status": "completed", "path": "/root/venice_videos/video_1234567890.mp4"}
```
## Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `model` | -- | *(required)* | Model used for generation |
| `queue_id` | -- | *(required)* | Queue ID from `venice-video-queue` |
| `--output` | `-o` | auto | Output file path |
| `--interval` | `-i` | `5` | Poll interval in seconds |
| `--max-wait` | `-w` | `600` | Maximum wait time in seconds |
| `--delete-after` | -- | off | Delete from Venice servers after download |
| `--quiet` | `-q` | off | Suppress progress output |
| `--json` | -- | off | JSON output |
## Exit Codes
| Code | Meaning |
|------|---------|
| `0` | Success |
| `1` | General error (API, network, etc.) |
| `2` | Timeout -- generation took too long |
## Python Import
```python
from retrieve_video import retrieve_and_save, poll_until_complete
# Full workflow: poll and save
path = retrieve_and_save(
model="wan-2.5-preview-text-to-video",
queue_id="abc123-def456",
output_path="/path/to/video.mp4",
poll_interval=5,
max_wait=600
)
print(f"Saved to: {path}")
# Just poll (without saving)
result = poll_until_complete(
model="wan-2.5-preview-text-to-video",
queue_id="abc123-def456"
)
# Access result.video_data or result.video_url
```
## Complete Two-Step Workflow
```bash
# Step 1: Queue
python ../venice-video-queue/scripts/queue_video.py "A cat playing piano" --json
# Output: {"model": "wan-2.5-preview-text-to-video", "queue_id": "abc123..."}
# Step 2: Retrieve
python scripts/retrieve_video.py wan-2.5-preview-text-to-video abc123...
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `VENICE_API_KEY` | Yes | Venice.ai API key |

View file

@ -0,0 +1,151 @@
---
name: "venice-video-retrieve"
description: "Retrieve a queued video from Venice.ai by queue_id. Polls until complete then downloads."
version: "1.0.0"
author: "Agent JAE"
tags: ["venice", "video", "ai", "generation", "download"]
trigger_patterns:
- "retrieve video"
- "download video"
- "get video"
- "venice video retrieve"
---
# Venice Video Retrieve
Retrieve and download queued videos from Venice.ai. Automatically polls until generation is complete.
## Requirements
- `VENICE_API_KEY` environment variable
- A `queue_id` from `venice-video-queue`
## CLI Usage
```bash
python ~/.jae/agent/skills/venice-video-retrieve/scripts/retrieve_video.py MODEL QUEUE_ID [options]
```
### Arguments
| Argument | Required | Description |
|----------|----------|-------------|
| `MODEL` | Yes | Model that was used for generation |
| `QUEUE_ID` | Yes | Queue ID returned from queue_video |
### Options
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `--output` | `-o` | Auto-generated | Output path for the video file |
| `--interval` | `-i` | `5` | Polling interval in seconds |
| `--max-wait` | `-w` | `600` | Maximum wait time in seconds |
| `--delete-after` | | False | Delete from Venice servers after download |
| `--quiet` | `-q` | False | Suppress progress output |
| `--json` | | False | Output result as JSON |
## Examples
### Basic Retrieval
```bash
python ~/.jae/agent/skills/venice-video-retrieve/scripts/retrieve_video.py \
wan-2.5-preview-text-to-video \
abc123-def456-queue-id
```
### Save to Specific Path
```bash
python ~/.jae/agent/skills/venice-video-retrieve/scripts/retrieve_video.py \
wan-2.5-preview-text-to-video \
abc123-def456-queue-id \
--output /path/to/my_video.mp4
```
### Custom Polling Settings
```bash
python ~/.jae/agent/skills/venice-video-retrieve/scripts/retrieve_video.py \
wan-2.5-preview-text-to-video \
abc123-def456-queue-id \
--interval 10 \
--max-wait 900
```
### Quiet Mode with JSON Output
```bash
python ~/.jae/agent/skills/venice-video-retrieve/scripts/retrieve_video.py \
wan-2.5-preview-text-to-video \
abc123-def456-queue-id \
--quiet --json
# Output: {"status": "completed", "path": "/root/venice_videos/video_1234567890.mp4"}
```
## Output
Progress output during polling:
```
🎬 Retrieving video...
Model: wan-2.5-preview-text-to-video
Queue ID: abc123-def456-...
[5s] Status: pending | Progress: 10% ETA 45s
[10s] Status: pending | Progress: 25% ETA 35s
[15s] Status: pending | Progress: 50% ETA 20s
[20s] Status: pending | Progress: 75% ETA 10s
[25s] Status: completed | Progress: 100%
✅ Video ready!
💾 Saved to: /root/venice_videos/video_1234567890.mp4
✅ Video saved to: /root/venice_videos/video_1234567890.mp4
```
## Default Output Location
If no `--output` is specified, videos are saved to:
```
/root/venice_videos/video_<timestamp>.mp4
```
## Error Handling
| Exit Code | Meaning |
|-----------|----------|
| `0` | Success |
| `1` | General error (API, network, etc.) |
| `2` | Timeout - generation took too long |
## Programmatic Usage
```python
from retrieve_video import retrieve_and_save, poll_until_complete
# Full workflow: poll and save
path = retrieve_and_save(
model="wan-2.5-preview-text-to-video",
queue_id="abc123-def456",
output_path="/path/to/video.mp4",
poll_interval=5,
max_wait=600
)
print(f"Saved to: {path}")
# Just poll (without saving)
result = poll_until_complete(
model="wan-2.5-preview-text-to-video",
queue_id="abc123-def456"
)
print(f"Status: {result.status}")
# Access result.video_data or result.video_url
```
## Complete Workflow Example
```bash
# Step 1: Queue a video
python ~/.jae/agent/skills/venice-video-queue/scripts/queue_video.py "A cat playing piano" --json
# Output: {"model": "wan-2.5-preview-text-to-video", "queue_id": "abc123..."}
# Step 2: Retrieve the video
python ~/.jae/agent/skills/venice-video-retrieve/scripts/retrieve_video.py \
wan-2.5-preview-text-to-video \
abc123... \
--output /root/cat_piano.mp4
```

View file

@ -0,0 +1,295 @@
"""# Venice.ai Video Retrieve Skill
Retrieve a queued video by polling until complete.
Usage:
python retrieve_video.py MODEL QUEUE_ID [options]
Examples:
# Basic retrieval
python retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id
# Save to specific path
python retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id --output /path/to/video.mp4
"""
import os
import sys
import time
import json
import base64
import argparse
import requests
from pathlib import Path
from typing import Optional, Union
from pydantic import BaseModel
VENICE_RETRIEVE_URL = "https://api.venice.ai/api/v1/video/retrieve"
VENICE_API_KEY = os.getenv("VENICE_API_KEY")
DEFAULT_OUTPUT_DIR = "/root/venice_videos"
class VideoRetrieveResponse(BaseModel):
status: str = "unknown"
video_url: Optional[str] = None
video_data: Optional[bytes] = None
error: Optional[str] = None
progress: Optional[float] = None
eta: Optional[int] = None
def retrieve_video(
model: str,
queue_id: str,
delete_media_on_completion: bool = False
) -> VideoRetrieveResponse:
"""Single retrieve request - handles JSON or binary video response."""
headers = {
"Authorization": f"Bearer {VENICE_API_KEY}",
"Content-Type": "application/json"
}
request_data = {
"model": model,
"queue_id": queue_id,
"delete_media_on_completion": delete_media_on_completion
}
response = requests.post(
VENICE_RETRIEVE_URL,
headers=headers,
json=request_data,
timeout=120
)
if not response.ok:
return VideoRetrieveResponse(
status="error",
error=f"HTTP {response.status_code}: {response.text[:200]}"
)
content_type = response.headers.get("Content-Type", "")
# If response is video data (MP4), return as completed
if "video" in content_type or b'ftyp' in response.content[:20]:
return VideoRetrieveResponse(
status="completed",
video_data=response.content
)
# Try to parse as JSON
if not response.text or response.text.strip() == "":
return VideoRetrieveResponse(
status="pending",
error="Empty response"
)
try:
data = response.json()
return VideoRetrieveResponse(**data)
except Exception as e:
# Might be binary video without proper content-type
if len(response.content) > 1000: # Probably video data
return VideoRetrieveResponse(
status="completed",
video_data=response.content
)
return VideoRetrieveResponse(
status="error",
error=f"Parse error: {e}"
)
def poll_until_complete(
model: str,
queue_id: str,
poll_interval: int = 5,
max_wait: int = 600,
delete_media_on_completion: bool = False,
verbose: bool = True
) -> VideoRetrieveResponse:
"""Poll until video is complete or failed."""
start_time = time.time()
error_count = 0
while True:
elapsed = time.time() - start_time
if elapsed > max_wait:
raise TimeoutError(f"Timed out after {max_wait}s")
result = retrieve_video(model, queue_id, delete_media_on_completion)
status = result.status.lower() if result.status else "unknown"
if verbose:
progress_str = f"{result.progress:.0f}%" if result.progress else "?"
eta_str = f"ETA {result.eta}s" if result.eta else ""
print(f" [{elapsed:.0f}s] Status: {status} | Progress: {progress_str} {eta_str}")
if status in ["completed", "complete"]:
if verbose:
print(f" ✅ Video ready!")
return result
if status == "failed":
raise RuntimeError(f"Generation failed: {result.error}")
if status == "error":
error_count += 1
if error_count > 10:
raise RuntimeError(f"Too many errors: {result.error}")
if verbose:
print(f" ⚠️ Retrying...")
else:
error_count = 0
time.sleep(poll_interval)
def save_video(
video_url: Optional[str] = None,
video_data: Optional[bytes] = None,
output_path: Optional[str] = None,
filename: Optional[str] = None
) -> str:
"""Save video from URL, base64, or raw bytes to disk."""
if output_path:
path = Path(output_path)
elif filename:
path = Path(DEFAULT_OUTPUT_DIR) / filename
else:
path = Path(DEFAULT_OUTPUT_DIR) / f"video_{int(time.time())}.mp4"
path.parent.mkdir(parents=True, exist_ok=True)
if video_data:
# Direct binary data
with open(path, "wb") as f:
f.write(video_data)
elif video_url:
if video_url.startswith("data:"):
# Base64 data URI
header, encoded = video_url.split(",", 1)
data = base64.b64decode(encoded)
with open(path, "wb") as f:
f.write(data)
else:
# URL download
response = requests.get(video_url, timeout=120)
response.raise_for_status()
with open(path, "wb") as f:
f.write(response.content)
else:
raise ValueError("No video_url or video_data provided")
return str(path)
def retrieve_and_save(
model: str,
queue_id: str,
output_path: Optional[str] = None,
filename: Optional[str] = None,
poll_interval: int = 5,
max_wait: int = 600,
verbose: bool = True
) -> str:
"""Poll until complete, then save to disk."""
result = poll_until_complete(
model=model,
queue_id=queue_id,
poll_interval=poll_interval,
max_wait=max_wait,
verbose=verbose
)
saved_path = save_video(
video_url=result.video_url,
video_data=result.video_data,
output_path=output_path,
filename=filename
)
if verbose:
print(f" 💾 Saved to: {saved_path}")
return saved_path
def main():
"""CLI entry point."""
parser = argparse.ArgumentParser(
description="Retrieve a queued video from Venice.ai",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""Examples:
# Basic retrieval (saves to default location)
python retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id
# Save to specific path
python retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id -o /path/to/video.mp4
# Custom polling settings
python retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id --interval 10 --max-wait 900
# Quiet mode (no progress output)
python retrieve_video.py wan-2.5-preview-text-to-video abc123-queue-id --quiet
"""
)
parser.add_argument("model", help="Model that was used for generation")
parser.add_argument("queue_id", help="Queue ID returned from queue_video")
parser.add_argument("--output", "-o", default=None,
help="Output path for the video file (default: /root/venice_videos/video_<timestamp>.mp4)")
parser.add_argument("--interval", "-i", type=int, default=5,
help="Polling interval in seconds (default: 5)")
parser.add_argument("--max-wait", "-w", type=int, default=600,
help="Maximum wait time in seconds (default: 600)")
parser.add_argument("--delete-after", action="store_true",
help="Delete video from Venice servers after download")
parser.add_argument("--quiet", "-q", action="store_true",
help="Suppress progress output")
parser.add_argument("--json", action="store_true",
help="Output result as JSON")
args = parser.parse_args()
# Check API key
if not VENICE_API_KEY:
print("Error: VENICE_API_KEY environment variable not set", file=sys.stderr)
sys.exit(1)
verbose = not args.quiet
try:
if verbose:
print(f"🎬 Retrieving video...")
print(f" Model: {args.model}")
print(f" Queue ID: {args.queue_id}")
print(f"")
saved_path = retrieve_and_save(
model=args.model,
queue_id=args.queue_id,
output_path=args.output,
poll_interval=args.interval,
max_wait=args.max_wait,
verbose=verbose
)
if args.json:
print(json.dumps({"status": "completed", "path": saved_path}))
elif verbose:
print(f"")
print(f"✅ Video saved to: {saved_path}")
except TimeoutError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(2)
except RuntimeError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()