Трасіроўка блог-канвеера на LangGraph з Langfuse: спаны, ацэнкі, папярэджанні
Інструментуем шматагентны канвеер LangGraph праз Langfuse: адна траса на артыкул, укладзеныя спаны, папярэджанні, ацэнкі і парадак адладкі слабых чарнавікоў.
Крыніцы: Building Multi-Agent System From Scratch — Part 6: Observability and Debugging
Сістэмы захоплення (перакантрольванне выхадных дадзенняў, перакантрольванне цітацый, ліміты павторных спроб, перадача на адповідальную адміністрацыю) дапамагаюць толькі тады, калі неудачны запуск можна пазней восстановіць. У такім случае блогавы пайплайн з калькольнікаямі агентамі выклікае множыць пытанняў: чы былі выхадныя даследжэння слабкімі? Чы пісар не зважаў на адзывы пра неабходнасць цітацый? Чы правы быў рэвізор, калі адмовіў? Чы час пошуку выйшаў за межы? Скількі коштаў запуск у відношэні да вызоў мадэляў і токенав?
Без можлівасці назірання сістэма ёсць чорныя скрынкі, абгорнутыя іншымі чорнымі скрынкамі. Тракароўка калькольнікаямі агентамі — гэта вельмі складная частка задачы. У данай налаадцы Langfuse дадаўся да пайплайна LangGraph для блога, што дазволяе ствараць адну траку на кожны запуск статыі, якая мае інформацыю пра перадачу заданняў, вызоў мадэляў і інструментаў, часы выканання, косты, адказы на памілкі і рэзультаты ацэнкі.
Выбіранне таго, што ўважаецца тракой
Перш чым працаваць з кодам, выберыце елемент, які трэба адзірнуць. У гэтым працэсе одна пазлічка выражае адзін спроба стварыць адзін артыкул. Ён пачынаецца, калі подаёцца тэма, і заканчваецца, калі працэс завершыўся, зупініўся чакаючы людзя або зазнаў неудачы. У ём уключаюцца адзіркі за кожным выкараным завадам:
blog-pipeline
├── research-agent
│ ├── search-web
│ └── research-model
├── writer-agent
│ └── writer-model
├── citation-check
│ └── citation-review-model
└── reviewer-agent
└── reviewer-model
Langfuse работае з трыма іменнікамі. Trace — цэх адначасовы запуск ад пачатку да канца. Observation — гэта адзін крок унутрь яго, які можа быць фрагментам (логікая прыемлівкі), запускам (выклік LLM) або запісам (мініятурны маркер). Session спаюе калькі trace-аў, напрыклад, усі спробы па аднам і там жа тэме блога.
Хіярархія адпавядае праблемам падчас дэбагавання: пачынаецца з адначасовага запуску, а потым далей разбіраецца конкрэтны агент, інструмент або запуск модэлю, які стаў прычыной проблемы.
Настройка Langfuse
Створыце праект Langfuse і задаце яго аутантыфікацыйныя даны праз зменныя сяродовышча. Секрэтны ключ ніколі не трэба пісаць у файлах коду.
pip install -U langfuse
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASE_URL="https://cloud.langfuse.com"
export LANGFUSE_TRACING_ENVIRONMENT="development"
langfuse пакет — гэта Python SDK. Два ключа аўтэнтыфікуюць дапрыгу пры працэўванні з праектам, а LANGFUSE_BASE_URL выбірае месца размешчэння. У прыкладзе выкарыстоўваецца хмарны рэгіон ЕС, таму за патрэбы можна заменіць яго на іншы рэгіон або самастойна размешчаную інстанцыю.
Атрыбут сяродовішча дапамагае раздзеляць следы з развіцця ад тых, што ствараюцца ў прыметнай сяродовішчы. Короткія, стабільныя назвы, такія як development, staging і production, добра падходзяць. Без іх експерыменты і рэальныя запускі статэйка падаюць у адну відзнаку, чым ускладняецца аптыварка.
Падключэнне працоўніка адпаведзення
Langfuse поставляе працоўнік адпаведзення LangChain. LangGraph будуецца на механізмах LangChain для запуску і адпаведзення, таму той самы працоўнік фіксуе падзеі, якія выкалываюцься з графамі, ланцоўкамі, інструментамі і мадэлямі:
from langfuse import get_client, propagate_attributes
from langfuse.langchain import CallbackHandler
langfuse = get_client()
langfuse_handler = CallbackHandler()
get_client() стварае кліента на адказе змяны сяродовішча. Адпрацоўвальнік павінен быць створаны аднойчы пад час запуску і вікорыстоўваны знову па кожным запуску. Ён слухае звычкі LangChain і перадае іх як спазырання, што усуняе патрэбу вручную сочыць за кожным вызывам модэлю.
Далей працюе propagate_attributes. Ён ставі тыя ж атрыбуты на кожнае спазырання па час адной працы, што дазволяе фільтруваць вызывы пошуку, генераванне тэкста і генераванне адгукнуць як частыны одной парадыгмы апрацоўкі статэйкі.
Вызыв графа абгортваецца ў корневым спане, пры чым адпрацоўвальнік перадаецца праз настройкі LangGraph:
def run_blog_pipeline(topic: str, blog_id: str, user_id: str):
initial_state = {
"topic": topic,
"audience": "developers new to agent systems",
"research_brief": "",
"sources": [],
"open_questions": [],
"article_draft": "",
"review_feedback": "",
"citation_issues": [],
"approved": False,
"revision_count": 0,
"status": "researching",
}
with langfuse.start_as_current_observation(
as_type="span",
name="blog-pipeline",
input={"topic": topic, "audience": initial_state["audience"]},
) as pipeline_span:
with propagate_attributes(
trace_name="blog-pipeline",
session_id=blog_id,
user_id=user_id,
tags=["blog-pipeline", "langgraph"],
version="1.0.0",
metadata={"workflow": "research-write-review"},
):
trace_id = langfuse.get_current_trace_id()
result = graph.invoke(
initial_state,
config={"callbacks": [langfuse_handler]},
)
pipeline_span.update(
output={
"status": result["status"],
"approved": result["approved"],
"revision_count": result["revision_count"],
}
)
return result, trace_id
Некалькі дэталей гэтага абгародніка маюць значэнне. Зовнішній вызов start_as_current_observation() стварае корневую абглядку, а ўсі параметры, якія ёй падаюць, залічваюцца спецыяльна кампактнымі — толькі тэма і аудыянс. Гэтае можна пазначыць за мгновенне ў спісе следаў, таму даныя пра внутрані стан не патрабуюцца там.
Функцыя propagate_attributes() дадае атрыбуты кожнай вкладзенай абглядцы. Значэнне blog_id служыць ідэнтыфікаторам сесіі, які агрупавае праказы чыстаўкі або падзеяванні, якія выконваюцца з адной статыей. Значэнне version відображае працэс выканання і момент публікацыі рэзультата, таму, калі змянюецца працэс, следы можна фільтраўваць па версіі, а не проста за здогадкамі.
Апрацоўшчык праходзіць праз стандартны config LangGraph і там самы аўтаматычна збірае дэталі на рэвэлю фрэймворку: кожны вызов вузла, генераванне LLM, вызов інструмента, вхідныя/выходныя даны, час адпаведзі, колькасць токенаў і ўскладнення.
У канцы корэнтны спан апдэйтуецца з трыма рэзультатамі на рэвэлі: чы гэты запуск быў завершан, чы праект быў апраўнан і скількі правак было выкарыстоўвана. Працэсаванны trace_id трэба зберагчыць у логах прыкладнення або ў інтэрфейсе, чым можна будзе безпосерэдньа перайсці ад запуску статыкі да яе трагу Langfuse.
Спаны на рэвэлі бізнес-рэвэля ў агентах
Аўтаматычны трэйсінг паказвае, што мадэль была вызвана. Ён не паказвае, што кожны спецыяліст перадаў наступнаму. Для гэтага трэба дадаць лёгкі спан унутры вузла агента і перадаць аргумент config у глыбокія вызовы LangChain або LangGraph, каб апрацоўшчык заставаўся ў тым жа кантэксце трагу:
from langchain_core.runnables import RunnableConfig
def research_node(state: BlogState, config: RunnableConfig) -> dict:
with langfuse.start_as_current_observation(
as_type="span",
name="research-agent",
input={"topic": state["topic"], "audience": state["audience"]},
) as span:
result = research_agent.invoke(
{"topic": state["topic"], "audience": state["audience"]},
config=config,
)
span.update(
output={
"source_count": len(result["sources"]),
"open_question_count": len(result["open_questions"]),
"brief": result["research_brief"],
}
)
return {
"research_brief": result["research_brief"],
"sources": result["sources"],
"open_questions": result["open_questions"],
"status": "writing",
}
Этот элемент дапамагае функцыі адпаведнага адпрацоўвання вызову і не заменяе яе. Ён дадае значэнне, якое фрэймворк не можа з’ясаваць, а самэй — што означае "доследжэнне завершылася успехам" для гэтага працэсу. Хутчэй ён фіксуе колькасць выхадных дасягненняў, колькасць нерашытых пытанняў і короткі апіс, які даесяць пісцачу.
Перадача параметра config: RunnableConfig у функцыю research_agent.invoke() таксама перадае функціі адпрацоўвання вызову і кантэкст трэйсінгу. Яго забывчына ёсць класычныяй памылкай у абэражнасці: верхніяй графік трэйсіруецца, але внутрашні вызывы паказваюцца як некалякія трэйсы або знікаюць.
Зберагаеце назвы элементаў абэражнасці стабільнымі: research-agent, writer-agent, citation-check. Тэму, назву мадэлі чы ўпынак карыстальніка не трэба включаць у назву, таму што гэтыя даныя належаць да вхідных дадзенняў, атрыбутаў мадэлі чы метададзей. Стабільныя назвы значаць, што панелі керування і фільтры застаюцца працэснае пасля змян у рэалізацыі.
Адвары і памылкі
Не кожны нестандартны рэзультат ўскладнюе працу. Сумнівны істочнік, выкараны бюджэт на рэвізію або непадтрымваная цітата таксама маюць значэнне і павінны выказвацца як адвар.
def record_source_assessment(assessment: SourceAssessment) -> None:
if assessment.suspicious_content:
langfuse.update_current_span(
level="WARNING",
status_message="Untrusted source contained agent-directed instructions.",
)
def record_pipeline_failure(error: Exception) -> None:
langfuse.update_current_span(
level="ERROR",
status_message=f"Pipeline failed: {type(error).__name__}",
)
Langfuse падтрымлівае рэжымы логаў для абсарвэйшанняў. WARNING прызначаны для тых запускаў, якія завершыліся, але трэбуюць перагляду, тады калі ERROR выкорыстоўваецца для справжніх абяканняў. Паведамленні пра статусе должны опісваць проблему з працай়ом, не копіюючы чутлівы тэкст адпаведна секрэты ў тэлеметрыю.
Запісчык ацэнкі выхаду належыць у шляхе пераканальнай перапачатковай перацэнкі, а запісчык абяканняў — у працоўніку з абяканнямі, які можа фактычна восстанавіць сітуацыю або адмаўіць пра яе. Ловля кожнай абяканняў толькі для ўлогавання яе — гэта пасляднія ідэя, адтолькі што нечаканыя абякання все раве должны прайсці да монітарынгу абяканняў і яскрава супрацаваць.
Оцэнка рэзультата
Трэйс паведамляе, што сталося, а рэйтынг — чы быў рэзультат годным. Калі запуск завершыцца, трэба прыўязаць першы дэтэрміністычны рэйтынг, выведзены ўзьміцца з стану канвэю:
result, trace_id = run_blog_pipeline(
topic="How AI agents use tools",
blog_id="blog-ai-tools-001",
user_id="philip",
)
if trace_id:
langfuse.create_score(
trace_id=trace_id,
name="review_approved",
value=1 if result["approved"] else 0,
data_type="BOOLEAN",
comment=result["status"],
)
langfuse.create_score(
trace_id=trace_id,
name="revision_count",
value=float(result["revision_count"]),
data_type="NUMERIC",
)
Перша оцінка відображае структуроване рышэння рэцензента шчодо схвалення чы відхілення, а друга — колькасць корэкцій, необхідніх для доробкі праекту. З часом яны даюць більшэ інформацыі, ніж просто окрэмі прыклады. Якая мадэль, версія запіту чы катэгорыя тэмі забезпечуюць меншэ корэкцій і більшэ схвалень?
Жодэн з цых показніків не ёсць абсолютною істыною, оскількі рэцензент можа помілятыся. Оцінка, вказана пасля прочытання остаточноі версіі, можа з’явітыся пізнішэ. Важліво фіксуваты всі зміні, шчоб можна було порывнюваты якісць і аналізуваты рэзультаты, а не покладатыся лішэ на кілька запам’ятовуваніх прыкладыв.
Процедура віправлення помілок
Калі артыкул віявляецца слабкою, перэгляньце іі історыю змін і поставце собі наступні запітання:
- Чы пройшов граф відповіднім шляхом? Перэвірце роздылі «Досліджэння», «Автор», «Перэвірка посілань» і «Рэцензент», а таксама будзь-які цыклі корэкцій.
Саме таму важна правільная структура логаў. Якшчы названыя дрэвы ператвараюць фразу „Блог быў некарэктны“ на точную заключэння, напрыклад „Авар перадаў твэрджэнне, якога не было ў анотацыі, пасля другага перагляду.“
Забэрэжыць чутлівыя даны
Функцыя адзінавання можа запісваць падказкі, рэзультаты выходу, рэзультаты працы інструментоў і метаданы, што таксама значыць, што яна можа зафіксаваць інформацыю, якая не павінна быць збераганая. Клучы API, заголовкі аўтэнтыфікацыі і всю таёмную настройку ніколі не трэба перадаваць у Langfuse. Калі даныя корыстніка прайдуць у канал пазней, пераканайцеся, што зафіксуецца тое, што трэба, і наладзіце маскаванне пры экспорце адзінавання. Langfuse прэдстаўляе функцыі маскавання, якія вычыраюць чутлівыя значэнні пры выходзе іх з прыемліка.
Золатая правіла: запісвайце столькі, сколькі трэба, каб поясніць рашэнне, і не больш, чым можна безпечна заставіць.
Што отрымае канал
Кожны запуск цяпер заставляе аперацыйныя запісы:
- Кожная спроба стварэння артыкула — гэта адзінае адзінаванне.
- Агенты, мадэлі і інструменты праказуюцца як вяроўжаныя назірання.
- Константныя назвы паўсюду з метаданамі, версіяй, сесіяй і сераўыскам дапамагаюць лёгка ўсе фільтруваць.
Дэбаггін больш не ўключае праскроллів логаў тэрміналу і згадкі, а стае адзіроўкай самэй моменту, калі ўсё зламалася.