doc: развязка LLM-саги, extractor, differ, текущий статус
This commit is contained in:
@@ -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]` в конфигах, если платформа капризная.
|
||||||
@@ -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.
|
||||||
@@ -343,3 +343,72 @@ Python-библиотеки (`requests`, `httpx`) не держат HTTP/2 из
|
|||||||
- `cc3203a` — `test_routes`: action `debug` для просмотра env vars
|
- `cc3203a` — `test_routes`: action `debug` для просмотра env vars
|
||||||
- `0d0ba18` — debug показывает длину ключа
|
- `0d0ba18` — debug показывает длину ключа
|
||||||
- `b457de4` — debug показывает `h2_available` и `httpx_version`
|
- `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
|
||||||
|
|||||||
Reference in New Issue
Block a user