Узнайте, как Rust-акселераторы ускоряют LangGraph до 737x на сериализации состояний. Замените deepcopy на drop-in компоненты без изменения кода.
Если вы запускали LangGraph-агента достаточно долго, то заметили: узким местом становится не вызов модели, а инфраструктура. Каждый шаг графа сериализует состояние в чекпоинт для возможности восстановления. LangGraph использует Python-функцию deepcopy. Для маленького словаря это нормально, но для состояния агента в 250 КБ с вложенными сообщениями, выводами инструментов и накопленным контекстом deepcopy работает мучительно медленно — и вы платите за это на каждом шаге.
Я создал fast-langgraph: набор Rust-акселераторов для горячих путей LangGraph, оформленных как заменяемые компоненты с полной совместимостью API.
Вот что дают Rust-пути в сравнении с Python-эквивалентами:
Автоматический режим, не требующий изменений кода, даёт около 2.8x для типичного вызова.
Честно: это не история «Rust быстрее во всём». Ускорение чекпоинтов зависит от размера состояния. Для маленького плоского словаря Python-словарь реализован на C и уже быстр. Rust там не выигрывает — и в README это честно указано. 737x — это число для большого сложного состояния, а не заголовок для игрушечного графа.
LangGraph хорош. Я не хотел форкать или заменять его. Я хотел заменить три операции, которые доминируют в реальной нагрузке:
Всё остальное остаётся на Python. Rust-код скрыт за совместимым интерфейсом.
Самый простой способ — один флаг или один вызов функции, и ваш существующий граф работает быстрее без изменения кода:
# В начале вашего приложения
import fast_langgraph
fast_langgraph.shim.patch_langgraph()
# Остальной код без изменений — работает в 2-3 раза быстрее
from langgraph.graph import StateGraph
# ...Или установите переменную окружения FAST_LANGGRAPH_AUTO_PATCH=1 — это мой предпочтительный путь в продакшне, так как не требует изменения кода.
Для наибольшего ускорения используйте компоненты напрямую. Чекпоинтер — замена «в лоб»:
from fast_langgraph import RustSQLiteCheckpointer
# В 5-6 раз быстрее стандартного чекпоинтера
checkpointer = RustSQLiteCheckpointer("state.db")
graph = graph.compile(checkpointer=checkpointer)Кеш LLM — это декоратор, который сообщает свой процент попаданий, чтобы вы могли доказать выигрыш:
from fast_langgraph import cached
@cached(max_size=1000)
def call_llm(prompt):
return llm.invoke(prompt)
# Первый вызов: обращение к API (~500 мс)
response = call_llm("Что такое LangGraph?")
# Второй идентичный вызов: возврат из кеша (~0.01 мс)
response = call_llm("Что такое LangGraph?")
print(call_llm.cache_stats()) # {'hits': 1, 'misses': 1, 'size': 1}Этот кеш даёт ускорение в 10 раз, но только при высоком проценте попаданий. Если все промпты уникальны, кеш бесполезен. Это оптимизация для RAG и повторных запросов, а не магия.
Главное ограничение дизайна: никто не будет переписывать своего агента ради ускорителя. Поэтому всё построено как совместимые прослойки над Rust-реализациями.
Прослойка патчит горячие функции LangGraph во время импорта. apply_writes, пакетное обновление каналов, получает Rust-версию. Путь кеширования исполнителя переиспользует ThreadPoolExecutor между вызовами вместо пересоздания. Ни одно из этих изменений не даёт огромного выигрыша по отдельности (1.2x и 2.3x соответственно), но в сумме они дают те самые ~2.8x в автоматическом режиме, потому что находятся на пути каждого вызова.
Ручные компоненты — это где живут большие числа. RustSQLiteCheckpointer заменяет путь сериализации и сохранения. langgraph_state_update выполняет слияние состояния на Rust с явными ключами добавления:
from fast_langgraph import langgraph_state_update
new_state = langgraph_state_update(
current_state,
{"messages": [new_message]},
append_keys=["messages"]
)Этот путь слияния даёт ускорение в 13–46 раз для длинных графов, которые сотни раз добавляют сообщения.
Также есть профайлер, чтобы вы могли найти своё узкое место до того, как что-то портировать:
from fast_langgraph.profiler import GraphProfiler
profiler = GraphProfiler()
with profiler.profile_run():
result = graph.invoke(input_data)
profiler.print_report()Сначала профилируйте. Выигрыш — в конкретных горячих путях, а не «во всём коде».
Лучше честно скажу, где это проигрывает, чтобы вы не обнаружили проблему в продакшне.
Если ваши агенты короткие и дешёвые, это не для вас. Если вы запускаете длинные, с сохранением состояния и с чекпоинтами в продакшне — сериализация — это то, куда уходит ваше время.
Узкое место в длинном запуске агента — обычно инфраструктура, а не модель. deepcopy на большом состоянии — реальный налог, который вы платите на каждом шаге.
Большие ускорения здесь — история о данных и сериализации, а не о языке. 737x — это число для состояния в 250 КБ, и оно масштабируется вниз с размером состояния.
Измеряйте маленькие случаи. Rust проигрывает C-реализациям Python-словарей на плоских состояниях, и притворяться обратным — значит попасть в ловушку.
Сделайте замену «в лоб» с автоматическим откатом, иначе она никогда не будет принята. Один импорт или одна переменная окружения — вот и всё внедрение.
Работает с любой версией LangGraph, Python 3.9+. Код, полный бенчмарк и заметки по архитектуре: https://github.com/neul-labs/fast-langgraph
Если вы запускаете агентов LangGraph с сохранением состояния в большом объёме, мне было бы интересно узнать, насколько большим становится ваше состояние чекпоинта. Запустите профайлер, расскажите о своём горячем пути. Пробуйте, issues приветствуются.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →