feat: add 11 Venice AI skills as bundled defaults
Some checks are pending
CI / build-check-test (push) Waiting to run
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:
parent
f2c4bdbb27
commit
19b25341bd
34 changed files with 5089 additions and 0 deletions
86
default-skills/README.md
Normal file
86
default-skills/README.md
Normal 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 |
|
||||||
124
default-skills/venice-chat-benchmark/README.md
Normal file
124
default-skills/venice-chat-benchmark/README.md
Normal 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` | -- | `/a0/usr/workdir/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 |
|
||||||
83
default-skills/venice-chat-benchmark/SKILL.md
Normal file
83
default-skills/venice-chat-benchmark/SKILL.md
Normal 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)
|
||||||
618
default-skills/venice-chat-benchmark/scripts/benchmark.py
Normal file
618
default-skills/venice-chat-benchmark/scripts/benchmark.py
Normal 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()
|
||||||
105
default-skills/venice-chat/README.md
Normal file
105
default-skills/venice-chat/README.md
Normal 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 |
|
||||||
76
default-skills/venice-chat/SKILL.md
Normal file
76
default-skills/venice-chat/SKILL.md
Normal 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
|
||||||
190
default-skills/venice-chat/scripts/chat.py
Normal file
190
default-skills/venice-chat/scripts/chat.py
Normal 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()
|
||||||
89
default-skills/venice-image-gen/README.md
Normal file
89
default-skills/venice-image-gen/README.md
Normal 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 |
|
||||||
63
default-skills/venice-image-gen/SKILL.md
Normal file
63
default-skills/venice-image-gen/SKILL.md
Normal 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
|
||||||
165
default-skills/venice-image-gen/scripts/generate_image.py
Normal file
165
default-skills/venice-image-gen/scripts/generate_image.py
Normal 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()
|
||||||
69
default-skills/venice-list-image-models/README.md
Normal file
69
default-skills/venice-list-image-models/README.md
Normal 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 |
|
||||||
47
default-skills/venice-list-image-models/SKILL.md
Normal file
47
default-skills/venice-list-image-models/SKILL.md
Normal 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)
|
||||||
|
|
@ -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}")
|
||||||
84
default-skills/venice-list-text-models/README.md
Normal file
84
default-skills/venice-list-text-models/README.md
Normal 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 |
|
||||||
67
default-skills/venice-list-text-models/SKILL.md
Normal file
67
default-skills/venice-list-text-models/SKILL.md
Normal 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
|
||||||
|
```
|
||||||
|
|
@ -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}")
|
||||||
80
default-skills/venice-list-video-models/README.md
Normal file
80
default-skills/venice-list-video-models/README.md
Normal 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 |
|
||||||
65
default-skills/venice-list-video-models/SKILL.md
Normal file
65
default-skills/venice-list-video-models/SKILL.md
Normal 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
|
||||||
|
|
@ -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()
|
||||||
95
default-skills/venice-tts/README.md
Normal file
95
default-skills/venice-tts/README.md
Normal 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 |
|
||||||
73
default-skills/venice-tts/SKILL.md
Normal file
73
default-skills/venice-tts/SKILL.md
Normal 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
|
||||||
176
default-skills/venice-tts/scripts/text_to_speech.py
Normal file
176
default-skills/venice-tts/scripts/text_to_speech.py
Normal 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()
|
||||||
111
default-skills/venice-video-generate/README.md
Normal file
111
default-skills/venice-video-generate/README.md
Normal 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 |
|
||||||
140
default-skills/venice-video-generate/SKILL.md
Normal file
140
default-skills/venice-video-generate/SKILL.md
Normal 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
|
||||||
550
default-skills/venice-video-generate/scripts/generate_video.py
Normal file
550
default-skills/venice-video-generate/scripts/generate_video.py
Normal 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())
|
||||||
88
default-skills/venice-video-queue/README.md
Normal file
88
default-skills/venice-video-queue/README.md
Normal 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 |
|
||||||
116
default-skills/venice-video-queue/SKILL.md
Normal file
116
default-skills/venice-video-queue/SKILL.md
Normal 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"
|
||||||
|
)
|
||||||
|
```
|
||||||
249
default-skills/venice-video-queue/scripts/queue_video.py
Normal file
249
default-skills/venice-video-queue/scripts/queue_video.py
Normal 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()
|
||||||
79
default-skills/venice-video-quote/README.md
Normal file
79
default-skills/venice-video-quote/README.md
Normal 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 |
|
||||||
56
default-skills/venice-video-quote/SKILL.md
Normal file
56
default-skills/venice-video-quote/SKILL.md
Normal 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
|
||||||
193
default-skills/venice-video-quote/scripts/get_video_quote.py
Normal file
193
default-skills/venice-video-quote/scripts/get_video_quote.py
Normal 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}")
|
||||||
111
default-skills/venice-video-retrieve/README.md
Normal file
111
default-skills/venice-video-retrieve/README.md
Normal 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 |
|
||||||
151
default-skills/venice-video-retrieve/SKILL.md
Normal file
151
default-skills/venice-video-retrieve/SKILL.md
Normal 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
|
||||||
|
```
|
||||||
295
default-skills/venice-video-retrieve/scripts/retrieve_video.py
Normal file
295
default-skills/venice-video-retrieve/scripts/retrieve_video.py
Normal 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()
|
||||||
Loading…
Add table
Reference in a new issue