chllm 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chllm-0.1.0/.github/workflows/ci.yml +44 -0
- chllm-0.1.0/.gitignore +36 -0
- chllm-0.1.0/LICENSE +21 -0
- chllm-0.1.0/PKG-INFO +172 -0
- chllm-0.1.0/README.md +154 -0
- chllm-0.1.0/antigravity.md +30 -0
- chllm-0.1.0/examples/orchestration_demo.py +61 -0
- chllm-0.1.0/examples/recovery_demo.py +51 -0
- chllm-0.1.0/pyproject.toml +70 -0
- chllm-0.1.0/src/chllm/__init__.py +39 -0
- chllm-0.1.0/src/chllm/antigravity.md +30 -0
- chllm-0.1.0/src/chllm/builder.py +89 -0
- chllm-0.1.0/src/chllm/context.py +83 -0
- chllm-0.1.0/src/chllm/exceptions/__init__.py +25 -0
- chllm-0.1.0/src/chllm/exceptions/base.py +5 -0
- chllm-0.1.0/src/chllm/exceptions/parsing.py +11 -0
- chllm-0.1.0/src/chllm/exceptions/provider.py +34 -0
- chllm-0.1.0/src/chllm/masking.py +119 -0
- chllm-0.1.0/src/chllm/metrics.py +122 -0
- chllm-0.1.0/src/chllm/orchestrator.py +293 -0
- chllm-0.1.0/src/chllm/parser.py +468 -0
- chllm-0.1.0/src/chllm/py.typed +1 -0
- chllm-0.1.0/src/chllm/utils.py +24 -0
- chllm-0.1.0/tests/__init__.py +0 -0
- chllm-0.1.0/tests/test_agent_orchestrator.py +54 -0
- chllm-0.1.0/tests/test_batch_utils.py +22 -0
- chllm-0.1.0/tests/test_builder.py +72 -0
- chllm-0.1.0/tests/test_context.py +38 -0
- chllm-0.1.0/tests/test_exceptions.py +25 -0
- chllm-0.1.0/tests/test_masking.py +93 -0
- chllm-0.1.0/tests/test_metrics.py +53 -0
- chllm-0.1.0/tests/test_orchestrator.py +80 -0
- chllm-0.1.0/tests/test_orchestrator_single.py +48 -0
- chllm-0.1.0/tests/test_parser.py +80 -0
- chllm-0.1.0/tests/test_parser_robustness.py +42 -0
- chllm-0.1.0/tests/test_parser_tools.py +91 -0
- chllm-0.1.0/tests/test_parser_verification.py +40 -0
- chllm-0.1.0/tests/test_prompt_hierarchy.py +20 -0
- chllm-0.1.0/uv.lock +1222 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
ci:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v3
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
cache-dependency-glob: "uv.lock"
|
|
24
|
+
|
|
25
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
26
|
+
run: uv python install ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: uv sync
|
|
30
|
+
|
|
31
|
+
- name: Run Ruff check
|
|
32
|
+
run: uv run ruff check .
|
|
33
|
+
|
|
34
|
+
- name: Run Ruff format check
|
|
35
|
+
run: uv run ruff format --check .
|
|
36
|
+
|
|
37
|
+
- name: Run Mypy
|
|
38
|
+
run: uv run mypy .
|
|
39
|
+
|
|
40
|
+
- name: Run chutils dev ai-lint
|
|
41
|
+
run: uv run chutils dev ai-lint
|
|
42
|
+
|
|
43
|
+
- name: Run tests with pytest
|
|
44
|
+
run: uv run pytest
|
chllm-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Environments
|
|
13
|
+
.venv/
|
|
14
|
+
env/
|
|
15
|
+
venv/
|
|
16
|
+
ENV/
|
|
17
|
+
|
|
18
|
+
# Testing & Coverage
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
htmlcov/
|
|
23
|
+
|
|
24
|
+
# Type Checking & Linters
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.ruff_cache/
|
|
27
|
+
|
|
28
|
+
# IDEs
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# Project specific
|
|
35
|
+
logs/
|
|
36
|
+
*.log
|
chllm-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chu4hel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
chllm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: chllm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Отказоустойчивый Python фреймворк для взаимодействия с LLM, валидации Pydantic и надежного парсинга структурированных ответов.
|
|
5
|
+
Author-email: Chu4hel <sergeiivanov636@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: pydantic>=2.8.0
|
|
10
|
+
Provides-Extra: all
|
|
11
|
+
Requires-Dist: chutils>=3.5.0; extra == 'all'
|
|
12
|
+
Requires-Dist: tiktoken>=0.7.0; extra == 'all'
|
|
13
|
+
Provides-Extra: chutils
|
|
14
|
+
Requires-Dist: chutils>=3.5.0; extra == 'chutils'
|
|
15
|
+
Provides-Extra: tokens
|
|
16
|
+
Requires-Dist: tiktoken>=0.7.0; extra == 'tokens'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# chllm: Robust Structured Data Framework for LLMs
|
|
20
|
+
|
|
21
|
+
[](https://www.python.org/downloads/)
|
|
22
|
+
[](https://opensource.org/licenses/MIT)
|
|
23
|
+
|
|
24
|
+
`chllm` (произносится *chill-em*) — это профессиональный легковесный Python-фреймворк для построения отказоустойчивых конвейеров обработки данных через LLM. Библиотека специализируется на извлечении структурированных ответов (JSON / Pydantic) в условиях нестабильных API, обрывов контекста и жестких лимитов провайдеров.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🚀 Почему chllm?
|
|
29
|
+
|
|
30
|
+
Работа с LLM в реальных приложениях сопряжена с рядом проблем:
|
|
31
|
+
- **Обрезанные ответы**: модели часто не успевают закрыть скобки/кавычки JSON из-за лимита токенов.
|
|
32
|
+
- **Галлюцинации синтаксиса**: ИИ может непреднамеренно перевести или сломать переменные, плейсхолдеры и теги.
|
|
33
|
+
- **Сложные ошибки и Rate Limits**: ретраи для ошибок 429, 503 и фильтрации контента требуют принципиально разной обработки.
|
|
34
|
+
- **Агентные циклы**: необходимость надежно парсить вызовы инструментов (Tool Calls) и управлять шагами выполнения.
|
|
35
|
+
|
|
36
|
+
`chllm` берет всю эту рутину на себя.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 📦 Установка
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Базовая установка (только Pydantic)
|
|
44
|
+
pip install chllm
|
|
45
|
+
# или через uv
|
|
46
|
+
uv add chllm
|
|
47
|
+
|
|
48
|
+
# С расширенным логированием через chutils
|
|
49
|
+
uv add "chllm[chutils]"
|
|
50
|
+
|
|
51
|
+
# С точным подсчетом токенов через tiktoken
|
|
52
|
+
uv add "chllm[tokens]"
|
|
53
|
+
|
|
54
|
+
# Полный набор
|
|
55
|
+
uv add "chllm[all]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🛠 Ключевые модули
|
|
61
|
+
|
|
62
|
+
### 1. RobustLLMParser (`chllm.parser`)
|
|
63
|
+
Интеллектуальный парсер, способный извлекать и восстанавливать данные даже из поврежденных ответов:
|
|
64
|
+
- **Стековое восстановление (Deep Recovery)**: автоматически достраивает незакрытые скобки, кавычки и массивы в оборванном JSON.
|
|
65
|
+
- **Извлечение из Markdown**: находит JSON-блоки внутри пояснительного текста или рассуждений модели.
|
|
66
|
+
- **JSON Lines Fallback**: если модель прислала поток несоединенных JSON-объектов, парсер автоматически объединит их в единый список/батч.
|
|
67
|
+
- **Интеграция с Pydantic**: строгая валидация и приведение к типам моделей на лету.
|
|
68
|
+
- **Tool Use Parsing**: метод `parse_tool_calls` находит структурированные вызовы инструментов.
|
|
69
|
+
|
|
70
|
+
### 2. Orchestrator & AgentOrchestrator (`chllm.orchestrator`)
|
|
71
|
+
Двигатель выполнения запросов с адаптивным поведением:
|
|
72
|
+
- **Бинарное деление батчей (Batch Splitting)**: при возникновении ошибок размера или цензуры рекурсивно делит батч, изолируя сбойный элемент.
|
|
73
|
+
- **Умная стратегия повторов (RetryStrategy)**: экспоненциальная задержка с рандомизированным джиттером для защиты от перегрузки API.
|
|
74
|
+
- **Одиночные запросы**: метод `execute_single(prompt)` для удобного выполнения утилитарных задач.
|
|
75
|
+
- **Агентный цикл (`AgentOrchestrator`)**: метод `execute_loop` берет на себя цикл "запрос -> парсинг инструментов -> выполнение -> возврат результата".
|
|
76
|
+
|
|
77
|
+
### 3. ContentMasker (`chllm.masking`)
|
|
78
|
+
Защита системного синтаксиса и чувствительных участков текста:
|
|
79
|
+
- Маскирует переменные (например, `[MCname]`, `%(user)s`, `{b}...{/b}`) в плейсхолдеры вида `[[[VAR_0]]]`.
|
|
80
|
+
- Модель видит структуру предложения, но физически не может повредить или перевести системные теги.
|
|
81
|
+
- Корректная сортировка паттернов по длине для предотвращения коллизий.
|
|
82
|
+
|
|
83
|
+
### 4. Metrics & Token Estimation (`chllm.metrics`)
|
|
84
|
+
Контроль расхода токенов:
|
|
85
|
+
- `TokenCounter`: поддержка эвристического расчета для русского и английского языков, а также токенизатора `tiktoken`.
|
|
86
|
+
- `estimate_completion_tokens`: прогнозирование объема ответа с учетом коэффициента языкового расширения и оверхеда схемы.
|
|
87
|
+
|
|
88
|
+
### 5. Context & Prompt Builders (`chllm.builder`, `chllm.context`)
|
|
89
|
+
- `PromptBuilder`: динамическая сборка системных промптов, контекста и пользовательских полезных нагрузок.
|
|
90
|
+
- `ContextBuilder`: управление скользящей цепочкой контекста диалогов (Chain Context).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 📖 Быстрый старт
|
|
95
|
+
|
|
96
|
+
### Восстановление поврежденного JSON
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from pydantic import BaseModel
|
|
100
|
+
from chllm import RobustLLMParser
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class UserItem(BaseModel):
|
|
104
|
+
id: int
|
|
105
|
+
name: str
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
parser = RobustLLMParser()
|
|
109
|
+
|
|
110
|
+
# Модель оборвала ответ на середине:
|
|
111
|
+
broken_response = """
|
|
112
|
+
Вот результаты:
|
|
113
|
+
```json
|
|
114
|
+
[
|
|
115
|
+
{"id": 1, "name": "Алиса"},
|
|
116
|
+
{"id": 2, "name": "Борис"
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
data = parser.parse(broken_response, validation_model=UserItem)
|
|
120
|
+
# data["batch"] -> [UserItem(id=1, name="Алиса")]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Защита переменных при переводе / рерайте
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from chllm import ContentMasker
|
|
127
|
+
|
|
128
|
+
masker = ContentMasker(patterns=[r"\[.+?\]", r"\{.+?\}"])
|
|
129
|
+
text = "Привет, [player_name]! Нажми {b}Старт{/b}."
|
|
130
|
+
|
|
131
|
+
masked = masker.mask(text)
|
|
132
|
+
# masked.masked_text -> "Привет, [[[VAR_0]]]! Нажми [[[VAR_1]]]Старт[[[VAR_2]]]."
|
|
133
|
+
|
|
134
|
+
# Отправляем masked.masked_text в LLM и получаем "Hello, [[[VAR_0]]]! Press [[[VAR_1]]]Start[[[VAR_2]]]."
|
|
135
|
+
|
|
136
|
+
demasked = masker.demask(translated_text, masked.mapping)
|
|
137
|
+
# demasked -> "Hello, [player_name]! Press {b}Start{/b}."
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Оркестратор запросов с ретраями
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from chllm import Orchestrator, RetryStrategy, RateLimitError
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class MyLLMProvider:
|
|
147
|
+
async def execute(self, payload: str) -> str:
|
|
148
|
+
# Ваш сетевой вызов к API модели
|
|
149
|
+
return await api_client.generate(payload)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
orchestrator = Orchestrator(
|
|
153
|
+
provider=MyLLMProvider(),
|
|
154
|
+
strategy=RetryStrategy(max_retries=3, base_delay=1.5),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
response = await orchestrator.execute_single("Объясни квантовую запутанность кратко.")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🏗 Архитектура
|
|
163
|
+
|
|
164
|
+
Библиотека строго следует принципу **Dependency Inversion**:
|
|
165
|
+
- Модули не привязаны к конкретным внешним SDK (Google GenAI, OpenAI, Anthropic) — взаимодействие построено через протоколы.
|
|
166
|
+
- Логирование автономно: при наличии `chutils` используется его структурированный логгер, иначе — стандартный `logging`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 📄 Лицензия
|
|
171
|
+
|
|
172
|
+
Распространяется под лицензией [MIT](LICENSE).
|
chllm-0.1.0/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# chllm: Robust Structured Data Framework for LLMs
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
`chllm` (произносится *chill-em*) — это профессиональный легковесный Python-фреймворк для построения отказоустойчивых конвейеров обработки данных через LLM. Библиотека специализируется на извлечении структурированных ответов (JSON / Pydantic) в условиях нестабильных API, обрывов контекста и жестких лимитов провайдеров.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🚀 Почему chllm?
|
|
11
|
+
|
|
12
|
+
Работа с LLM в реальных приложениях сопряжена с рядом проблем:
|
|
13
|
+
- **Обрезанные ответы**: модели часто не успевают закрыть скобки/кавычки JSON из-за лимита токенов.
|
|
14
|
+
- **Галлюцинации синтаксиса**: ИИ может непреднамеренно перевести или сломать переменные, плейсхолдеры и теги.
|
|
15
|
+
- **Сложные ошибки и Rate Limits**: ретраи для ошибок 429, 503 и фильтрации контента требуют принципиально разной обработки.
|
|
16
|
+
- **Агентные циклы**: необходимость надежно парсить вызовы инструментов (Tool Calls) и управлять шагами выполнения.
|
|
17
|
+
|
|
18
|
+
`chllm` берет всю эту рутину на себя.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📦 Установка
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Базовая установка (только Pydantic)
|
|
26
|
+
pip install chllm
|
|
27
|
+
# или через uv
|
|
28
|
+
uv add chllm
|
|
29
|
+
|
|
30
|
+
# С расширенным логированием через chutils
|
|
31
|
+
uv add "chllm[chutils]"
|
|
32
|
+
|
|
33
|
+
# С точным подсчетом токенов через tiktoken
|
|
34
|
+
uv add "chllm[tokens]"
|
|
35
|
+
|
|
36
|
+
# Полный набор
|
|
37
|
+
uv add "chllm[all]"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🛠 Ключевые модули
|
|
43
|
+
|
|
44
|
+
### 1. RobustLLMParser (`chllm.parser`)
|
|
45
|
+
Интеллектуальный парсер, способный извлекать и восстанавливать данные даже из поврежденных ответов:
|
|
46
|
+
- **Стековое восстановление (Deep Recovery)**: автоматически достраивает незакрытые скобки, кавычки и массивы в оборванном JSON.
|
|
47
|
+
- **Извлечение из Markdown**: находит JSON-блоки внутри пояснительного текста или рассуждений модели.
|
|
48
|
+
- **JSON Lines Fallback**: если модель прислала поток несоединенных JSON-объектов, парсер автоматически объединит их в единый список/батч.
|
|
49
|
+
- **Интеграция с Pydantic**: строгая валидация и приведение к типам моделей на лету.
|
|
50
|
+
- **Tool Use Parsing**: метод `parse_tool_calls` находит структурированные вызовы инструментов.
|
|
51
|
+
|
|
52
|
+
### 2. Orchestrator & AgentOrchestrator (`chllm.orchestrator`)
|
|
53
|
+
Двигатель выполнения запросов с адаптивным поведением:
|
|
54
|
+
- **Бинарное деление батчей (Batch Splitting)**: при возникновении ошибок размера или цензуры рекурсивно делит батч, изолируя сбойный элемент.
|
|
55
|
+
- **Умная стратегия повторов (RetryStrategy)**: экспоненциальная задержка с рандомизированным джиттером для защиты от перегрузки API.
|
|
56
|
+
- **Одиночные запросы**: метод `execute_single(prompt)` для удобного выполнения утилитарных задач.
|
|
57
|
+
- **Агентный цикл (`AgentOrchestrator`)**: метод `execute_loop` берет на себя цикл "запрос -> парсинг инструментов -> выполнение -> возврат результата".
|
|
58
|
+
|
|
59
|
+
### 3. ContentMasker (`chllm.masking`)
|
|
60
|
+
Защита системного синтаксиса и чувствительных участков текста:
|
|
61
|
+
- Маскирует переменные (например, `[MCname]`, `%(user)s`, `{b}...{/b}`) в плейсхолдеры вида `[[[VAR_0]]]`.
|
|
62
|
+
- Модель видит структуру предложения, но физически не может повредить или перевести системные теги.
|
|
63
|
+
- Корректная сортировка паттернов по длине для предотвращения коллизий.
|
|
64
|
+
|
|
65
|
+
### 4. Metrics & Token Estimation (`chllm.metrics`)
|
|
66
|
+
Контроль расхода токенов:
|
|
67
|
+
- `TokenCounter`: поддержка эвристического расчета для русского и английского языков, а также токенизатора `tiktoken`.
|
|
68
|
+
- `estimate_completion_tokens`: прогнозирование объема ответа с учетом коэффициента языкового расширения и оверхеда схемы.
|
|
69
|
+
|
|
70
|
+
### 5. Context & Prompt Builders (`chllm.builder`, `chllm.context`)
|
|
71
|
+
- `PromptBuilder`: динамическая сборка системных промптов, контекста и пользовательских полезных нагрузок.
|
|
72
|
+
- `ContextBuilder`: управление скользящей цепочкой контекста диалогов (Chain Context).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 📖 Быстрый старт
|
|
77
|
+
|
|
78
|
+
### Восстановление поврежденного JSON
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from pydantic import BaseModel
|
|
82
|
+
from chllm import RobustLLMParser
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class UserItem(BaseModel):
|
|
86
|
+
id: int
|
|
87
|
+
name: str
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
parser = RobustLLMParser()
|
|
91
|
+
|
|
92
|
+
# Модель оборвала ответ на середине:
|
|
93
|
+
broken_response = """
|
|
94
|
+
Вот результаты:
|
|
95
|
+
```json
|
|
96
|
+
[
|
|
97
|
+
{"id": 1, "name": "Алиса"},
|
|
98
|
+
{"id": 2, "name": "Борис"
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
data = parser.parse(broken_response, validation_model=UserItem)
|
|
102
|
+
# data["batch"] -> [UserItem(id=1, name="Алиса")]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Защита переменных при переводе / рерайте
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from chllm import ContentMasker
|
|
109
|
+
|
|
110
|
+
masker = ContentMasker(patterns=[r"\[.+?\]", r"\{.+?\}"])
|
|
111
|
+
text = "Привет, [player_name]! Нажми {b}Старт{/b}."
|
|
112
|
+
|
|
113
|
+
masked = masker.mask(text)
|
|
114
|
+
# masked.masked_text -> "Привет, [[[VAR_0]]]! Нажми [[[VAR_1]]]Старт[[[VAR_2]]]."
|
|
115
|
+
|
|
116
|
+
# Отправляем masked.masked_text в LLM и получаем "Hello, [[[VAR_0]]]! Press [[[VAR_1]]]Start[[[VAR_2]]]."
|
|
117
|
+
|
|
118
|
+
demasked = masker.demask(translated_text, masked.mapping)
|
|
119
|
+
# demasked -> "Hello, [player_name]! Press {b}Start{/b}."
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Оркестратор запросов с ретраями
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from chllm import Orchestrator, RetryStrategy, RateLimitError
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class MyLLMProvider:
|
|
129
|
+
async def execute(self, payload: str) -> str:
|
|
130
|
+
# Ваш сетевой вызов к API модели
|
|
131
|
+
return await api_client.generate(payload)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
orchestrator = Orchestrator(
|
|
135
|
+
provider=MyLLMProvider(),
|
|
136
|
+
strategy=RetryStrategy(max_retries=3, base_delay=1.5),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
response = await orchestrator.execute_single("Объясни квантовую запутанность кратко.")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 🏗 Архитектура
|
|
145
|
+
|
|
146
|
+
Библиотека строго следует принципу **Dependency Inversion**:
|
|
147
|
+
- Модули не привязаны к конкретным внешним SDK (Google GenAI, OpenAI, Anthropic) — взаимодействие построено через протоколы.
|
|
148
|
+
- Логирование автономно: при наличии `chutils` используется его структурированный логгер, иначе — стандартный `logging`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 📄 Лицензия
|
|
153
|
+
|
|
154
|
+
Распространяется под лицензией [MIT](LICENSE).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Манифест ИИ: chllm (Robust LLM Framework)
|
|
2
|
+
|
|
3
|
+
Модуль chllm представляет собой отказоустойчивый фреймворк для взаимодействия с LLM-провайдерами, управления контекстом промптов и надежного парсинга структурированных ответов.
|
|
4
|
+
|
|
5
|
+
## Назначение и Архитектура
|
|
6
|
+
|
|
7
|
+
Модуль изолирует работу с нейросетевыми моделями от доменной логики приложения.
|
|
8
|
+
|
|
9
|
+
### Ключевые компоненты:
|
|
10
|
+
|
|
11
|
+
1. **RobustLLMParser (parser.py)**:
|
|
12
|
+
- Извлечение JSON, Markdown и очистка ответа от технического шума LLM.
|
|
13
|
+
- Восстановление поврежденного JSON и валидация схем Pydantic.
|
|
14
|
+
|
|
15
|
+
2. **Orchestrator, AgentOrchestrator (orchestrator.py)**:
|
|
16
|
+
- Управление ротацией API-ключей и прокси.
|
|
17
|
+
- Стратегии повторных попыток (RetryStrategy) с экспоненциальной задержкой.
|
|
18
|
+
|
|
19
|
+
3. **ContentMasker (masking.py)**:
|
|
20
|
+
- Маскирование и защитная подстановка чувствительных данных и тегов Ren'Py перед отправкой в LLM.
|
|
21
|
+
|
|
22
|
+
4. **PromptBuilder, ContextBuilder (uilder.py, context.py)**:
|
|
23
|
+
- Динамическая сборка системных и пользовательских промптов с ограничением по токенам.
|
|
24
|
+
|
|
25
|
+
5. **TokenCounter, UsageMetrics (metrics.py)**:
|
|
26
|
+
- Подсчет токенов и сбор статистики использования API.
|
|
27
|
+
|
|
28
|
+
## Правила использования
|
|
29
|
+
- При работе с ответами LLM всегда использовать RobustLLMParser.
|
|
30
|
+
- Ошибки LLM обрабатывать через иерархию CHLLMError (например, RateLimitError, QuotaExceededError).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Демонстрация работы Orchestrator: деление батча и ретраи.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from chllm import ContentBlockedError, Orchestrator, RateLimitError, RetryStrategy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FakeAIProvider:
|
|
11
|
+
"""Мок-провайдер, имитирующий проблемы с лимитами и цензурой."""
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
"""Инициализирует тестовый провайдер."""
|
|
15
|
+
self.attempts: int = 0
|
|
16
|
+
|
|
17
|
+
async def execute(self, data: object) -> object:
|
|
18
|
+
"""Выполняет имитацию запроса к ИИ.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
data: Данные для обработки.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Результат обработки.
|
|
25
|
+
"""
|
|
26
|
+
self.attempts += 1
|
|
27
|
+
|
|
28
|
+
# 1. Имитируем временный лимит на первую попытку
|
|
29
|
+
if self.attempts == 1:
|
|
30
|
+
print("Попытка 1: Имитируем Rate Limit...")
|
|
31
|
+
raise RateLimitError("Rate limit exceeded", retry_delay=0.1)
|
|
32
|
+
|
|
33
|
+
# 2. Имитируем блокировку контента для больших батчей
|
|
34
|
+
if isinstance(data, list) and len(data) > 1:
|
|
35
|
+
print(f"Батч из {len(data)} элементов заблокирован цензурой. Требуется деление!")
|
|
36
|
+
raise ContentBlockedError("Sensitive content detected")
|
|
37
|
+
|
|
38
|
+
print(f"Успешное выполнение для данных: {data}")
|
|
39
|
+
return f"OK: {data}"
|
|
40
|
+
|
|
41
|
+
def pack_single_prompt(self, prompt: str) -> object:
|
|
42
|
+
"""Упаковывает строку промпта."""
|
|
43
|
+
return prompt
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def run_demo() -> None:
|
|
47
|
+
"""Запускает демонстрацию работы оркестратора с обработкой ошибок."""
|
|
48
|
+
provider = FakeAIProvider()
|
|
49
|
+
orchestrator = Orchestrator(provider=provider, strategy=RetryStrategy(max_retries=3, base_delay=0.1))
|
|
50
|
+
|
|
51
|
+
print("--- Запуск оркестратора для батча из 3-х элементов ---")
|
|
52
|
+
# Оркестратор сделает ретрай при первой ошибке,
|
|
53
|
+
# а затем поделит батч, пока не найдет безопасные части.
|
|
54
|
+
results = await orchestrator.execute(["Safe 1", "Safe 2", "Safe 3"])
|
|
55
|
+
|
|
56
|
+
print("\n--- Финальные результаты ---")
|
|
57
|
+
print(results)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
asyncio.run(run_demo())
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Пример использования RobustLLMParser для восстановления поврежденного JSON.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from chllm import RobustLLMParser
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Product(BaseModel):
|
|
11
|
+
"""Модель данных товара для демонстрации парсинга."""
|
|
12
|
+
|
|
13
|
+
name: str
|
|
14
|
+
price: float
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_demo() -> None:
|
|
18
|
+
"""Запускает демонстрацию восстановления поврежденного JSON ответа."""
|
|
19
|
+
parser = RobustLLMParser()
|
|
20
|
+
|
|
21
|
+
# Пример 1: ИИ обрезал ответ на середине
|
|
22
|
+
raw_response = """
|
|
23
|
+
Вот список товаров в формате JSON:
|
|
24
|
+
```json
|
|
25
|
+
[
|
|
26
|
+
{"name": "Laptop", "price": 999.99},
|
|
27
|
+
{"name": "Smartphone", "price": 49
|
|
28
|
+
""" # JSON не закрыт, кавычка оборвана
|
|
29
|
+
|
|
30
|
+
print("--- Исходный текст ---")
|
|
31
|
+
print(raw_response)
|
|
32
|
+
|
|
33
|
+
result = parser.parse(raw_response, validation_model=Product)
|
|
34
|
+
|
|
35
|
+
print("\n--- Результат парсинга (бач) ---")
|
|
36
|
+
for item in result["batch"]:
|
|
37
|
+
if isinstance(item, Product):
|
|
38
|
+
print(f"Товар: {item.name}, Цена: {item.price}")
|
|
39
|
+
else:
|
|
40
|
+
print(f"Товар: {item.get('name')}, Цена: {item.get('price')}")
|
|
41
|
+
|
|
42
|
+
# Пример 2: Глубокая вложенность
|
|
43
|
+
nested_raw = '{"data": {"user": {"id": 1, "meta": {"bio": "Hello'
|
|
44
|
+
raw_obj = parser.parse_raw(nested_raw)
|
|
45
|
+
|
|
46
|
+
print("\n--- Восстановление вложенного объекта ---")
|
|
47
|
+
print(f"Полученный объект: {raw_obj}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
run_demo()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "chllm"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Отказоустойчивый Python фреймворк для взаимодействия с LLM, валидации Pydantic и надежного парсинга структурированных ответов."
|
|
5
|
+
authors = [{ name = "Chu4hel", email = "sergeiivanov636@gmail.com" }]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"pydantic>=2.8.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
chutils = ["chutils>=3.5.0"]
|
|
15
|
+
tokens = ["tiktoken>=0.7.0"]
|
|
16
|
+
all = [
|
|
17
|
+
"chutils>=3.5.0",
|
|
18
|
+
"tiktoken>=0.7.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8.0.0",
|
|
24
|
+
"pytest-asyncio>=0.23.0",
|
|
25
|
+
"pytest-mock>=3.12.0",
|
|
26
|
+
"ruff>=0.8.0",
|
|
27
|
+
"mypy>=1.10.0",
|
|
28
|
+
"chutils[rich]>=3.5.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/chllm"]
|
|
33
|
+
|
|
34
|
+
[[tool.uv.index]]
|
|
35
|
+
name = "aliyun"
|
|
36
|
+
url = "https://mirrors.aliyun.com/pypi/simple/"
|
|
37
|
+
default = true
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["hatchling"]
|
|
41
|
+
build-backend = "hatchling.build"
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
pythonpath = ["src"]
|
|
45
|
+
asyncio_mode = "auto"
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
markers = [
|
|
48
|
+
"slow: маркер для долгих тестов",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
target-version = "py310"
|
|
53
|
+
line-length = 120
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = ["E", "F", "W", "I", "UP", "SIM", "RUF"]
|
|
57
|
+
ignore = [
|
|
58
|
+
"RUF001", # Ambiguous unicode characters in strings (русский язык)
|
|
59
|
+
"RUF002", # Ambiguous unicode characters in docstrings
|
|
60
|
+
"RUF003", # Ambiguous unicode characters in comments
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.per-file-ignores]
|
|
64
|
+
"__init__.py" = ["F401"]
|
|
65
|
+
"tests/*" = ["S101"]
|
|
66
|
+
|
|
67
|
+
[tool.mypy]
|
|
68
|
+
python_version = "3.10"
|
|
69
|
+
check_untyped_defs = true
|
|
70
|
+
ignore_missing_imports = true
|