auto-sync: 2026-04-18 20:40:01
This commit is contained in:
@@ -102,14 +102,6 @@
|
||||
- Скилл открывается ДО начала действий, а не после того как застряла
|
||||
- Исключение: диагностика когда непонятно куда смотреть — но потом сверяться со скиллом
|
||||
- Факт нарушения: 12.04.2026 — HA audit делала вручную, скилл не открыла → потеряла время на костыли
|
||||
- **Удалён** из HA (компонент + строка в configuration.yaml)
|
||||
- Был создан Dev-агентом без явной хотелки Славы — оказался не нужен
|
||||
- Запрос на удаление: Слава, 12.04.2026
|
||||
|
||||
## Учёт токенов Dev-агента
|
||||
- Реальные данные — из OpenRouter dashboard
|
||||
- Формула: `(input×$3 + output×$15) / 1M` (Sonnet 4.6)
|
||||
- Лог: `tasks/token-log.md`, формат: `DEV-XXX | XK in / XK out → $X.XX`
|
||||
|
||||
## Проекты
|
||||
|
||||
|
||||
@@ -227,6 +227,17 @@ python3 scripts/ontology.py list --type Person
|
||||
- `references/schema.md` — Full type definitions and constraint patterns
|
||||
- `references/queries.md` — Query language and traversal examples
|
||||
|
||||
## Current OpenClaw Convention
|
||||
|
||||
Use ontology as the source of truth for project/work tracking:
|
||||
|
||||
- **Task without Project does not exist**
|
||||
- `Project` should carry readable `folder` and `doc_path`
|
||||
- `Task` should carry `project`, `folder`, and `doc_path`
|
||||
- Human-facing names stay in `name` / `title`; internal ids stay internal
|
||||
- `tasks/<project-slug>/` is the project root; task folders live under `tasks/<project-slug>/TASKS/<status>/<task-slug>/`
|
||||
- If a work item does not fit an existing project, create or clarify the project first, then the task
|
||||
|
||||
## Instruction Scope
|
||||
|
||||
Runtime instructions operate on local files (`memory/ontology/graph.jsonl` and `memory/ontology/schema.yaml`) and provide CLI usage for create/query/relate/validate; this is within scope. The skill reads/writes workspace files and will create the `memory/ontology` directory when used. Validation includes property/enum/forbidden checks, relation type/cardinality validation, acyclicity for relations marked `acyclic: true`, and Event `end >= start` checks; other higher-level constraints may still be documentation-only unless implemented in code.
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
# DEV-014: Аудит и настройка моделей в OpenClaw
|
||||
|
||||
**Дата:** 27 марта 2026
|
||||
**Статус:** ✅ Завершено
|
||||
|
||||
---
|
||||
|
||||
## 1. Что нашёл в документации
|
||||
|
||||
### `/app/docs/providers/google.md`
|
||||
- Провайдер: `google`
|
||||
- Аутентификация: `GEMINI_API_KEY` или `GOOGLE_API_KEY` (из `~/.openclaw/.env`)
|
||||
- Синтаксис модели в конфиге: `google/gemini-3.1-pro-preview` (prefix `google/` + ID модели)
|
||||
- Если Gateway запущен как демон — `GEMINI_API_KEY` должен быть в `~/.openclaw/.env` ✅ (уже так)
|
||||
- Альтернативный провайдер: `google-gemini-cli` (OAuth, unofficial)
|
||||
|
||||
### `/app/docs/providers/models.md`
|
||||
- Формат задания модели: `provider/model` (например `google/gemini-2.5-pro`)
|
||||
- Для OpenRouter: `openrouter/<provider>/<model>`
|
||||
|
||||
### `/app/docs/providers/openrouter.md`
|
||||
- Модели OpenRouter: `openrouter/<provider>/<model>` (например `openrouter/anthropic/claude-sonnet-4.6`)
|
||||
- Принципиальное отличие от Google: OpenRouter имеет промежуточный слой (один ключ → много провайдеров)
|
||||
|
||||
---
|
||||
|
||||
## 2. Что было неправильно в конфиге
|
||||
|
||||
### Проблема: Устаревшие/несуществующие ID моделей Google
|
||||
|
||||
Были добавлены следующие модели, которые **не существуют в Google API**:
|
||||
|
||||
| Неправильный ID | HTTP ответ | Причина ошибки |
|
||||
|-----------------|------------|----------------|
|
||||
| `gemini-2.5-pro-preview-03-25` | **404 NOT FOUND** | Устаревший preview ID (был заменён стабильным) |
|
||||
| `gemini-2.5-flash-preview-04-17` | **404 NOT FOUND** | Устаревший preview ID (был заменён стабильным) |
|
||||
|
||||
Проверка через `GET /v1beta/models?key=...` показала, что этих моделей больше нет в API.
|
||||
|
||||
---
|
||||
|
||||
## 3. Что исправил
|
||||
|
||||
### Удалены невалидные модели
|
||||
```
|
||||
- google/gemini-2.5-pro-preview-03-25
|
||||
- gemini-2.5-pro-preview-03-25
|
||||
- google/gemini-2.5-flash-preview-04-17
|
||||
- gemini-2.5-flash-preview-04-17
|
||||
```
|
||||
|
||||
### Добавлены актуальные модели (проверено через Google ListModels API)
|
||||
```json
|
||||
"google/gemini-2.5-pro": {},
|
||||
"gemini-2.5-pro": {},
|
||||
"google/gemini-2.5-flash": {},
|
||||
"gemini-2.5-flash": {},
|
||||
"google/gemini-2.5-flash-lite": {},
|
||||
"gemini-2.5-flash-lite": {},
|
||||
"google/gemini-2.0-flash": {},
|
||||
"gemini-2.0-flash": {},
|
||||
"google/gemini-3.1-pro-preview": {},
|
||||
"gemini-3.1-pro-preview": {},
|
||||
"google/gemini-3.1-flash-lite-preview": {},
|
||||
"gemini-3.1-flash-lite-preview": {}
|
||||
```
|
||||
|
||||
Все модели добавлены **двумя записями** — с prefix `google/` и без (согласно правилу из MEMORY.md).
|
||||
|
||||
### Почему профиль `google:default` корректен
|
||||
```json
|
||||
"google:default": {
|
||||
"provider": "google",
|
||||
"mode": "api_key"
|
||||
}
|
||||
```
|
||||
Профиль настроен правильно. Ключ `GEMINI_API_KEY` читается из `~/.openclaw/.env` ✅.
|
||||
|
||||
---
|
||||
|
||||
## 4. Результаты тестирования
|
||||
|
||||
### Полный список актуальных моделей Google (через ListModels API)
|
||||
Из API получен список всех моделей, поддерживающих `generateContent`:
|
||||
|
||||
| ID модели | Название |
|
||||
|-----------|---------|
|
||||
| `gemini-2.5-flash` | Gemini 2.5 Flash |
|
||||
| `gemini-2.5-pro` | Gemini 2.5 Pro |
|
||||
| `gemini-2.0-flash` | Gemini 2.0 Flash |
|
||||
| `gemini-2.0-flash-001` | Gemini 2.0 Flash 001 |
|
||||
| `gemini-2.0-flash-lite` | Gemini 2.0 Flash-Lite |
|
||||
| `gemini-2.5-flash-lite` | Gemini 2.5 Flash-Lite |
|
||||
| `gemini-3.1-pro-preview` | Gemini 3.1 Pro Preview |
|
||||
| `gemini-3.1-flash-lite-preview` | Gemini 3.1 Flash Lite Preview |
|
||||
| `gemini-3-pro-preview` | Gemini 3 Pro Preview |
|
||||
| `gemini-3-flash-preview` | Gemini 3 Flash Preview |
|
||||
|
||||
### Тестовые вызовы
|
||||
| Модель | Статус | Результат |
|
||||
|--------|--------|-----------|
|
||||
| `gemini-2.5-flash` | ✅ 200 OK | `Hi` (корректный ответ) |
|
||||
| `gemini-2.5-pro` | ⚠️ 429 | Rate limit (Free Tier) — модель существует, лимит квоты |
|
||||
| `gemini-3.1-pro-preview` | ⚠️ 429 | Rate limit (Free Tier) — модель существует, лимит квоты |
|
||||
| `gemini-2.5-pro-preview-03-25` | ❌ 404 | Модель не существует |
|
||||
| `gemini-2.5-flash-preview-04-17` | ❌ 404 | Модель не существует |
|
||||
|
||||
**Вывод:** API работает. Free Tier имеет строгие rate limits, но модели валидны.
|
||||
|
||||
---
|
||||
|
||||
## 5. Нужна ли перезагрузка гейтвея?
|
||||
|
||||
**Да, нужна** — изменения в `openclaw.json` (список `agents.defaults.models`) вступают в силу только после перезапуска Gateway.
|
||||
|
||||
```bash
|
||||
# Перезапуск Gateway
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
Или вручную:
|
||||
```bash
|
||||
kill -9 $(pgrep -f "openclaw gateway") && openclaw gateway &
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Итог
|
||||
|
||||
- ✅ Документация изучена — Google provider работает через `google/` prefix + `GEMINI_API_KEY` в `.env`
|
||||
- ✅ Обнаружены 2 невалидных ID моделей (устаревшие preview-версии с датой)
|
||||
- ✅ Конфиг исправлен: добавлены актуальные стабильные модели Google (2.5-pro, 2.5-flash, 2.0-flash, 2.5-flash-lite, 3.1-pro-preview, 3.1-flash-lite-preview)
|
||||
- ✅ Все модели добавлены двумя записями (с prefix `google/` и без)
|
||||
- ✅ API протестирован — `gemini-2.5-flash` отвечает корректно (HTTP 200)
|
||||
- ⚠️ Free Tier активен — есть rate limits, но это ограничение плана, не конфигурации
|
||||
- 🔄 Требуется перезапуск Gateway для применения изменений
|
||||
@@ -1,116 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
import glob
|
||||
|
||||
def parse_iso(timestamp):
|
||||
# Parse ISO timestamp with timezone
|
||||
try:
|
||||
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
|
||||
return dt
|
||||
except Exception as e:
|
||||
print(f'Error parsing {timestamp}: {e}')
|
||||
return None
|
||||
|
||||
def process_session_file(path):
|
||||
today = datetime(2026, 3, 22, tzinfo=timezone.utc)
|
||||
total_input = 0
|
||||
total_output = 0
|
||||
total_cost = 0.0
|
||||
model_counts = {}
|
||||
|
||||
with open(path, 'r') as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
data = json.loads(line)
|
||||
except json.JSONDecodeError as e:
|
||||
continue
|
||||
|
||||
# Check timestamp for today
|
||||
timestamp = data.get('timestamp')
|
||||
if not timestamp:
|
||||
continue
|
||||
dt = parse_iso(timestamp)
|
||||
if not dt:
|
||||
continue
|
||||
if dt.date() != today.date():
|
||||
continue
|
||||
|
||||
# We need messages with usage (assistant responses)
|
||||
if data.get('type') == 'message':
|
||||
msg = data.get('message', {})
|
||||
if msg.get('role') == 'assistant' and 'usage' in msg:
|
||||
usage = msg['usage']
|
||||
input_tokens = usage.get('input', 0)
|
||||
output_tokens = usage.get('output', 0)
|
||||
cache_read = usage.get('cacheRead', 0)
|
||||
cache_write = usage.get('cacheWrite', 0)
|
||||
# cost may be inside usage['cost']
|
||||
cost = usage.get('cost', {})
|
||||
cost_total = cost.get('total', 0.0)
|
||||
model = msg.get('model', 'unknown')
|
||||
|
||||
total_input += input_tokens
|
||||
total_output += output_tokens
|
||||
total_cost += cost_total
|
||||
|
||||
# Track per model
|
||||
if model not in model_counts:
|
||||
model_counts[model] = {'input': 0, 'output': 0, 'cost': 0.0}
|
||||
model_counts[model]['input'] += input_tokens
|
||||
model_counts[model]['output'] += output_tokens
|
||||
model_counts[model]['cost'] += cost_total
|
||||
|
||||
return total_input, total_output, total_cost, model_counts
|
||||
|
||||
sessions_dir = '/home/node/.openclaw/agents/main/sessions'
|
||||
jsonl_files = glob.glob(os.path.join(sessions_dir, '*.jsonl'))
|
||||
|
||||
print('📊 Сводка использования токенов за сегодня (2026-03-22)')
|
||||
print('=' * 60)
|
||||
|
||||
overall_input = 0
|
||||
overall_output = 0
|
||||
overall_cost = 0.0
|
||||
all_model_counts = {}
|
||||
|
||||
for file_path in jsonl_files:
|
||||
file_name = os.path.basename(file_path)
|
||||
print(f'\n📁 Файл сессии: {file_name}')
|
||||
inp, out, cost, models = process_session_file(file_path)
|
||||
print(f' Входные токены: {inp:,}')
|
||||
print(f' Выходные токены: {out:,}')
|
||||
print(f' Примерная стоимость: ${cost:.6f}')
|
||||
if models:
|
||||
for model, counts in models.items():
|
||||
print(f' Модель: {model}')
|
||||
print(f' вход: {counts[\"input\"]:,}, выход: {counts[\"output\"]:,}, стоимость: ${counts[\"cost\"]:.6f}')
|
||||
# Aggregate across files
|
||||
if model not in all_model_counts:
|
||||
all_model_counts[model] = counts.copy()
|
||||
else:
|
||||
all_model_counts[model]['input'] += counts['input']
|
||||
all_model_counts[model]['output'] += counts['output']
|
||||
all_model_counts[model]['cost'] += counts['cost']
|
||||
|
||||
overall_input += inp
|
||||
overall_output += out
|
||||
overall_cost += cost
|
||||
|
||||
print('\n' + '=' * 60)
|
||||
print('📈 ОБЩИЙ ИТОГ за сегодня:')
|
||||
print(f' Всего входных токенов: {overall_input:,}')
|
||||
print(f' Всего выходных токенов: {overall_output:,}')
|
||||
print(f' Общая стоимость: ${overall_cost:.6f}')
|
||||
print()
|
||||
print('📋 По моделям:')
|
||||
for model, counts in all_model_counts.items():
|
||||
print(f' • {model}')
|
||||
print(f' вход: {counts[\"input\"]:,} токенов, выход: {counts[\"output\"]:,} токенов')
|
||||
print(f' стоимость: ${counts[\"cost\"]:.6f}')
|
||||
print()
|
||||
print('💡 Примечание: стоимость может не включать кэшированные токены.')
|
||||
print(' Данные основаны на записях сессий.')
|
||||
@@ -1,91 +0,0 @@
|
||||
import json
|
||||
import glob
|
||||
from datetime import datetime
|
||||
|
||||
def parse_iso(timestamp):
|
||||
try:
|
||||
return datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
|
||||
except:
|
||||
return None
|
||||
|
||||
today = datetime(2026, 3, 22).date()
|
||||
sessions_dir = '/home/node/.openclaw/agents/main/sessions'
|
||||
jsonl_files = glob.glob(sessions_dir + '/*.jsonl')
|
||||
|
||||
claude_input = 0
|
||||
claude_output = 0
|
||||
claude_cost = 0.0
|
||||
deepseek_input = 0
|
||||
deepseek_output = 0
|
||||
deepseek_cost = 0.0
|
||||
|
||||
for file_path in jsonl_files:
|
||||
with open(file_path, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
data = json.loads(line)
|
||||
except:
|
||||
continue
|
||||
if data.get('type') != 'message':
|
||||
continue
|
||||
msg = data.get('message', {})
|
||||
if msg.get('role') != 'assistant' or 'usage' not in msg:
|
||||
continue
|
||||
timestamp = data.get('timestamp')
|
||||
if not timestamp:
|
||||
continue
|
||||
dt = parse_iso(timestamp)
|
||||
if not dt or dt.date() != today:
|
||||
continue
|
||||
usage = msg['usage']
|
||||
inp = usage.get('input', 0)
|
||||
out = usage.get('output', 0)
|
||||
cost = usage.get('cost', {}).get('total', 0.0)
|
||||
model = msg.get('model', '')
|
||||
if 'claude' in model.lower():
|
||||
claude_input += inp
|
||||
claude_output += out
|
||||
claude_cost += cost
|
||||
elif 'deepseek' in model.lower():
|
||||
deepseek_input += inp
|
||||
deepseek_output += out
|
||||
deepseek_cost += cost
|
||||
|
||||
total_input = claude_input + deepseek_input
|
||||
total_output = claude_output + deepseek_output
|
||||
total_cost = claude_cost + deepseek_cost
|
||||
|
||||
print('=== Сводка использования OpenRouter за 22 марта 2026 ===')
|
||||
print()
|
||||
print('📊 По моделям:')
|
||||
print('1. Claude Sonnet 4.6')
|
||||
print(' • Входные токены:', f'{claude_input:,}')
|
||||
print(' • Выходные токены:', f'{claude_output:,}')
|
||||
print(' • Стоимость: $' + f'{claude_cost:.6f}')
|
||||
print()
|
||||
print('2. DeepSeek V3.2')
|
||||
print(' • Входные токены:', f'{deepseek_input:,}')
|
||||
print(' • Выходные токены:', f'{deepseek_output:,}')
|
||||
print(' • Стоимость: $' + f'{deepseek_cost:.6f}')
|
||||
print()
|
||||
print('📈 ИТОГО:')
|
||||
print(' Всего входных токенов:', f'{total_input:,}')
|
||||
print(' Всего выходных токенов:', f'{total_output:,}')
|
||||
print(' Общая стоимость: $' + f'{total_cost:.6f}')
|
||||
print()
|
||||
print('💎 Средняя стоимость за 1 тыс. входных токенов:')
|
||||
if total_input > 0:
|
||||
avg = total_cost / total_input * 1000
|
||||
print(' $' + f'{avg:.6f}')
|
||||
else:
|
||||
print(' N/A')
|
||||
print()
|
||||
print('🔄 Переход на DeepSeek:')
|
||||
print(' • Claude использовался до ~06:30 UTC')
|
||||
print(' • DeepSeek используется с ~06:30 UTC')
|
||||
print()
|
||||
print('💡 Примечание: входные токены включают контекст всей сессии,')
|
||||
print(' который пересылается при каждом запросе. Выходные токены — ответы модели.')
|
||||
Reference in New Issue
Block a user