diff --git a/MEMORY.md b/MEMORY.md index 5fa53bb..c54d2fb 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -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` ## Проекты diff --git a/skills/ontology/SKILL.md b/skills/ontology/SKILL.md index d136c31..2fd3188 100644 --- a/skills/ontology/SKILL.md +++ b/skills/ontology/SKILL.md @@ -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//` is the project root; task folders live under `tasks//TASKS///` +- 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. diff --git a/tasks/reports/DEV-014-models-audit.md b/tasks/reports/DEV-014-models-audit.md deleted file mode 100644 index 7f92ba5..0000000 --- a/tasks/reports/DEV-014-models-audit.md +++ /dev/null @@ -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//` - -### `/app/docs/providers/openrouter.md` -- Модели OpenRouter: `openrouter//` (например `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 для применения изменений diff --git a/tasks/scripts/token_summary.py b/tasks/scripts/token_summary.py deleted file mode 100644 index 06905c3..0000000 --- a/tasks/scripts/token_summary.py +++ /dev/null @@ -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(' Данные основаны на записях сессий.') \ No newline at end of file diff --git a/tasks/scripts/usage_summary.py b/tasks/scripts/usage_summary.py deleted file mode 100644 index ff22b60..0000000 --- a/tasks/scripts/usage_summary.py +++ /dev/null @@ -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(' который пересылается при каждом запросе. Выходные токены — ответы модели.') \ No newline at end of file