Хопіць прымушаць агента кадавання перачытваць кодавую базу: патэрн LLM Wiki
Ідэя LLM Wiki ад Карпатага і openwiki ад LangChain: сырыя крыніцы, вікі для агентаў і ўказальнік у AGENTS.md, а таксама задачы абнаўлення, ацэнка і трасіроўка.
Крыніцы: Stop Making Your AI Agent Re-Read Your Entire Codebase Every Session
Большасць агентаў для кодавання маюць раздражаючую прыzwyczайнасць — кожна сесія пачынаецца з нуля: яны занова ачынаюць файлы, занова выявляюць, які модуль залежыць ад каго, і занову вучыцца, для чаго служыць кожная частка кодавой базы. Вы плаціце токенамі, а значыць і грошамі, за знанні, якія агент меў пяць хвілін раней, але выкінуў, калі закрылася вікна кантэксту.
Андрэй Карпаты опісаў гэтую проблему і запропанаваў спосаб яе рашэння, які назваў шаблонам LLM Wiki. LangChain выдала адкрытая версія гэтага рашэння пад назвай openwiki. Ніжэй шаблон і яго рэалізацыя рассматрываюцца паўза-паўза: што ўсё-такі ўскладніць, чаму гэта важліва і як працуе код у дэмавай прыкладнай програме. Рэзультатам ёсць ментальны модэль, які можна застосаваць да будзь-якай кодавой базы, якую трэба зрозумець.
Агенты з амнэзіяй
Паўначальна інфармацыя пра спосабы выканання задач у дакладных тэрмінах. Звычны адказ на запытанне "як агент разумее базу коду?" — гэта RAG. Файлы-выхаднікі падключаюцца, ставяцца ў векторную базу дадзэнняў, і пад час запытання запрашваюцца адпаведныя фрагменты.
RAG падходзіць для адпаведзення на запытанні. Але для агентаў, якія займаюцца програмаваннем, ён не падходзіць з трохоў прычын.
Разуменне коду перасчыляецца ў кожны раз. Падазроўваючы, што агенту патрэбна звязка між agent.py і memory.py, ён запрашвае фрагменты, мадэль знову шукае адпаведны адказ, а рэзультат зноў відкідаецца.
Запрашэнне дадзэнняў залежыць ад вектарных прэдставленняў. Калі той, хто напісаў код, вжываў іншы слоўнік ніж у запытанні агента, правыя фрагменты ніколі не будуць знайдзены. Вектарныя прэдставленняя ёсць апрыксіматычнымі, тады як архітэктура ёсць точной.
Ён стае некорыстным без жаднага звуку. Сховішча вектараў не ведаюць, што memory.py быў перэраблены ў пасляўтні втарок, і з аблікаваннем яго выдаюць старыя даны.
Ідэя Карпаты была простая: нехай сама мадэль пішае документацыю у фармаце, прызначаным для чытання мадэлямі, і нехай яна автаматычна застаецца актуальной. Гэта значыць документацыю для агентаў, а не для людзей.
Тры шары
Кожны элемент openwiki належыць да аднаго з трох шароў у дзяле Карпаты.
┌─────────────────────────────────────────────────┐
│ Layer 3: The Schema │
│ AGENTS.md / CLAUDE.md — "the wiki exists here" │
├─────────────────────────────────────────────────┤
│ Layer 2: The Wiki │
│ openwiki/*.md — LLM-generated structured docs │
├─────────────────────────────────────────────────┤
│ Layer 1: Raw Sources │
│ Your actual codebase — immutable to the wiki │
└─────────────────────────────────────────────────┘
Кожны слой знаходзіцца на тым, што знаходзіцца пад ям.
Слой 1: первыя джэрэла
Первыя джэрэла — гэта проста ваша база коду. З пункту зору вікі яны ўзламныя: OpenWiki чытае іх і ніколі іх не зміняе.
У дэмнаўскам праекце джэрэлы зберагаюцца ў каталозе sample_agent/ — гэта рэалістычны агент LangChain ReAct, які впішаецца ў уявленне большасці інжынераў з штучнага інтэлекту.
# sample_agent/agent.py
from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from .memory import build_memory
from .prompts import build_prompt
from .tools import TOOLS
def build_agent(
model: str = "openai/gpt-4o-mini",
base_url: str = "https://openrouter.ai/api/v1",
api_key: str | None = None,
temperature: float = 0.0,
) -> AgentExecutor:
llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=temperature)
prompt = build_prompt()
memory = build_memory()
agent = create_react_agent(llm=llm, tools=TOOLS, prompt=prompt)
return AgentExecutor(
agent=agent,
tools=TOOLS,
memory=memory,
verbose=True,
handle_parsing_errors=True,
max_iterations=10,
)
Чаты складаюцца з чатырох элементаў: кліента-модэля (ChatOpenAI), шаблона запіту, памяці для дыялога і інструментаў. Цэўсь тэксткнічны прыем ReAct — самэ гэты код і ўжоўсяць у OpenWiki. Інструменты спецыяльна залучаны простымі: імітаваны пошук і безпечны калькулятор:
# sample_agent/tools.py
@tool
def calculator(expression: str) -> str:
"""Evaluate a safe mathematical expression and return the result."""
allowed_names: dict = {}
try:
result = eval(expression, {"__builtins__": {}}, allowed_names)
return str(result)
except Exception as exc:
return f"Error evaluating '{expression}': {exc}"
Паглядзіце на {"__builtins__": {}}. Ён адзьёмае всі вбудованы элементы Python з прастору eval, ўнаследке чаго корыстнік не можа перадаць ўсё такое, як __import__('os').system('rm -rf /'), і ўрабіць яго запуск. Гэта незначны дэтал, але ён мае рэальныя наследкі для безпекі.
Што стосуецца шаблона, важна тое, што OpenWiki будзе чытаць кожны з гэтых файлоў, вырашаць, якія межы межу ўсія ўзаімна, і ствараць адну вікі-старонку, якая будзе все гэта поясняць, а не ў кожны раз, калі запускаецца агент.
Слой 2: сама вікі
Тут знаходзіцца центральная ідэя. Рукапісныя дакументы выцвараюцца, а RAG над сырым кодам занова вырахоўвае усё з кожным запітам, таму шаблон прымушвае мадэль ствараць структураваны markdown, прыстосаваны да спосабу, якім мадэлі прыймаюць кантэкст.
Як OpenWiki гэта стварае
У дэмавай прыемлі Streamlit кнопка «Generate Wiki» запускае гэта:
# app.py
def run_openwiki(
args: list[str],
provider: str,
api_key: str,
model: str,
langsmith: str,
cf_account_id: str = "",
) -> tuple[str, int]:
import sys
env = _build_openwiki_env(provider, api_key, model, langsmith, cf_account_id)
result = subprocess.run(
["openwiki", *args],
cwd=str(SAMPLE_AGENT_DIR),
env=env,
capture_output=True,
text=True,
timeout=300,
shell=sys.platform == "win32", # npm .cmd files need shell=True on Windows
)
return result.stdout + result.stderr, result.returncode
Важны два моменты.
Паўва, cwd=SAMPLE_AGENT_DIR — гэта свяжана з планамі. Командная лінія openwiki скануе той каталог, з якога запускаецца. Вызначэнне каталогу працы як sample_agent/ обмежвае скануванне толькі цім кодавым базам, а не всім праектам. Калі занедбаць гэты параметр, програма будзе скануваць з корневага каталогу праекта, чыяе рэзультат будзе включаць тэсты, настройкі CI і каталог node_modules.
Паўтора, shell=True у Windows. У глобальных пакетах npm там інсталююцца скрыпты-обгорткі .cmd. Калі вызваць subprocess.run(["openwiki", ...]), апраношчаець па бінарны файл пад назвай openwiki, якога не знаходзіць, і выклікаецца FileNotFoundError: [WinError 2]. Якшы выкорыстаць shell=sys.platform == "win32", вызов будзе пераданы праз cmd.exe, які правільна обрабоцоўвае файлы .cmd. Спачатку гэта можа заплутаць, але пасля — стане зрозумела.
Што значыць фарматаванне "agent-native"
Документацыя для людзей пішыта так, каб яе чыталі зверху вярху. Вы ачынаеце README.md, пачынаеце з верху і поступова з’ясоўваеце кантэкст. Агенты чытаюць не так. Яны шукаюць у заглав’ях стосунакіяныя раздзелы, выкарыстоўваюць тамтэйную інфармацыю і пераходзяць да наступнага.
Документацыя у стылі "agent-native" ствараецца са уважнам да такога спосабу працы:
- Інтэнсівныя загаловкі на кожным рэвэлі, тады структуру можна аналізаваць без чытання прозы.
- Чысткія перакіранні, такія як
See: patterns.md#memory-strategy, якія агент выконвае механічна, а не на інтуіцыі. - Апৱатыяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяя
# Architecture — sample_agent
## Overview
ReAct agent built on LangChain AgentExecutor. Entry point: agent.py.
Four components: LLM (ChatOpenAI via OpenRouter), tools, memory, prompt.
## Components
- agent.py - AgentExecutor wiring. See: components.md#agent
- tools.py - search (simulated) + calculator (sandboxed eval). See: components.md#tools
- memory.py - ConversationBufferWindowMemory, k=5 turns
- prompts.py - ReAct ChatPromptTemplate with agent_scratchpad placeholder
- evaluator.py - LLM-as-judge chain. See: patterns.md#evaluation
## Data Flow
question → AgentExecutor.invoke() → ReAct loop (max 10 iter)
→ tool calls → final answer → evaluator.evaluate() (optional)
Застаўце гэта па боку ад стандартнага чалавечага README-а, які складаецца з трохі параграфаў з апісанням сітуаціі, прычым перш чым показуецца ўсё конкрэтнае. Агентам не патрэбна інформацыя пра сітуацію — ім патрэбны факты.
Слой 3: схема, якая замыкае цыкл
Стварэнне вікі — гэта палова працы. Іншая палова — гэта перакананне, што кодавальны агент ведае пра існаванне вікі.
Гэтая роля належыць файлам AGENTS.md і CLAUDE.md — файлам з інструкцыямі, якія Claude Code, Cursor, GitHub Copilot і большасць сучасных кодавальных агентоў чытаюць на пачатку сесіі. OpenWiki вставляе короткія рэферэнсы ў той з гэтых файлаў, які вікорыстоўвае ваш праект. Да гэтага:
# sample_agent/AGENTS.md (before OpenWiki)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
- tools.py - available tools (search, calculator)
- memory.py - conversation memory strategy
- prompts.py - system prompt and ReAct template
- evaluator.py - LLM-as-judge evaluation chain
І пасля запуску openwiki --init:
# sample_agent/AGENTS.md (after OpenWiki injection)
This repository contains a LangChain ReAct agent.
## Architecture
- agent.py - AgentExecutor entry point
...
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
Key files:
- `openwiki/architecture.md` - overall structure and file responsibilities
- `openwiki/patterns.md` - coding patterns and conventions
- `openwiki/components.md` - detailed component documentation
The wiki is auto-updated daily. Always prefer wiki context over re-reading source files.
Ін’екцыя ўмышленна короткая. Якбы весь вікі быў пакладзены ў файл з інструкцыямі, гэта спрачынала б перапоўнення вікна кантэксту на пачатку кожнай сесіі у велікай базе коду. Дастатнек адносу. Агент чытае яго, ачынае openwiki/architecture.md толькі тады, калі патрэбны даныя кантэксту базы коду, і дзеяўна вывучае неабходную інфармацыю, не чапаючы жаднага файла з выканаю.
Чэтырты карточкі дэманстрацыі паказвае стан да і пасля, імітуючы працэс ін’екцыі, ўбачлівы для розумення механізму:
# app.py — Tab 4
agents_md_content = AGENTS_MD.read_text(encoding="utf-8")
openwiki_injection = """
## OpenWiki Context
When searching for codebase context, check `openwiki/` first.
...
"""
col_before, col_after = st.columns(2)
with col_before:
st.code(agents_md_content, language="markdown")
with col_after:
st.code(agents_md_content + openwiki_injection, language="markdown")
Выбір прадаўца модэляў
OpenWiki не піклуецца пра тое, які LLM знаходзіцца за ягом. Ён выкарыстоўвае ChatOpenAI з LangChain, які чытае OPENAI_BASE_URL для спецыяльнага канцэнтра, таму падходзіць будзь-яны прадаўцы, сумесны з OpenAI. Дэманстрацыя падтрымляе два такіх прадаўцы:
# app.py
def _build_openwiki_env(
provider: str,
api_key: str,
model: str,
langsmith: str,
cf_account_id: str = "",
) -> dict:
env = {**os.environ, "OPENWIKI_MODEL_ID": model}
if provider == "Cloudflare Workers AI" and cf_account_id:
cf_base = f"https://api.cloudflare.com/client/v4/accounts/{cf_account_id}/ai/v1"
env["OPENAI_API_KEY"] = api_key
env["OPENAI_BASE_URL"] = cf_base # LangChain reads this automatically
else:
env["OPENROUTER_API_KEY"] = api_key # openwiki's native key name
return env
OpenRouter — гэта просты варыянт. Адна клавіша дазволяе запускаць большасць популярных модэляў праз адну тэрмінал-точку, а заданне OPENWIKI_MODEL_ID=openai/gpt-4o-mini завершае настройкі.
Cloudflare Workers AI аддае магчымасць выкарыстоўваць модэлі з перымэтра. Модэль GLM ад ZhipuAI (@cf/zai-org/glm-5.2) працюе на сэрвісах самай Cloudflare, таму час адпаведзення зменшыцца, калі вы пераводзіце працэс у працэйны рэжым і хочаце выкарыстоўваць толькі аднаго прадаўца хмарных сервісаў. Паколькі Cloudflare адкрывае тэрмінал-точку, сумелуючую з OpenAI, пад адрэсам /ai/v1, не патрэбны якія-небудзь спецыяльныя кліенты. Направіце OPENAI_BASE_URL на яе, і ужо наяўная інтэграцыя з LangChain адрабоць усё рэшта.
Падтрымка актуальнасці вікі
Кожная сістэма документацыі зрэшты трапляеся ў тых сабе проблемах: люды забываюць яе апдэйтаваць, калі змініцца код.
Карпаты сказаў, што гэта не проблема дысцыпліны, а проблема эканамікі. Косты тыпаваркі растуць з розмерам кодавой базы, тады як чалавек, які яе ведае, не атрымвае негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе негараздзе
# .github/workflows/openwiki-update.yml
name: OpenWiki Update
on:
schedule:
- cron: "0 8 * * *" # 08:00 UTC daily
workflow_dispatch:
jobs:
update-wiki:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full git history so openwiki can read diffs
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm install -g openwiki
- name: Update wiki
working-directory: sample_agent # scope to our codebase
run: openwiki --update --print
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENWIKI_MODEL_ID: openai/gpt-4o-mini
- uses: peter-evans/create-pull-request@v7
with:
commit-message: "docs: update openwiki pages"
title: "OpenWiki: daily documentation update"
branch: openwiki/auto-update
Зверніце увагу на working-directory: sample_agent — гэта і ж самая настройка, якая вікорыстоўваецца пад час вызову Python subprocess. Незалежна ад таго, дзе вы яго розмішчаеце, запускайце --update з корню кодбазы, а не з корню праекту.
Створэнне pull request — гэта правільны падход. Автаматычная перадача змян у галоўную версію дазволіла б змяніць документацыю без перагляду, тады як PR дае можлівасць пераглянуць разлік змян і адхіліць некальканосны выхід мадэлі. Пятыя вкладка дэманстрацыі імітуюць працэс апдэйту локальна:
# app.py — Tab 5
if st.button("🔄 Run openwiki --update", type="primary"):
with st.spinner("Checking for changes and updating wiki…"):
output, returncode = run_openwiki(
["--update", "--print"],
provider,
api_key if provider == "Cloudflare Workers AI" else openrouter_key,
model_id,
langsmith_key,
cf_account_id,
)
st.code(output, language="bash")
Ацэнавальнік у фармаце LLM-судды
Разам з агентам выкорыстоўваецца ацэнавальнік у фармаце LLM-судды — тэхніка, якая заслуговае на адзінэчны аналіз.
# sample_agent/evaluator.py
def evaluate(
question: str,
response: str,
model: str = "openai/gpt-4o-mini",
base_url: str = "https://openrouter.ai/api/v1",
api_key: str | None = None,
) -> dict[str, int | str]:
llm = ChatOpenAI(model=model, base_url=base_url, api_key=api_key, temperature=0)
chain = _JUDGE_PROMPT | llm
result = chain.invoke({"question": question, "response": response})
try:
return json.loads(result.content)
except json.JSONDecodeError as exc:
raise ValueError(
f"LLM returned non-JSON response: {result.content!r}"
) from exc
temperature=0 тут не ўзначальваецца як неабяжнае. Суддзя павінен быць дэтэрміністычным, ўсёлякія ўмовныя факторы не павінны вплываць на рашэння, тады тая ж самая запытка і адпаведзь завжды будуць атрыбуаваны той самы бал.
json.loads абгорнуты ў try/except JSONDecodeError таму, што мадэлі іноды размешчаюць свой JSON у рамках коду markdown, дадаюць паўвступныя рэчы або заканчваюць адпаведзь занадта дзялёга. Явны абэкт абэкцыі з result.content!r у паведамленні паказвае точна, што вернула мадэль, што спрыяе шырокаму дыбагаванню.
Прампт суддзі атрыбуе балы за точнасць, корыстнасць і яснасць окрэма, адколі гэтыя характэрыстыкі перацэнююцца незалежна. Адпаведзь можа быць правільной, але не адпавядаць на справжню запытку, або можа паказаць правы направлення, але заплутаць чытача. Раздзелэнне ўсіх гэтых характэрыстык дазволяе атрыбуаваць конкрэтныя балы, на якія можна адразу вярнуцца.
Аналіз унутраніх дадзеных за дапамою LangSmith
Установіце LANGSMITH_API_KEY, і кожны крок, які выканае агент openwiki, будзе фіксаваць: якія файлы ён чытае, запыт, які ён адправіў, што вернуў мадэль і сколькі часу трывала кожная вызова.
# app.py
def _build_openwiki_env(...) -> dict:
...
if langsmith:
env["LANGSMITH_API_KEY"] = langsmith
env["LANGCHAIN_TRACING_V2"] = "true"
env["LANGCHAIN_PROJECT"] = "openwiki-demo"
return env
Для гэтага патрэбны толькі тры зменныя сяродовышча. LangChain сама ўсё гэта перыяктуе без неабяжных змян у кодзе openwiki чы праца. Гэта правілле для праектаў LangChain: відстэйкаванне праблемаў праз сяродовышча, а не праз код, тады можна увімкнуць відстэйкаванне ў працоўным рэжыме без змян у самай аплікацыі.
Відстэйкаванне таксама паказвае калькуючую інфармацыю. Выклік --init — гэта не адзін выклік LLM. Гэта цыкл праца, які включае выклікі для планавання, чытання файлаў і запісу ў wiki, і відстэйкаванне паказвае колькісць токенаў і час адпаведных операцый. Так можна адпавесці на пытанне "чаму --init займае 90 секунд": проста адкрываеце відстэйкаванне і бачыце тры выклікі модэлю по 30 секунд кожны, якія выкананы адна за іншай.
Полны працэс
Як гэта работае разам:
Developer commits code change
↓
GitHub Action runs openwiki --update at 08:00 UTC
↓
CLI reads git diff → only changed files
↓
LLM regenerates affected wiki pages
↓
PR opened with updated openwiki/*.md
↓
Developer merges PR
↓
Agent starts next session → reads AGENTS.md → sees wiki reference
↓
Agent fetches openwiki/architecture.md when it needs codebase context
↓
Agent makes informed change without reading source files
Вікі постаўляецца з часам. Шысць месцаў ўлікаваеінняя ўлікаваеінняя ўлікаваеінняя openwiki/patterns.md мае больш ніж сучасную структуру: ён таксама фіксуе, як эвалююцый кодбаза, уклучаючы шаблоны, якія з’яўіліся падчас рэфакторавання, і прычыны стварэння правілаў. Новы агент або новы працавнік магчымаецца абавесці ўсё гэта адразу.
Праўая інсайт
У сутнасці шаблон не стосуецца дакументацыі. Гэта пытанне пра тое, хто несе выклікі з разумеў кодбаза.
Пад модэлем RAG кожная сесыя агента купуе: паўтарнае выведзенне асоцыяцый, паўтарнае выучэнне правілаў, паўтарнае чытанне файлоў, якія ён чытаў раней. Выклікі зростаюць з тым, як часта адбываюцца сесыі.
У модэлі вікі выклікі плаціюцца адна раз у час кодзовых змян, праз запланаваны задачы, якія працуюць, калі ніхто не чакае, за частку цэнты за стораніцу, і тое, што ён стварае, продовжвае здобываць цэннась.
Модэлі не вярожуць у падтрыманні вікіў у актуальным стане. Канец часу не прымусвае іх адкладаць документаванне, і яны ніколі не залішаюць запіску „TODO: документавацыя пазней“ і не забываюць пра яе. Самэ гэта нявярнасць і ёсць сутністым пунктам.
Ёсць можласць спробаваць: запынцуйце Node.js 18 або новейшую версію для CLI-інструмента openwiki і атрымайце ключ API ад OpenRouter або Cloudflare Workers AI.
Спадні матэрыялы
- Самапраўцавальныя агенты за дапамогою навыкаў: паўтарна аднарожнае выкарыстанне, выбіранне, карэкцыя і перакананне — чатырычастыя прынцыпы дызайну агентаў, якія праграмаваны практычваць своі навыкі: зберагчыкаць працюючы код, удосконаліць апісанні, запропанаваць пераглянутыя карэкцыі і пераканацца ў рэзультатах перш чым перадаць ўваў.