diff --git a/history/llm-http2-answer.md b/history/llm-http2-answer.md new file mode 100644 index 0000000..2de50d0 --- /dev/null +++ b/history/llm-http2-answer.md @@ -0,0 +1,60 @@ +# Ответ: HTTP/2 из Python к aillm.ru + +_Дата: 2026-06-14_ + +--- + +## Проблема +`api.aillm.ru` защищен ddos-guard, который требует наличия HTTP/2 в TLS-хендшейке от облачных IP. Библиотека `requests` работает только по HTTP/1.1, что приводит к ложным ошибкам 401/403. + +## Решение +Использование библиотеки **`httpx`** с установленной поддержкой HTTP/2 через чистый Python-пакет **`h2`**. + +### 1. Подготовка зависимостей (requirements.txt) +Так как облачная платформа не всегда корректно обрабатывает синтаксис `httpx[http2]`, необходимо прописать зависимости **явно отдельными строками**. Это гарантирует установку `h2` — pure Python реализации протокола, которая не требует системных библиотек или `apt`. + +```text +# requirements.txt +httpx +h2 +``` + +### 2. Код клиента (llm_client.py) +Для работы по HTTP/2 в `httpx` нужно явно передать параметр `http2=True` при создании клиента. + +```python +import os +import httpx + +def ask_llm(prompt: str): + api_url = "https://api.aillm.ru/v1/chat/completions" + api_key = os.getenv("LLM_API_KEY", "sk-ucI5YvOticoOQ9Kuj5K9mQ") + + payload = { + "model": "gpt-oss-120b", + "messages": [{"role": "user", "content": prompt}], + "max_tokens": 8000, + "temperature": 0.1 + } + + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + + # ВАЖНО: http2=True включает поддержку нужного протокола + with httpx.Client(http2=True, timeout=120) as client: + resp = client.post(api_url, json=payload, headers=headers) + resp.raise_for_status() + return resp.json() +``` + +## Почему это работает в slim-контейнере +1. **`httpx`** — современный клиент, поддерживающий HTTP/2. +2. **`h2`** — это замена системным библиотекам `nghttp2`. Она написана на чистом Python, поэтому корректно устанавливается через `pip` даже в максимально «обрезанных» образах (\*-slim, alpine) без компиляторов и `apt`. +3. **HTTP/2 Fingerprint** — ddos-guard видит поддержку HTTP/2 в TLS Client Hello, что является для него признаком «легального» клиента (браузера или современного SDK), а не простого скрипта на `requests` или старого бота. + +## Резюме для реализации +- Используйте `httpx.Client(http2=True)`. +- Убедитесь, что `h2` есть в `requirements.txt` отдельной строкой. +- Не используйте `httpx[http2]` в конфигах, если платформа капризная. diff --git a/history/llm-http2-question.md b/history/llm-http2-question.md new file mode 100644 index 0000000..236cebc --- /dev/null +++ b/history/llm-http2-question.md @@ -0,0 +1,45 @@ +# Вопрос: HTTP/2 из Python к aillm.ru + +## Контекст + +Flask-приложение в контейнере `python:3.12-slim`, деплоится через облачную платформу. +Нужно отправлять промпты к LLM API: `https://api.aillm.ru/v1/chat/completions` +(ключ: `sk-ucI5YvOticoOQ9Kuj5K9mQ`, модель: `gpt-oss-120b`). + +## Проблема + +`api.aillm.ru` за ddos-guard, который **требует HTTP/2 от облачных IP**. +С локальной машины работает любой HTTP-клиент (requests, curl без --http2), +но из облачного контейнера — только HTTP/2. + +Что пробовали, что НЕ сработало: +- `requests` — HTTP/1.1, ddos-guard возвращает ложный 401 +- `httpx[http2]` в requirements.txt — платформа не парсит `[http2]`, h2 не ставится +- `curl` через subprocess — curl не установлен в slim-контейнере, apt недоступен +- свой HTTP/2 клиент на h2+socket — слишком сложно, заказчик против + +## Вопрос + +**Как в Python из slim-контейнера сделать HTTP/2 POST-запрос к aillm.ru?** + +Требования: +1. Никакого apt/curl — только pip +2. Просто, без велосипедов на сокетах +3. Работает в python:3.12-slim +4. Ключ передаётся как `Authorization: Bearer sk-ucI5YvOticoOQ9Kuj5K9mQ` +5. Тело: JSON с model, messages, max_tokens, temperature + +Доступные модели на aillm.ru: `gpt-oss-120b`, `qwen3.6-27b-fp8`, `qwen3-6-27b-fp8-opt`, `whisper-large-v3-turbo`. + +## Для справки + +В Node.js это работает одной строкой: +```js +await fetch('https://api.aillm.ru/v1/chat/completions', { + method: 'POST', + headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ model: 'gpt-oss-120b', messages: [...] }) +}); +``` + +Нужен такой же простой способ в Python. diff --git a/history/session-01-init.md b/history/session-01-init.md index b9a82fd..50fcee7 100644 --- a/history/session-01-init.md +++ b/history/session-01-init.md @@ -343,3 +343,72 @@ Python-библиотеки (`requests`, `httpx`) не держат HTTP/2 из - `cc3203a` — `test_routes`: action `debug` для просмотра env vars - `0d0ba18` — debug показывает длину ключа - `b457de4` — debug показывает `h2_available` и `httpx_version` + +--- + +## Развязка саги с LLM: опечатка в ключе + +После 2 часов попыток (HTTP/2, h2, curl, httpx) причина оказалась простой: +**в переменной `LLM_API_KEY` в платформе была опечатка**. + +Сравнение хешей: +- Правильный ключ: `sk-ucI5YvOticoOQ9Kuj5K9mQ` → hash `5fad79ba` +- Ключ в облаке: `sk-ucI5YvOticoOQK9ujK5m9Q` → hash `c772c3e5` (ошибка 401) + +Перепутаны местами `9K` → `K9` и `5K` → `K5`. + +**Урок:** всегда первым делом проверять входные данные, а не гнаться за архитектурными гипотезами. + +### Финальное решение LLM +`httpx` + `h2` (отдельно в requirements) + `http2=True`. Работает. +Модель: `gpt-oss-120b` (120B, reasoning). + +--- + +## Extractor (extractor.py) — 14.06.2026 + +LLM-извлечение строк спецификации из распарсенного текста. + +**Поток:** `parsed_text → промпт → LLM → JSON → [{row_num, name, price, qty, sum, date}]` + +**Результат на реальном документе:** 14 строк из двух таблиц спецификации. +LLM корректно определила изменение цены (213 905 → 250 000) и разные даты (01.04 → 25.04). + +Промпт: строгий формат JSON, поддержка `unresolved` для нераспознанных значений. + +--- + +## Differ (differ.py) — 14.06.2026 + +Сравнение строк спецификаций между допниками. Чистая функция без зависимостей. + +**Вход:** `rows_old`, `rows_new` (списки dict) +**Выход:** `{changes: [{row_num, change_type, old_values, new_values, changed_fields}], summary}` + +**change_type:** `added` | `deleted` | `changed` | `unchanged` + +**Тест:** row 1 (changed: price, date), row 2 (unchanged), row 3 (added) ✅ + +--- + +## Текущий статус (14.06.2026 08:15) + +### Готовые слои (11 файлов) +``` +site/ +├── app.py — сборка (ensure_schema + Blueprints) +├── db.py — транспорт БД (connect, query, execute, query_one) +├── schema.py — DDL 5 таблиц +├── parser.py — bytes → elements (docx/pdf/doc/zip) +├── textify.py — elements → текст +├── upload.py — POST /upload (file → parse → textify → DB) +├── llm_client.py — HTTP/2 к aillm.ru (httpx+h2) +├── extractor.py — текст → LLM → строки спецификации +├── differ.py — сравнение строк между версиями +├── test_routes.py — /test (status, tables, sql, exec, llm, extract, debug, createdb) +└── templates/ +``` + +### Осталось +- `api.py` — Blueprint для договоров/допников/истории +- Интеграция цепочки: upload → extract → save spec_rows → diff → spec_history