Skip to content

reactifact

Аудируемые агенты для Python. Стройте knowledge-ассистентов по докам, таблицам и вебу — там, где путь к ответу зависит от вопроса и графом не ложится. Каждый ответ вычисляется, версионируется и воспроизводим — а не строка, которой приходится верить.

Это: Python-библиотека (3 core-зависимости) · один процесс · типизированные версионируемые артефакты с провенансом · детерминированный replay · работает офлайн, без API-ключа Это не: managed-платформа · распределённая очередь задач · готовый маркетплейс агентов/инструментов

Вопрос вроде «почему в Q2 выросли инфраструктурные расходы?» требует таблицы, политики и страницы из веба — а следующий вопрос требует уже другого набора. Это не один граф, который можно нарисовать заранее. Вы объявляете, какие артефакты существуют и что агенты умеют с ними делать; порядок — и граф зависимостей — рантайм выводит из состояния; рисовать граф не нужно. Каждый производный артефакт затем ссылается на то, что его создало (Answer —supported_by→ Evidence —extracted_from→ Doc), поэтому «почему он так сказал?» — это запрос, а не догадка, а запуск воспроизводим (context_hash) и проверяем (reactifact replay --verify).

Если вы знаете Celery… — модель программирования, а не его распределённый рантайм Python-разработчик уже знает эту модель по Celery: объяви **задачу**, скажи, что её запускает, — рантайм сам её выполнит. `reactifact` применяет это к агентам: задача реагирует на появление **типизированного версионируемого артефакта** в контексте, а не на сообщение в очереди и не на ребро графа. | Celery | reactifact | | --- | --- | | задача (task) | `Step` / `@step(produces=Model, consumes=Input)` — единица работы, пишущая артефакт | | `delay()` / `apply_async()` | вызывать не нужно: создание входного артефакта **и есть** триггер | | routing key / очередь | `Consume(Type)` — какой тип артефакта будит задачу | | chain / group / chord | несколько `consumes` / `produces`; порядок рантайм выводит сам | | result backend | `Context` — типизированные версионируемые артефакты | | worker | `Runtime` | **Не наследуется:** брокер, пул воркеров, `acks_late`/redelivery, cross-process exactly-once — *модель* Celery-образная, деплой — нет.

Сверх этого: проверяемый ответ

examples/fintech_audit — CSV транзакций, CSV бюджета и документ политики, без API-ключа. Число никогда не считает модель; это делает обычный Python, а ответ связан со своими доказательствами:

variance vs budget: +12.5%   ($45,000 факт vs $40,000 бюджет, порог 10% → превышен)
answer sha256 5461290d…  ·  context sha256 24449f6f…

повторный прогон даёт тот же хеш — или проверьте сохранённый запуск:
  reactifact replay <store> --session <id> --verify 24449f6f…

fintech_audit demo: вариация, ответ и воспроизводимый context hash — повторный прогон печатает тот же хеш.

Как это работает

                                   EVENT (created/updated)
                                          │
                                          ▼
CONTEXT ───────► ARTIFACTS ──► AGENTS REACT ──effects──► EFFECTS
   ▲                 │                                            │
   │                 │                                            │  compile
   └────── CONTEXT' ◄────────────────┘ PATCH ◄────────────────────┘

Вы описываете какие данные существуют, какие артефакты есть и что агенты могут с ними делать. Остальное делает runtime.

Ментальная модель

Традиционный агент reactifact
Программа идёт по графу/плану Агенты реагируют на изменения состояния
Сообщения — строки Типизированные артефакты (Claim, Evidence, Answer, …)
Оркестрация явная Оркестрация выводится из состояния
Компонент возвращает результат Step пишет effects (effects) — компилирует runtime
Ретраи/откаты вручную Контекст версионируется (коммиты как в git, diff, rollback)
«Кто это произвёл?» теряется Провенанс связывает каждый производный артефакт с его входами

Почему effects вместо «вернуть изменение»?

В центре цикла — то, как шаг вносит изменение. Многие фреймворки просят единицу работы вернуть результат, а какой-то оркестратор применяет его. reactifact переворачивает авторство: шаг формулирует, что должно измениться, через effects (create / update / link / ask); runtime компилирует набор эффектов в один атомарный патч — либо применяется весь шаг, либо ничего.

async def run(self, doc: Document, turn: Turn, effects: Effects) -> Answer:
    evidence = effects.create(Evidence(...), id="evidence:q1")
    answer = effects.create(Answer(...), id="answer:q1")
    evidence.link("extracted_from", doc)
    answer.link("supported_by", evidence)
    effects.update(turn, status="answered")
    return answer

Поскольку handle — это объекты, а не id, одно выражение может ссылаться на артефакт, созданный другим. А так как компиляцией владеет runtime, ручной сборки Patch не бывает. Участие человека — просто ещё один эффект (effects.ask(...)). Подробнее — в Почему reactifact и в контракте шага.

Почему артефакты вместо сообщений?

Сообщения непрозрачны; артефакты инспектируемы. Объект Evidence знает свой текст, источник и оценку. Благодаря провенансу runtime отвечает на вопрос «почему агент так сказал?», проходя по связям Answer —supported_by→ Claim —derived_from→ Evidence —extracted_from→ Doc.

