Преминаване към основното съдържание
OpenAI

23 януари 2026 г.

Инженерство

Разгръщане на цикъла на агента с Codex

От Майкъл Болин, член на техническия екип

Зареждане…

Codex CLI(отваря се в нов прозорец) е нашият междуплатформен локален софтуерен агент, създаден да извършва висококачествени и надеждни софтуерни промени, като работи безопасно и ефективно на Вашето устройство. Научихме изключително много за това как да изградим софтуерен агент от световна класа откакто за първи път стартирахме CLI през април. Ще разгледаме тези изводи в тази публикация, която е първата от продължителна поредица, разглеждаща различни аспекти на работата на Codex, както и трудно извоювани уроци. (За още по-подробен поглед върху това как е изграден Codex CLI, посетете нашето хранилище с отворен код на https://github.com/openai/codex(отваря се в нов прозорец). Много от по-дребните подробности за нашите решения относно дизайна са документирани относно инциденти и искания за предложения в GitHub, ако желаете да научите повече.)

Като начало ще се фокусираме върху агентен цикъл, който е основната логика в Codex CLI, отговорна за оркестрирането на взаимодействието между потребителя, модела и инструментите, които моделът извиква, за да извършва смислена софтуерна работа. Надяваме се, че тази публикация Ви дава добра представа за ролята, която нашият агент, или harness („поводите“) играе при използването на големи езикови модели.

Преди да започнем, кратка бележка относно терминологията. В OpenAI „Codex“ обхваща набор от предложения за софтуерни агенти, включително Codex CLI, Codex Cloud и разширението Codex за VS Code. Тази публикация се фокусира върху Codex harness, който предоставя основния цикъл на агента и логиката за изпълнение, които са в основата на всички изживявания с Codex и се предоставят чрез Codex CLI. За удобство тук ще използваме термините „Codex“ и „Codex CLI“ взаимозаменяемо.

Цикълът на агента

В основата на всеки интелигентен агент е нещо, наречено „цикъл на агента“. Опростена илюстрация на цикъла на агента изглежда по следния начин:

Диаграма, озаглавена „Цикъл на агента“, илюстрираща как система с ИИ обработва потребителска заявка, заявява инструменти, наблюдава резултатите, актуализира плана си и връща изходни данни. Стрелките свързват стъпки като потребителско въвеждане, структурирано анализиране на модела, действия на инструменти и краен отговор.

За да започне, агентът взема входни данни от потребителя, за да ги включи в набора от текстови инструкции, които подготвя за модела, известен като подкана.

Следващата стъпка е да направите заявка към модела, като му изпратите нашите указания и го помолите да генерира отговор – процес, известен като инференция. По време на инференция текстовата подкана първо се преобразува в последователност от входни токени(отваря се в нов прозорец) – цели числа, които служат като индекси в речника на модела. След това тези токени се използват за изпробване на модела, като се генерира нова последователност от изходни токени.

Изходните токени се превеждат обратно в текст, който е отговорът на модела. Тъй като токените се генерират постепенно, преводът може да се извършва, докато моделът работи, което е причината много приложения, базирани на големи езикови модели, да показват поточни изходни данни. На практика, извеждането обикновено е капсулирано зад ППИ (API), който работи с текст, като скрива детайлите на токенизацията.

В резултат на стъпката на инференция моделът или (1) създава окончателен отговор на първоначалните входни данни на потребителя, или (2) прави заявка за инструмент, която агентът се очаква да изпълни (напр. „изпълнете ls и докладвайте изходни те данни“). Във втория случай агентът изпълнява заявката за инструмента и добавя изходните му данни към оригиналната подкана. Тези изходни данни се използват за генериране на нови входни данни, които се използват за повторно запитване към модела. След това агентът може да вземе предвид тази нова информация и да опита отново.

Този процес се повтаря, докато моделът не спре да прави заявки за инструменти и вместо това не създаде съобщение за потребителя (наричано съобщение на асистента в моделите на OpenAI). В много случаи това съобщение директно отговаря на първоначалната заявка на потребителя, но може също така да бъде последващ въпрос към потребителя.

Тъй като агентът може да изпълнява заявки за инструменти, които променят локалната среда, неговият „резултат“ не се ограничава само до съобщението на асистента. В много случаи основният резултат от работата на софтуерен агент е кодът, който той пише или редактира на вашата машина. Въпреки това, всеки ход винаги завършва със съобщение от асистента – например „Добавих architecture.md, който поискахте“ – което сигнализира за състояние на прекратяване в цикъла на агента. От гледна точка на агента, работата му е завършена и контролът е обратно в ръцете на потребителя.

Пътят от потребителски входни данни до отговор на агент, показан на диаграмата, се нарича един ход на разговор (един низв Codex). Въпреки това този ход на разговора може да включва множество итерации между извеждането на заключения от модела и заявките за инструменти. Всеки път, когато изпращате ново съобщение в съществуващ разговор, историята на разговора се включва като част от подкана за новия ход, която съдържа съобщенията и заявките за инструменти от предишни ходове:

Диаграма, озаглавена „Цикъл на агент с няколко хода“, показваща как интелигентен агент итеративно приема входни данни от потребителя, генерира действия, консултира се с инструменти, актуализира състоянието и връща резултати. Включва обозначени стъпки, стрелки и примерни изходни данни от инструменти, илюстриращи цикъла на структурирано анализиране на агента.

Това означава, че с напредването на разговора се увеличава и дължината на подканата, използвана за изпробване на модела. Тази дължина е важна, защото всеки модел има контекстен прозорец, който представлява максималния брой токени, които може да използва за една заявка за инференция. Имайте предвид, че този прозорец включва както входни, така и изходни токени. Както вероятно си представяте, един агент може да реши да направи стотици заявки за инструменти в един ход, като потенциално изчерпи контекстния прозорец. Поради тази причина управлението на контекстния прозорец е една от многото отговорности на агента. Сега нека разгледаме това по-отблизо, за да видим как Codex изпълнява цикъла на агента.

Инференция на модел

Codex CLI изпраща HTTP заявки към Responses API(отваря се в нов прозорец), за да извърши инференция на модел. Ще разгледаме как информацията протича през Codex, който използва Responses API, за да задвижва цикъла на агента.

Крайната точка на API за отговори, която Codex CLI използва, може да се конфигурира(отваря се в нов прозорец), така че може да се използва с всяка крайна точка, която прилага API за отговори(отваря се в нов прозорец):

Нека разгледаме как Codex създава подкана за първото извикване на инференция в разговор.

Създаване на първоначалната подкана

Като краен потребител Вие не посочвате дословно подкана, използвана за изпробване на модела, когато правите заявка към Responses API. Вместо това Вие посочвате различни типове входни данни като част от Вашата заявка, а сървърът на API за отговори решава как да структурира тази информация в подкана, която моделът е проектиран да обработва. Можете да разглеждате подканата като „списък с елементи“; този раздел ще обясни как Вашата заявка се преобразува в този списък.

В първоначалната подкана всеки елемент от списъка е свързан с роля. Ролята показва колко голяма тежест трябва да има свързаното съдържание и е една от следните стойности (в низходящ ред на приоритети): система, разработчик, потребител, асистент.

Responses API(отваря се в нов прозорец) приема данни от JSON с много параметри. Ще се съсредоточим върху тези три:

В Codex полето указания се чете от model_instructions_file(отваря се в нов прозорец) в ~/.codex/config.toml, ако е зададено; в противен случай се използват base_instructions свързани с модел(отваря се в нов прозорец). Указания, специфични за модела, се намират в хранилището Codex и са включени в CLI (напр., gpt-5.2-codex_prompt.md(отваря се в нов прозорец)).

Полето инструменти е списък с дефиниции на инструменти, които съответстват на схема, определена от Responses API. За Codex това включва инструменти, предоставени от Codex CLI, инструменти, предоставени от API за отговори, които трябва да бъдат направени достъпни за Codex, както и инструменти, предоставени от потребителя, обикновено чрез MCP сървъри:

JavaScript

1
[
2
// Codex's default shell tool for spawning new processes locally.
3
{
4
"type": "function",
5
"name": "shell",
6
"description": "Runs a shell command and returns its output...",
7
"strict": false,
8
"parameters": {
9
"type": "object",
10
"properties": {
11
"command": {"type": "array", "description": "The command to execute", ...},
12
"workdir": {"description": "The working directory...", ...},
13
"timeout_ms": {"description": "The timeout for the command...", ...},
14
...
15
},
16
"required": ["command"],
17
}
18
}
19

20
// Codex's built-in plan tool.
21
{
22
"type": "function",
23
"name": "update_plan",
24
"description": "Updates the task plan...",
25
"strict": false,
26
"parameters": {
27
"type": "object",
28
"properties": {"plan":..., "explanation":...},
29
"required": ["plan"]
30
}
31
},
32

33
// Web search tool provided by the Responses API.
34
{
35
"type": "web_search",
36
"external_web_access": false
37
},
38

39
// MCP server for getting weather as configured in the
40
// user's ~/.codex/config.toml.
41
{
42
"type": "function",
43
"name": "mcp__weather__get-forecast",
44
"description": "Get weather alerts for a US state",
45
"strict": false,
46
"parameters": {
47
"type": "object",
48
"properties": {"latitude": {...}, "longitude": {...}},
49
"required": ["latitude", "longitude"]
50
}
51
}
52
]

И накрая, полето входни данни на JSON данните е списък с елементи. Codex вмъква следните елементи(отваря се в нов прозорец) във входни данни преди да добави съобщението на потребителя:

1. Съобщение с role=developer, което описва изолираната среда, която се прилага само за предоставения от Codex инструмент на командния ред, дефиниран в секцията инструменти. Тоест, други инструменти, като тези, предоставени от MCP сървъри, не са изолирани от Codex и са отговорни за прилагането на собствените си защитни мерки.

Съобщението е създадено от шаблон, където ключовите части от съдържанието идват от фрагменти на Markdown, включени в Codex CLI, като workspace_write.md(отваря се в нов прозорец) и on_request.md(отваря се в нов прозорец):

Обикновен текст

1
<permissions instructions>
2
- description of the sandbox explaining file permissions and network access
3
- instructions for when to ask the user for permissions to run a shell command
4
- list of folders writable by Codex, if any
5
</permissions instructions>

2. (По избор) Съобщение с role=developer, чието съдържание е стойността developer_instructions, прочетена от файла config.toml на потребителя.

3. (По избор) Съобщение с role=user, чието съдържание са „потребителските указания“, които не са извлечени от един файл, а са събрани от множество източници(отваря се в нов прозорец). По принцип, по-конкретни указания се появяват по-късно:

4. Съобщение с role=user, което описва локалната среда, в която агентът в момента работи. Това указва текущата работна директория и командната среда на потребителя(отваря се в нов прозорец):

Обикновен текст

1
<environment_context>
2
<cwd>/Users/mbolin/code/codex5</cwd>
3
<shell>zsh</shell>
4
</environment_context>

След като Codex извърши всички горепосочени изчисления за стартиране на входни данни, той добавя съобщението на потребителя, за да започне разговора.

Предишните примери се фокусираха върху съдържанието на всяко съобщение, но имайте предвид, че всеки елемент на входни данни е JSON обект с тип, роля(отваря се в нов прозорец) и съдържание, както следва:

JSON

1
{
2
"type": "message",
3
"role": "user",
4
"content": [
5
{
6
"type": "input_text",
7
"text": "Add an architecture diagram to the README.md"
8
}
9
]
10
}

След като Codex изгради пълните данни в JSON за изпращане към Responses API, той прави HTTP POST заявка със заглавка упълномощаване, в зависимост от това как е конфигурирана крайната точка на Responses API в ~/.codex/config.toml (допълнителни HTTP заглавки и параметри на заявката се добавят, ако са посочени).

Когато сървър на OpenAI Responses API получи заявката, той използва JSON, за да извлече подкана за модела, както следва (за да е сигурно, персонализирана имплементация на Responses API може да направи различен избор):

Диаграма на моментна снимка, показваща една стъпка в цикъл на интелигентен агент. Потребителска заявка влиза в модела, който генерира мисъл, действие с име на инструмент и входни данни за инструмента. Диаграмата подчертава тази междинна стъпка на структурирано анализиране, преди да направи заявка за инструмента.

Както можете да видите, редът на първите три елемента в подканата се определя от сървъра, а не от клиента. Въпреки това, от тези три елемента, само съдържанието на системното съобщение се контролира от сървъра, тъй като инструментите и инструкциите се определят от клиента. След това следват входни данни от данните в JSON, за да се завърши подкана.

Сега, след като имаме нашата подкана, сме готови да изпробваме модела.

Първият ход

Тази HTTP заявка към Responses API инициира първия „етап“ на разговор в Codex. Сървърът отговаря с поток от събития, изпратени от сървъра (SSE(отваря се в нов прозорец)). Данните на всяко събитие са данни в JSON с "тип", който започва с "отговор", което може да бъде нещо подобно на ето това (пълен списък на събитията можете да намерите в нашата API документация(отваря се в нов прозорец)):

Обикновен текст

1
data: {"type":"response.reasoning_summary_text.delta","delta":"ah ", ...}
2
data: {"type":"response.reasoning_summary_text.delta","delta":"ha!", ...}
3
data: {"type":"response.reasoning_summary_text.done", "item_id":...}
4
data: {"type":"response.output_item.added", "item":{...}}
5
data: {"type":"response.output_text.delta", "delta":"forty-", ...}
6
data: {"type":"response.output_text.delta", "delta":"two!", ...}
7
data: {"type":"response.completed","response":{...}}

Codex консумира потока от събития(отваря се в нов прозорец) и ги публикува отново като обекти на вътрешни събития, които могат да бъдат използвани от клиент. Събития като response.output_text.delta се използват за поддръжка на поточно предаване в потребителския интерфейс, докато други събития като response.output_item.added се преобразуват в обекти, които се добавят към входни данни за последващи заявки към API за отговори.

Да предположим, че първата заявка към Responses API включва две събития response.output_item.done: едно с type=reasoning и едно с type=function_call. Тези събития трябва да бъдат представени в полето входни данни на JSON, когато отново направим запитване към модела с отговора на заявката за инструмента: 

JavaScript

1
[
2
/* ... original 5 items from the input array ... */
3
{
4
"type": "reasoning",
5
"summary": [
6
"type": "summary_text",
7
"text": "**Adding an architecture diagram for README.md**\n\nI need to..."
8
],
9
"encrypted_content": "gAAAAABpaDWNMxMeLw..."
10
},
11
{
12
"type": "function_call",
13
"name": "shell",
14
"arguments": "{\"command\":\"cat README.md\",\"workdir\":\"/Users/mbolin/code/codex5\"}",
15
"call_id": "call_8675309..."
16
},
17
{
18
"type": "function_call_output",
19
"call_id": "call_8675309...",
20
"output": "<p align=\"center\"><code>npm i -g @openai/codex</code>..."
21
}
22
]

Получената подкана, използвана за извадка на модела като част от последващата заявка, би изглеждала така:

Диаграма с етикет „Snapshot 2“, показваща ИИ агент след заявка на инструмент. Моделът получава наблюдение от инструмент и генерира нова мисъл и ново действие. Стрелките свързват входни данни, наблюдения и изходни данни, за да илюстрират как агентът повтаря своя цикъл на структурирано анализиране.

По-специално, обърнете внимание как старата подкана е точен начален низ на новата подкана. Това е умишлено, тъй като прави следващите заявки много по-ефективни, защото ни позволява да се възползваме от кеширане на подкани (което ще обсъдим в следващия раздел за производителност).

Като се върнем към първата ни диаграма на цикъла на агента, виждаме, че може да има много итерации между извеждането на заключения и заявките на инструменти. Подканата може да продължи да се разраства, докато накрая не получим съобщение от асистента, което да показва края на хода:

Обикновен текст

1
data: {"type":"response.output_text.done","text": "I added a diagram to explain...", ...}
2
data: {"type":"response.completed","response":{...}}

В Codex CLI представяме съобщението на асистента на потребителя и фокусираме композера, за да покажем на потребителя, че е негов „ред“ да продължи разговора. Ако потребителят отговори, както съобщението на асистента от предишния ход, така и новото съобщение на потребителя трябва да бъдат добавени към входни данни в заявката към Responses API, за да започне новият ход:

JavaScript

1
[
2
/* ... all items from the last Responses API request ... */
3
{
4
"type": "message",
5
"role": "assistant",
6
"content": [
7
{
8
"type": "output_text",
9
"text": "I added a diagram to explain the client/server architecture."
10
}
11
]
12
},
13
{
14
"type": "message",
15
"role": "user",
16
"content": [
17
{
18
"type": "input_text",
19
"text": "That's not bad, but the diagram is missing the bike shed."
20
}
21
]
22
}
23
]

И отново, тъй като продължаваме разговора, дължината на входните данни, които изпращаме към Responses API, продължава да се увеличава:

Диаграма с етикет „Snapshot 3“, показваща финалния етап от цикъл на агента с изкуствен интелект. След като получи резултатите от инструмента, моделът генерира заключителна мисъл и финален отговор, който се връща на потребителя. Стрелките показват прехода от резултата на инструмента към завършения отговор.

Нека разгледаме как се отразява на производителността тази непрекъснато нарастваща подкана.

Съображения относно производителността

Може би се питате: „Чакайте, нали цикълът на агента е квадратичен по отношение на количеството JSON, изпратено към Responses API по време на разговора?“ Бихте били прави. Въпреки че API за отговори поддържа незадължителен параметър previous_response_id(отваря се в нов прозорец), за да смекчи този проблем, Codex не го използва в момента, главно за да поддържа заявките напълно без състояние и да поддържа конфигурации за нулево задържане на данни (ZDR).

Избягването на previous_response_id опростява нещата за доставчика на API за отговори, защото гарантира, че всяка заявка е без състояние. Това също така улеснява подпомагането на клиенти, които са избрали нулево задържане на данни (ZDR)(отваря се в нов прозорец), тъй като съхраняването на данните, необходими за поддръжка на previous_response_id, би било в противоречие със ZDR. Имайте предвид, че клиентите на ZDR не жертват възможността да се възползват от собствени съобщения за структурирано анализиране от предишни ходове, тъй като свързаното encrypted_content може да бъде декриптирано на сървъра. (OpenAI съхранява ключа за декриптиране на клиент на ZDR, но не и данните му.) Вижте PRs #642(отваря се в нов прозорец) и #1641(отваря се в нов прозорец) за свързаните промени в Codex за поддръжка на ZDR.

Обикновено разходите за изпробване на модела надвишават разходите за мрежов трафик, което прави изпробването основен фокус на нашите усилия за ефективност. Ето защо кеширането на подкани е толкова важно, тъй като ни позволява да използваме повторно изчисления от предишна заявка за инференция. Когато получаваме попадения в кеша, изпробването на модела е линейно, вместо квадратично. Нашата документация за кеширане на подкани (отваря се в нов прозорец)обяснява това по-подробно:

Попадения в кеша са възможни само при точни съвпадения на префикса в рамките на подкана. За да се възползвате от предимствата на кеширането, поставете статично съдържание, като указания и примери, в началото на Вашата подкана, а променливото съдържание, като информация, специфична за потребителя, поставете в края. Това се отнася и за изображенията и инструментите, които трябва да бъдат идентични между заявките.

Имайки това предвид, нека разгледаме какви типове операции биха могли да причинят „пропуски в кеша“ в Codex:

  • Промяна на наличните за модела инструменти по средата на разговора.
  • Промяна на модела, който е цел на заявката към Responses API (на практика това променя третия елемент в оригиналната подкана, тъй като съдържа инструкции, специфични за модела).
  • Промяна на конфигурацията на изолираната среда, режима на одобрение или текущата работна директория.

Екипът на Codex трябва да внимава, когато въвежда нови функции в Codex CLI, които могат да компрометират кеширането на подкани. Например, първоначалната ни поддръжка за MCP инструменти въведе грешка, при която не успяхме да изброим инструментите в последователен ред(отваря се в нов прозорец), което доведе до пропуски в кеша. Имайте предвид, че инструментите на MCP могат да бъдат особено сложни, защото сървърите на MCP могат да променят списъка с инструменти, които предоставят, в движение чрез известие notifications/tools/list_changed(отваря се в нов прозорец). Спазването на това известие по средата на дълъг разговор може да доведе до скъп пропуск в кеша.

Когато е възможно, ние обработваме промените в конфигурацията, които се случват по средата на разговора, като добавяме ново съобщение към входни данни, за да отразим промяната, вместо да променяме по-ранно съобщение:

Полагаме големи усилия, за да осигурим попадения в кеша за по-добра производителност. Има още един ключов ресурс, който трябва да управляваме: контекстният прозорец.

Нашата обща стратегия, за да избегнем изчерпване на контекстния прозорец, е да уплътним разговора, след като броят на токените надхвърли определен праг. По-конкретно, заменяме входните данни с нов, по-малък списък от елементи, който е представителен за разговора, като по този начин позволяваме на агента да продължи с разбиране за това какво се е случило до момента. Ранна реализация на компресиране(отваря се в нов прозорец) изискваше потребителят ръчно да извика командата /compact, която правеше заявка към Responses API, използвайки съществуващия разговор плюс персонализирани инструкции за обобщение(отваря се в нов прозорец). Codex използва полученото съобщение от асистента, съдържащо резюмето като нови входни данни(отваря се в нов прозорец) за следващите ходове на разговора.

Оттогава API за отговори се е развил, за да поддържа специална /responses/compact крайна точка(отваря се в нов прозорец), която извършва по-ефективно компресиране. Това връща списък с елементи(отваря се в нов прозорец), които могат да се използват вместо предишните входни данни, за да продължите разговора, като същевременно освобождавате контекстния прозорец. Този списък включва специален елемент type=compaction с непрозрачен елемент encrypted_content, който запазва латентното разбиране на модела за оригиналния разговор. Сега Codex автоматично използва този крайна точка, за да уплътни разговора, когато auto_compact_limit(отваря се в нов прозорец) бъде превишен.

Какво предстои

Представихме цикъла на агента на Codex и разгледахме как Codex създава и управлява своя контекст при заявка към модел. По пътя подчертахме практически съображения и най-добри практики, които важат за всеки, който изгражда агентен цикъл върху Responses API.

Въпреки че цикълът на агента в Codex е основата, това е само началото. В предстоящите публикации ще се задълбочим в архитектурата на CLI, ще разгледаме как е реализирано използването на инструменти и ще разгледаме по-подробно модела за изолирана среда на Codex.

Автор

Michael Bolin

Благодарности

Специални благодарности към целия екип, който изгради Codex CLI.