Символы верхнего уровня, экспортируемые reactifact (см. reactifact/__init__.py).
Формат по группам: имя — роль в одну строку. Детали — в док-строках модулей —
см. автосгенерированный референс (только на английском,
докстринги в коде не переведены): сигнатуры, типы и полный текст докстринга
рендерятся прямо из исходников на сайте документации, а не копируются сюда
вручную.
Начиная с 0.4.0 reactifact всё ещё pre-1.0, но уже не rc — поверхность ниже
это стабильный контракт, а не движущаяся цель.
Публичный API = каждое имя в reactifact.__all__ (и в __all__ каждого
подмодуля — reactifact.recipes, reactifact.providers, reactifact.viz,
reactifact.eval, reactifact.quick, reactifact.redaction, reactifact.audit, …) — это ровно тот набор символов, что задокументирован на
этой странице. Если что-то импортируется из reactifact, но не входит в
__all__ — это внутренняя деталь без гарантий совместимости. Например,
reactifact.relations.RelationGraph и reactifact.commit_log.CommitLog
существуют потому, что Context разбили на модули поменьше ради
читаемости, но ни один из них не экспортируется: поддерживаемая
поверхность — это Context, а не они.
SemVer в pre-1.0-стиле: минорный бамп (0.4.0 → 0.5.0) может добавить
символы или, в редких случаях, изменить поведение так, что CHANGELOG.md
явно пометит это Breaking — минорные релизы до 1.0 всё ещё позволяют
reactifact исправить архитектурную ошибку. Патч (0.4.0 → 0.4.1) никогда не
убирает и не переименовывает публичный символ и никогда не меняет
задокументированное поведение — только чинит баги относительно него.
Любое ломающее изменение помечено в CHANGELOG.md заголовком
### Breaking, даже в pre-1.0 релизе — см. release.md. Если
перед апгрейдом читать только один раздел — читайте этот.
Всё, что находится под reactifact.cli.* за пределами задокументированных
подкоманд python -m reactifact …, а также любые тест-хелперы модулей —
деталь реализации, даже если формально импортируется.
Тонкий сахар над примитивами ниже для четырёх частых первых задач — у каждого
объекта есть настоящий .agent/.agents и .context запуска, поэтому он
выпускается в Consume/Produce/Effects без переписывания. См.
Быстрый старт §0.
Символ
Роль
agent(system, schema)
один структурированный вызов LLM → один типизированный артефакт
rag(sources)
поиск → материализация → ответ, с провенансом supported_by
tools_agent(system, tools, human=False)
LLM + тулы (human=True → HITL-вопросы)
chat_agent(agents)
настроенный ChatAssistant (по умолчанию store в памяти)
Question / Doc / Answer
обобщённые модели артефактов, которые использует фасад
провайдеры + источники + ресурсы приложения; register(Type, instance) / get(Type) / require(Type) / has(Type) для типизированных коллабораторов (ResourceKey[T], когда их два одного типа); строковые get/set остаются escape hatch'ом (additional); redactor= вычищает текст трейсов; await resources.aclose() закрывает HTTP-клиенты llm/embedder (duck-typed) — вызывайте сами при завершении
ResourceKey[T]
типизированный ключ для регистрации двух ресурсов одного типа (primary/replica, per-tenant)
ResourceScope / RuntimeResources.scope(factory)
async with-билдер: создаёт ресурсы на текущем loop и закрывает на выходе — loop-safe способ владеть провайдерами
current_request()
request-маппинг активного хода (то же, что ProduceCall.request)
конструктор-фабрика агента — без подкласса для обычных контейнеров
Consume / consume
декларативная (или декоратор) завязка реакции; Consume.by_field для скоуп-событий; wakes=False — читать как вход, не будя агента; debounce=True схлопывает несколько событий одного поколения в один запуск
reactifact.consume.CorrelatedConsume
срабатывает (и питает входы) только для ключа корреляции, где присутствуют все типы из require и отсутствуют все из forbid — механизм за JoinConsume/AbsentConsume
reactifact.consume.JoinConsume(*parts, key=…)
фабрика над CorrelatedConsume: срабатывает, когда для одного ключа существуют все перечисленные типы
фабрика над CorrelatedConsume: срабатывает для type, только если для того же ключа ещё нет absent_type
Produce / produce
производитель: пишет self.effects (или слот effects в функции-декораторе) → None; возврат модели/Patch тоже компилируется. Два канонических стиля — подкласс и функция @produce (см. effects); reacts_to=(Type, …) ограничивает produce конкретными триггерящими событиями, когда несколько produce одного агента реагируют не на одно и то же; produce с необязательным параметром trigger получает уже резолвленный триггерящий артефакт вместо сырого event — гарантированно не None для CREATED/UPDATED/STALE события, если также задан reacts_to
Trigger
вторичное (не артефактное) условие входа produce; context_condition(artifact, context) — для условий, которым нужны другие артефакты (join/корреляция); флаг debounce, который читает Runtime
блокирующий цикл LLM+инструменты (system, tools, max_steps, deferred_tool_groups)
HITLLMAgent
цикл LLM+инструменты с паузами на ответ человека (max_asks, отчёт о возобновлении)
ToolUse, ToolUseHITL
produce цикла инструментов; HITL-вариант ждёт одобрения перед исполнением
DeferredToolGroup (reactifact.tool_use)
группа инструментов, чьи схемы не попадают в промпт, пока встроенный инструмент load_tools в ToolUse их не запросит (только LLMAgent/ToolUse, не HITL-вариант)
Tool, FunctionTool, tool, ToolOutput
абстракция и регистрация инструментов
ToolAnswer, Observation
результаты инструментов и наблюдения модели (протокол цикла)
PendingQuestion
HITL-примитив: приостановленный вопрос, ждущий ответа человека, возобновляется через self.effects.resume(...)
будит агентов по событиям; run / arun / astream (каждый принимает request=Mapping); бюджет и параллельность; isolate_errors=True + on_agent_error(agent, event, exc), чтобы исключение одного агента не обрывало весь запуск (по умолчанию — пробрасывается, §69)
ProduceCall.request
request-маппинг хода внутри produce (Runtime.arun(request=…) / ChatAssistant.stream(request=…)); пустой, если не задан
записывает каждый LLM-вызов в JSONL или воспроизводит их точно; ReplayMiss при расхождении
ReplayMiss
воспроизводимый вызов не совпал с записью
verify_run(build, *, recording=None, repeat=2)
прогоняет build(resources) -> Contextrepeat раз под записанной моделью и строгими id (counter_ids), возвращает ReproReport; ok=False, если context_hash разошёлся (реальная недетерминированность)
counter_ids()
детерминированный id_factory (Model:0000, Model:0001, …) для RuntimeResources(id_factory=…) — делает артефакты без явного id воспроизводимыми
replay_context(store, session_id, version=None)
восстанавливает состояние сохранённой сессии на коммите
один структурный вызов; None при честном сбое; validate(model)->bool добавляет домен-проверку, повторяемую как parse-сбой; repair(invalid_or_None, last_reply)->str даёт инструкцию для повтора; on_error(reason, exc) ("no_provider"|"provider_error"|"parse_error"|"validation_error") — понять почему, не меняя контракт None
строгий рендер {var}: объявленные variables, KeyError при нехватке, поля атрибутов модели ({question.text}); подставляются только placeholder'ы-идентификаторы — литеральный JSON {"name": …} / {} в промпте остаётся как есть, без экранирования; .hash — стабильный sha256 шаблона
MessagesPrompt([(role, template), …])
рендерит чат-последовательность в list[Message]; .hash покрывает все строки
Каждый провайдер лениво создаёт httpx.AsyncClient и перепривязывает его к
текущему event loop, поэтому один экземпляр провайдера переживает повторные
asyncio.run(...) и per-test loop'ы (без RuntimeError: Event loop is closed);
aclose() закрывает клиент текущего loop. То же — для WebSource и
OTLP/Langfuse-приёмников.
эмбеддинги/TTS/STT для вендоров, чьи не-чат эндпоинты подтверждённо OpenAI-совместимы (см. providers)
Message, Role
одно сообщение чата; role — закрытый Literal + фабрики Message.system/user/assistant/tool
LLMRequest
одна генерация: messages + temperature/max_tokens — None = дефолт провайдера (вызов перекрывает провайдера, провайдер None = поле не отправляется)
LLMResponse, LLMResponseChunk
результат одной генерации / один поточный чанк, возвращаемые провайдером
*_from_env(**overrides)
подключение из .env; возвращает None, если не настроено
from_env(**overrides)
выбор в один вызов: сперва OPENROUTER_API_KEY, иначе OPENAI_BASE_URL, иначе None — тот самый двухветочный дефолт, что каждый пример вручную собирает в своём build_llm()
key/value чекпоинты под сессии (pg extra для Postgres) — async-native: файловый I/O уходит в отдельный поток, SQLite/Postgres держат одно постоянное соединение (WAL + busy_timeout у SQLite) под asyncio.Lock
внешние приёмники трейсов — OTLPTracer вендор-нейтральный (GenAI semconv, любой OTLP/HTTP-коллектор), LangfuseTracer заточен под Langfuse, Postgres поддерживает async чтение+запись; дашборд (create_trace_router) принимает любой TraceReader