Почему версионируемый контекст?

Каждый запуск — это коммит:

  • Diff — что именно изменилось между двумя ходами.
  • Rollback — отменить неудачный шаг и перезапустить с чистого состояния.
  • Детерминированные повторы — та же история даёт тот же результат.
  • Инспектируемость — полная, запрашиваемая история всего, что произошло.

Требования

  • Python 3.11–3.14 (.venv управляется через uv; CI гоняет весь набор тестов на всех четырёх).
  • pydantic для моделей артефактов; FastAPI/uvicorn — только для web-примеров.

Быстрый старт

Два агента, и между ними не объявлено ни одной связи в графе — второй реагирует потому, что появился результат первого, а ответ несёт доказательство того, откуда он взялся:

from pydantic import BaseModel

from reactifact import Budget, Context, Runtime, RuntimeResources, create_agent, step


class Question(BaseModel):
    text: str


class Evidence(BaseModel):
    text: str


class Answer(BaseModel):
    text: str


DOCS = {
    "refund": "Возврат возможен в течение 14 дней с момента покупки.",
    "pricing": "Тариф Pro — $49/месяц при годовой оплате.",
}


@step(produces=Evidence, consumes=Question)
async def find_evidence(call):
    question = call.trigger
    hit = next((v for k, v in DOCS.items() if k in question.data.text.lower()), None)
    if hit is not None:
        call.effects.create(Evidence(text=hit))


@step(produces=Answer, consumes=Evidence)
async def answer_from_evidence(call):
    evidence = call.trigger
    call.effects.create(Answer(text=evidence.data.text)).link("supported_by", evidence)


search_agent = create_agent("search", steps=[find_evidence])
answer_agent = create_agent("answer", steps=[answer_from_evidence])

ctx = Context(resources=RuntimeResources())
runtime = Runtime(ctx, agents=[search_agent, answer_agent], budget=Budget(max_runs=10))

ctx.create(Question(text="какая у вас политика возврата?"))
runtime.run()  # оба агента реагируют сами — никто их не связывал вручную

answer = ctx.latest(Answer)
evidence = ctx.related(answer.id, "supported_by")[0]
print(answer.data.text)                     # "Возврат возможен в течение 14 дней с момента покупки."
print("supported_by:", evidence.data.text)  # провенанс, который можно проследить, а не строка в логе

Это весь цикл: создан артефакт → агенты реагируют → применён патч → версия контекста продвинулась. Всё остальное в этой документации надстраивается над этим циклом.

Хотите чего-то ближе к реальному приложению? Quickstart — три рабочих, проверенных сниппета: tool-calling агент, поиск по своим документам, чат-бот с сохраняемыми сессиями — каждый со ссылкой на полный пример, из которого он урезан.

Куда дальше

Понять идею

  • Почему reactifact — дизайн-аргумент: почему effects, почему без графа, почему детерминизм, почему версионируемое состояние.
  • Сравнение — reactifact vs LangGraph/CrewAI по пунктам, и когда reactifact не стоит использовать.
  • Concepts — Context, Artifact, Patch, Step, Agent.

Строить на этом

  • Quickstart — три рабочих сниппета: tool-calling агент, поиск по своим документам, чат-бот с сохраняемыми сессиями.
  • Sources — откуда агенты берут информацию.
  • Providers — как подключить LLM/эмбеддер/провайдер изображений/речи в RuntimeResources.
  • Recipes — готовые search fan-out, материализация референсов, машины состояний жизненного цикла.
  • Patterns — переиспользуемые паттерны (reflection, map-reduce, supervisor, …), каждый — на конкретном примере.

Эксплуатировать

  • Observability — трейс каждого запуска: агентские спаны, чтения/записи, LLM-вызовы; офлайн-дашборд или экспорт в Langfuse/Postgres; коррелированные логи (configure_logging()) несут run_id/session_id/ agent в каждой строке.
  • Устойчивость и resume — прогон на сессии переживает рестарт процесса; что значит «at-least-once» и как сделать produce идемпотентным; дедлайны и graceful shutdown (Budget.max_seconds — жёсткий, request_stop()/ashutdown(), active_runs() для readiness).
  • Диагностика проблем — «агент не запустился» / «запустился дважды» / «run остановился раньше времени» — по симптомам, не по фичам.
  • Evaluation — многоуровневая оценка финального Context (качество evidence, provenance grounding, корректность вычислений, …).
  • Branching & merge — форк состояния, исследование альтернатив, трёхстороннее слияние с явными конфликтами.
  • Replay — детерминированная реконструкция «почему агент ответил именно так», без повторного запуска агентов.
  • Visualization & CLI — Mermaid-диаграммы графа артефактов и трейса запуска; CLI-утилиты reactifact.
  • API reference — все верхнеуровневые символы, по строке на каждый.

Посмотреть в деле

  • Examples — двадцать три работающих приложения, которые можно запустить.
  • Port matrix — какой классический паттерн LangGraph/CrewAI/DSPy соответствует какому примеру.
  • Design notes — более глубокое обоснование адаптивного планировщика; модель компиляции effects → Patch описана в English design note (пока не переведена — это архивный лог обсуждения, актуальное поведение уже в effects.md).