trustgraph/docs/tech-specs/jsonl-prompt-output.ru.md

492 lines
24 KiB
Markdown
Raw Normal View History

---
layout: default
title: "Техническая спецификация формата вывода JSONL для запросов"
parent: "Russian (Beta)"
---
# Техническая спецификация формата вывода JSONL для запросов
> **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.
## Обзор
Эта спецификация описывает реализацию формата вывода JSONL (JSON Lines) для ответов на запросы в TrustGraph. JSONL обеспечивает устойчивость к усечению при извлечении структурированных данных из ответов LLM, решая критические проблемы, связанные с повреждением выходных массивов JSON, когда ответы LLM достигают лимита токенов.
Эта реализация поддерживает следующие сценарии использования:
1. **Извлечение, устойчивое к усечению**: Извлекайте допустимые частичные результаты, даже когда
вывод LLM усекается в середине ответа.
<<<<<<< HEAD
2. **Извлечение в больших масштабах**: Обрабатывайте извлечение большого количества элементов без риска
=======
2. **Масштабное извлечение**: Обрабатывайте извлечение большого количества элементов без риска
>>>>>>> 82edf2d (New md files from RunPod)
полной сбоя из-за ограничений на количество токенов.
3. **Извлечение данных разных типов**: Поддерживайте извлечение нескольких типов сущностей
(определения, отношения, сущности, атрибуты) в одном запросе.
4. **Вывод, совместимый со стримингом**: Обеспечьте возможность будущей потоковой/инкрементной
обработки результатов извлечения.
## Цели
**Обратная совместимость**: Существующие запросы, использующие `response-type: "text"` и
`response-type: "json"`, продолжают работать без изменений.
**Устойчивость к усечению**: Частичные выходные данные LLM приводят к частичным, но допустимым результатам,
а не к полному сбою.
**Проверка схемы**: Поддержка проверки JSON-схемы для отдельных объектов.
**Дискриминированные объединения**: Поддержка выходных данных смешанных типов с использованием поля `type`
в качестве дискриминатора.
<<<<<<< HEAD
**Минимальные изменения API**: Расширение существующей конфигурации запросов с добавлением нового
типа ответа и ключа схемы.
## Обзор
=======
**Минимальные изменения API**: Расширение существующей конфигурации запросов с помощью нового
типа ответа и ключа схемы.
## Контекст
>>>>>>> 82edf2d (New md files from RunPod)
### Текущая архитектура
Сервис запросов поддерживает два типа ответов:
1. `response-type: "text"` - Необработанный текстовый ответ, возвращаемый как есть.
2. `response-type: "json"` - JSON, полученный из ответа и проверенный на соответствие
необязательной `schema`.
Текущая реализация в `trustgraph-flow/trustgraph/template/prompt_manager.py`:
```python
class Prompt:
def __init__(self, template, response_type = "text", terms=None, schema=None):
self.template = template
self.response_type = response_type
self.terms = terms
self.schema = schema
```
### Текущие ограничения
Когда запросы извлечения требуют вывод в виде массивов JSON (`[{...}, {...}, ...]`):
**Повреждение из-за усечения**: Если языковая модель достигает лимита выходных токенов в середине массива,
весь ответ становится недействительным JSON и не может быть проанализирован.
**Анализ "все или ничего"**: Необходимо получить полный вывод перед анализом.
**Нет частичных результатов**: Усеченный ответ дает нулевые полезные данные.
**Ненадежно для больших извлечений**: Чем больше извлеченных элементов, тем выше риск сбоя.
Данная спецификация решает эти ограничения, вводя формат JSONL для
запросов извлечения, где каждый извлеченный элемент является полным JSON-объектом на своей
строке.
## Техническое проектирование
### Расширение типа ответа
Добавьте новый тип ответа `"jsonl"` наряду с существующими типами `"text"` и `"json"`.
#### Изменения конфигурации
**Новое значение типа ответа:**
```
"response-type": "jsonl"
```
**Интерпретация схемы:**
Существующий ключ `"schema"` используется как для `"json"`, так и для `"jsonl"` типов ответов.
Интерпретация зависит от типа ответа:
`"json"`: Схема описывает весь ответ (обычно массив или объект).
`"jsonl"`: Схема описывает каждую отдельную строку/объект.
```json
{
"response-type": "jsonl",
"schema": {
"type": "object",
"properties": {
"entity": { "type": "string" },
"definition": { "type": "string" }
},
"required": ["entity", "definition"]
}
}
```
Это позволяет избежать изменений в инструментах и редакторах конфигурации подсказок.
### Спецификация формата JSONL
#### Простое извлечение
Для подсказок, извлекающих один тип объекта (определения, отношения,
темы, строки), вывод представляет собой один объект JSON на строку без обертки:
**Формат вывода подсказки:**
```
{"entity": "photosynthesis", "definition": "Process by which plants convert sunlight"}
{"entity": "chlorophyll", "definition": "Green pigment in plants"}
{"entity": "mitochondria", "definition": "Powerhouse of the cell"}
```
**Контраст с предыдущим форматом JSON-массива:**
```json
[
{"entity": "photosynthesis", "definition": "Process by which plants convert sunlight"},
{"entity": "chlorophyll", "definition": "Green pigment in plants"},
{"entity": "mitochondria", "definition": "Powerhouse of the cell"}
]
```
Если LLM обрезает текст после строки 2, формат массива JSON приводит к недействительному JSON,
в то время как JSONL дает два допустимых объекта.
#### Извлечение данных смешанных типов (дискриминированные объединения)
Для запросов, извлекающих несколько типов объектов (например, определения и
отношения, или сущности, отношения и атрибуты), используйте поле `"type"`
в качестве дискриминатора:
**Формат вывода запроса:**
```
{"type": "definition", "entity": "DNA", "definition": "Molecule carrying genetic instructions"}
{"type": "relationship", "subject": "DNA", "predicate": "located_in", "object": "cell nucleus", "object-entity": true}
{"type": "definition", "entity": "RNA", "definition": "Molecule that carries genetic information"}
{"type": "relationship", "subject": "RNA", "predicate": "transcribed_from", "object": "DNA", "object-entity": true}
```
**Схема для дискриминированных объединений использует `oneOf`:**
```json
{
"response-type": "jsonl",
"schema": {
"oneOf": [
{
"type": "object",
"properties": {
"type": { "const": "definition" },
"entity": { "type": "string" },
"definition": { "type": "string" }
},
"required": ["type", "entity", "definition"]
},
{
"type": "object",
"properties": {
"type": { "const": "relationship" },
"subject": { "type": "string" },
"predicate": { "type": "string" },
"object": { "type": "string" },
"object-entity": { "type": "boolean" }
},
"required": ["type", "subject", "predicate", "object", "object-entity"]
}
]
}
}
```
#### Извлечение онтологии
Для извлечения онтологии на основе сущностей, связей и атрибутов:
**Формат вывода запроса:**
```
{"type": "entity", "entity": "Cornish pasty", "entity_type": "fo/Recipe"}
{"type": "entity", "entity": "beef", "entity_type": "fo/Food"}
{"type": "relationship", "subject": "Cornish pasty", "subject_type": "fo/Recipe", "relation": "fo/has_ingredient", "object": "beef", "object_type": "fo/Food"}
{"type": "attribute", "entity": "Cornish pasty", "entity_type": "fo/Recipe", "attribute": "fo/serves", "value": "4 people"}
```
### Детали реализации
#### Класс Prompt
Существующий класс `Prompt` не требует изменений. Поле `schema` используется повторно
<<<<<<< HEAD
для JSONL, а его интерпретация определяется `response_type`:
=======
для JSONL, и его интерпретация определяется `response_type`:
>>>>>>> 82edf2d (New md files from RunPod)
```python
class Prompt:
def __init__(self, template, response_type="text", terms=None, schema=None):
self.template = template
self.response_type = response_type
self.terms = terms
self.schema = schema # Interpretation depends on response_type
```
#### PromptManager.load_config
Изменения не требуются - существующая загрузка конфигурации уже обрабатывает
ключ `schema`.
#### Разбор JSONL
Добавлен новый метод разбора ответов в формате JSONL:
```python
def parse_jsonl(self, text):
"""
Parse JSONL response, returning list of valid objects.
Invalid lines (malformed JSON, empty lines) are skipped with warnings.
This provides truncation resilience - partial output yields partial results.
"""
results = []
for line_num, line in enumerate(text.strip().split('\n'), 1):
line = line.strip()
# Skip empty lines
if not line:
continue
# Skip markdown code fence markers if present
if line.startswith('```'):
continue
try:
obj = json.loads(line)
results.append(obj)
except json.JSONDecodeError as e:
# Log warning but continue - this provides truncation resilience
logger.warning(f"JSONL parse error on line {line_num}: {e}")
return results
```
#### Изменения в PromptManager.invoke
Расширить метод invoke для обработки нового типа ответа:
```python
async def invoke(self, id, input, llm):
logger.debug("Invoking prompt template...")
terms = self.terms | self.prompts[id].terms | input
resp_type = self.prompts[id].response_type
prompt = {
"system": self.system_template.render(terms),
"prompt": self.render(id, input)
}
resp = await llm(**prompt)
if resp_type == "text":
return resp
if resp_type == "json":
try:
obj = self.parse_json(resp)
except:
logger.error(f"JSON parse failed: {resp}")
raise RuntimeError("JSON parse fail")
if self.prompts[id].schema:
try:
validate(instance=obj, schema=self.prompts[id].schema)
logger.debug("Schema validation successful")
except Exception as e:
raise RuntimeError(f"Schema validation fail: {e}")
return obj
if resp_type == "jsonl":
objects = self.parse_jsonl(resp)
if not objects:
logger.warning("JSONL parse returned no valid objects")
return []
# Validate each object against schema if provided
if self.prompts[id].schema:
validated = []
for i, obj in enumerate(objects):
try:
validate(instance=obj, schema=self.prompts[id].schema)
validated.append(obj)
except Exception as e:
logger.warning(f"Object {i} failed schema validation: {e}")
return validated
return objects
raise RuntimeError(f"Response type {resp_type} not known")
```
### Затронутые запросы
Следующие запросы должны быть перенесены в формат JSONL:
| Идентификатор запроса | Описание | Тип поля |
|-----------|-------------|------------|
| `extract-definitions` | Извлечение сущностей/определений | Нет (один тип) |
| `extract-relationships` | Извлечение отношений | Нет (один тип) |
| `extract-topics` | Извлечение темы/определения | Нет (один тип) |
| `extract-rows` | Извлечение структурированных строк | Нет (один тип) |
| `agent-kg-extract` | Комбинированное извлечение определений + отношений | Да: `"definition"`, `"relationship"` |
| `extract-with-ontologies` / `ontology-extract` | Извлечение на основе онтологии | Да: `"entity"`, `"relationship"`, `"attribute"` |
### Изменения API
#### Точка зрения клиента
Разбор JSONL прозрачен для вызывающих API сервиса запросов. Разбор происходит
на стороне сервера в сервисе запросов, и ответ возвращается через стандартное
поле `PromptResponse.object` в виде сериализованного массива JSON.
Когда клиенты вызывают сервис запросов (через `PromptClient.prompt()` или аналогично):
**`response-type: "json"`** с схемой массива → клиент получает Python `list`
**`response-type: "jsonl"`** → клиент получает Python `list`
С точки зрения клиента, оба возвращают идентичные структуры данных. Разница заключается только в том, как вывод LLM разбирается на стороне сервера:
Формат массива JSON: Один вызов `json.loads()`; полностью завершается с ошибкой, если усечен
Формат JSONL: Разбор построчно; дает частичные результаты, если усечен
Это означает, что существующий клиентский код, ожидающий список от запросов извлечения,
не требует изменений при переносе запросов из формата JSON в формат JSONL.
#### Возвращаемое значение сервера
Для `response-type: "jsonl"` метод `PromptManager.invoke()` возвращает
`list[dict]`, содержащий все успешно разобранные и проверенные объекты. Этот
список затем сериализуется в JSON для поля `PromptResponse.object`.
### Обработка ошибок
Пустые результаты: Возвращает пустой список `[]` с предупреждением в журнале
Частная ошибка разбора: Возвращает список успешно разобранных объектов с
предупреждениями в журналах об ошибках
Полная ошибка разбора: Возвращает пустой список `[]` с предупреждениями в журналах
Это отличается от `response-type: "json"`, который вызывает `RuntimeError` при
ошибке разбора. Легкое поведение для JSONL намеренно, чтобы обеспечить
устойчивость к усечению.
### Пример конфигурации
Полный пример конфигурации запроса:
```json
{
"prompt": "Extract all entities and their definitions from the following text. Output one JSON object per line.\n\nText:\n{{text}}\n\nOutput format per line:\n{\"entity\": \"<name>\", \"definition\": \"<definition>\"}",
"response-type": "jsonl",
"schema": {
"type": "object",
"properties": {
"entity": {
"type": "string",
"description": "The entity name"
},
"definition": {
"type": "string",
"description": "A clear definition of the entity"
}
},
"required": ["entity", "definition"]
}
}
```
## Соображения безопасности
**Проверка входных данных**: Разбор JSON использует стандартную `json.loads()`, что безопасно
от атак внедрения.
**Проверка схемы**: Используется `jsonschema.validate()` для обеспечения соответствия схеме.
**Отсутствие новых уязвимостей**: Разбор JSONL значительно безопаснее, чем разбор JSON-массивов,
благодаря обработке построчно.
## Соображения производительности
**Память**: Построчный разбор использует меньше пиковой памяти, чем загрузка полных
JSON-массивов.
**Задержка**: Производительность разбора сопоставима с разбором JSON-массивов.
**Проверка**: Проверка схемы выполняется для каждого объекта, что добавляет накладные
расходы, но позволяет получать частичные результаты в случае сбоя проверки.
## Стратегия тестирования
### Юнит-тесты
Разбор JSONL с допустимыми входными данными.
Разбор JSONL с пустыми строками.
<<<<<<< HEAD
Разбор JSONL с блоками форматированного текста Markdown.
=======
Разбор JSONL с блоками кода Markdown.
>>>>>>> 82edf2d (New md files from RunPod)
Разбор JSONL с обрезанной последней строкой.
Разбор JSONL со строками, содержащими недопустимый JSON.
Проверка схемы с использованием `oneOf` для дискриминируемых объединений.
Обратная совместимость: существующие `"text"` и `"json"` подсказки не изменены.
### Интеграционные тесты
<<<<<<< HEAD
Комплексная извлечение данных с использованием подсказок JSONL.
Извлечение данных с имитацией усечения (искусственно ограниченный ответ).
Извлечение данных смешанных типов с использованием дискриминатора типов.
Извлечение данных онтологии со всеми тремя типами.
### Тесты качества извлечения данных.
=======
Комплексная извлечение с подсказками JSONL.
Извлечение с имитацией усечения (искусственно ограниченный ответ).
Извлечение смешанных типов с использованием дискриминатора типа.
Извлечение онтологии со всеми тремя типами.
### Тесты качества извлечения.
>>>>>>> 82edf2d (New md files from RunPod)
Сравнение результатов извлечения: формат JSONL против массива JSON.
Проверка устойчивости к усечению: JSONL возвращает частичные результаты, в то время как JSON - нет.
## План миграции
### Этап 1: Реализация
1. Реализовать метод `parse_jsonl()` в `PromptManager`.
2. Расширить `invoke()` для обработки `response-type: "jsonl"`.
3. Добавить модульные тесты.
### Этап 2: Миграция подсказок
1. Обновить подсказку `extract-definitions` и конфигурацию.
2. Обновить подсказку `extract-relationships` и конфигурацию.
3. Обновить подсказку `extract-topics` и конфигурацию.
4. Обновить подсказку `extract-rows` и конфигурацию.
5. Обновить подсказку `agent-kg-extract` и конфигурацию.
6. Обновить подсказку `extract-with-ontologies` и конфигурацию.
### Этап 3: Обновления для последующих этапов
1. Обновить любой код, использующий результаты извлечения, чтобы он мог обрабатывать возвращаемый тип списка.
2. Обновить код, который категоризует извлечения смешанных типов, используя поле `type`.
3. Обновить тесты, которые проверяют формат выходных данных извлечения.
## Открытые вопросы
На данный момент нет.
## Ссылки
Текущая реализация: `trustgraph-flow/trustgraph/template/prompt_manager.py`
Спецификация JSON Lines: https://jsonlines.org/
Схема JSON `oneOf`: https://json-schema.org/understanding-json-schema/reference/combining.html#oneof
Связанная спецификация: Streaming LLM Responses (`docs/tech-specs/streaming-llm-responses.md`)