ข้ามไปยังเนื้อหาหลัก
OpenAI

23 มกราคม 2569

วิศวกรรม

การแตกกระบวนการลูปของเอเจนต์ Codex ออกเป็นส่วนๆ

โดย Michael Bolin สมาชิกฝ่ายเทคนิค

กำลังโหลด…

Codex CLI(เปิดในหน้าต่างใหม่) คือเอเจนต์ซอฟต์แวร์ท้องถิ่นแบบข้ามแพลตฟอร์มของเรา ถูกออกแบบมาเพื่อสร้างการเปลี่ยนแปลงซอฟต์แวร์ที่มีคุณภาพสูงและเชื่อถือได้ พร้อมทั้งทำงานอย่างปลอดภัยและมีประสิทธิภาพบนเครื่องของคุณ เราได้เรียนรู้มากมายเกี่ยวกับการสร้างเอเจนต์ซอฟต์แวร์ระดับโลก ตั้งแต่เราเปิดตัว CLI ครั้งแรกในเดือนเมษายน เพื่ออธิบายข้อมูลเชิงลึกเหล่านี้ โพสต์นี้คือโพสต์แรกในชุดบทความต่อเนื่องที่เราจะสำรวจการทำงานของ Codex และบทเรียนที่เรียนรู้ที่ได้จากประสบการณ์จริง (หากต้องการดูรายละเอียดเชิงลึกเกี่ยวกับวิธีการสร้าง Codex CLI โปรดดูที่ Repository โอเพ่นซอร์สของเราที่ https://github.com/openai/codex(เปิดในหน้าต่างใหม่) หลายรายละเอียดเชิงลึกเกี่ยวกับการตัดสินใจด้านการออกแบบของเราถูกเก็บไว้ใน GitHub Issues และ Pull Requests สำหรับผู้ที่สนใจศึกษาเพิ่มเติม

เพื่อเริ่มต้น เราจะมุ่งเน้นที่ ลูปเอเจนต์ ซึ่งเป็นตรรกะหลักใน Codex CLI ที่ทำหน้าที่การประสานการโต้ตอบระหว่างผู้ใช้ โมเดล และเครื่องมือที่โมเดลเรียกใช้เพื่อทำงานซอฟต์แวร์ที่มีความหมาย เราหวังว่าโพสต์นี้จะทำให้คุณเข้าใจบทบาทของเอเจนต์ (หรือ "โครงสร้างควบคุม") ของเราในการใช้ประโยชน์จาก LLM ได้ดีขึ้น

ก่อนที่เราจะเริ่มลงรายละเอียด เราขอชี้แจงเกี่ยวกับคำศัพท์สักเล็กน้อย ที่ OpenAI "Codex" หมายถึงชุดผลิตภัณฑ์ซอฟต์แวร์เอเจนต์ ซึ่งรวมถึง Codex CLI, Codex Cloud และส่วนขยาย Codex บน VS Code โพสต์นี้มุ่งเน้นไปที่ โครงสร้างควบคุม ของ Codex ซึ่งทำหน้าที่เป็นลูปเอเจนต์แกนกลางและตรรกะการประมวลผลที่รองรับประสบการณ์ทั้งหมดของ Codex และถูกเผยให้ใช้งานผ่าน Codex CLI เพื่อความง่ายในการอธิบาย เราจะถือว่า "Codex" และ "Codex CLI" ใช้แทนกันได้ในที่นี้

ลูปเอเจนต์

หัวใจสำคัญของเอเจนต์ AI ทุกตัวคือสิ่งที่เรียกว่า "ลูปเอเจนต์" ภาพประกอบแบบง่ายของลูปเอเจนต์มีลักษณะดังนี้

ไดอะแกรมชื่อ "ลูปของเอเจนต์" แสดงให้เห็นว่า ระบบ AI ประมวลผลคำขอของผู้ใช้ เรียกใช้เครื่องมือ สังเกตผลลัพธ์ อัปเดตแผน และส่งคืนผลลัพธ์ได้อย่างไร ลูกศรเชื่อมต่อขั้นตอนต่างๆ เช่น อินพุตของผู้ใช้ การให้เหตุผลของโมเดล การดำเนินการของเครื่องมือ และ การตอบขั้นสุดท้าย

ขั้นแรกเอเจนต์จะนำอินพุตจากผู้ใช้มาใส่ในชุดคำสั่งข้อความที่มันจัดเตรียมให้โมเดล ซึ่งรู้จักกันในนามว่าคำสั่ง

ขั้นตอนถัดไปคือการส่งคำสั่งของเราไปให้โมเดลและขอให้มันสร้างคำตอบ ซึ่งกระบวนการนี้เรียกว่า การอนุมาน ระหว่างการอนุมาน ที่เป็นข้อความจะถูกแปลงเป็นลำดับของโทเค็น(เปิดในหน้าต่างใหม่)อินพุต ซึ่งเป็นตัวเลขที่ชี้ตำแหน่งคำในคลังคำศัพท์ของโมเดล จากนั้นโทเค็นเหล่านี้จะถูกใช้เพื่อสุ่มตัวอย่างโมเดล ซึ่งจะสร้างลำดับใหม่ของโทเค็นเอาต์พุต

โทเค็นเอาต์พุตจะถูกแปลกลับเป็นข้อความ ซึ่งจะกลายเป็นการตอบกลับของโมเดล เนื่องจากโทเค็นถูกสร้างขึ้นทีละน้อย การแปลนี้จึงสามารถเกิดขึ้นระหว่างที่โมเดลทำงานได้ นี่จึงเป็นเหตุผลว่าทำไมแอปพลิเคชันหลายตัวที่ใช้ LLM จึงแสดงผลแบบสตรีมมิ่ง ในทางปฏิบัติการอนุมานมักถูกซ่อนอยู่หลัง API ที่ทำงานกับข้อความ โดยซ่อนรายละเอียดของ Tokenization

ผลลัพธ์จากขั้นตอนการอนุมานคือ โมเดลจะเลือก (1) ให้คำตอบสุดท้ายต่ออินพุตของผู้ใช้ หรือ (2) ขอให้เอเจนต์เรียกใช้เครื่องมือ (ตัวอย่างเช่น "รัน ls แล้วรายงานผล") ในกรณีที่(2) เอเจนต์จะดำเนินการเรียกใช้เครื่องมือและเพิ่มผลลัพธ์ต่อท้ายคำสั่งต้นฉบับ เอาต์พุตนี้ใช้เพื่อสร้างอินพุตใหม่สำหรับการส่งคำขอไปยังโมเดลอีกครั้ง จากนั้นเอเจนต์สามารถนำข้อมูลใหม่นี้มาพิจารณาและลองใหม่ได้

กระบวนการนี้จะทำซ้ำไปเรื่อยๆ จนกว่าโมเดลจะหยุดการเรียกใช้เครื่องมือ และสร้างข้อความสำหรับผู้ใช้แทน (เรียกว่า ข้อความผู้ช่วย ในโมเดลของ OpenAI) ในหลายกรณีข้อความนี้จะตอบคำขอเดิมของผู้ใช้โดยตรง แต่บางครั้งก็อาจเป็นคำถามติดตามเพิ่มเติมสำหรับผู้ใช้ได้เช่นกัน

เนื่องจากเอเจนต์สามารถเรียกใช้เครื่องมือที่เปลี่ยนแปลงสภาพแวดล้อมภายในเครื่องได้ ผลลัพธ์ของมันจึงไม่จำกัดอยู่แค่ข้อความตอบกลับของผู้ช่วย บ่อยครั้งผลลัพธ์สำคัญของซอฟต์แวร์เอเจนต์คือโค้ดที่มันสร้างหรือปรับแก้ในเครื่องของคุณ ถึงอย่างนั้น แต่ละรอบก็จะปิดท้ายด้วยข้อความจากผู้ช่วยเสมอ เช่น ฉันเพิ่ม architecture.md ให้ตามที่คุณต้องการแล้ว" ซึ่งเป็นสัญญาณว่าลูปของเอเจนต์สิ้นสุดลง สำหรับเอเจนต์ ถือว่างานของมันเสร็จเรียบร้อย และการควบคุมจะถูกส่งกลับไปยังผู้ใช้

กระบวนการตั้งแต่ อินพุตของผู้ใช้ ให้ข้อมูลจนถึง การตอบกลับของเอเจนต์ ตามที่เห็นในไดอะแกรม ถือว่าเป็นหนึ่งเทิร์นของการสนทนา (หรือหนึ่ง เธรด ใน Codex) แม้ว่าเทิร์นของบทสนทนาอาจมีการวนซ้ำหลายครั้งระหว่าง การอนุมานโมเดล และการเรียกใช้เครื่องมือ เมื่อใดก็ตามที่คุณส่งข้อความใหม่ในบทสนทนาเดิม ระบบจะรวมประวัติการสนทนาไว้ในคำสั่งของเทิร์นถัดไปด้วย ซึ่งประกอบด้วยข้อความและการเรียกใช้เครื่องมือจากเทิร์นก่อนหน้า

ไดอะแกรมชื่อ "ลูปการทำงานของเอเจนต์แบบหลายขั้นตอน" แสดงขั้นตอนที่เอเจนต์ AI รับอินพุตจากผู้ใช้ สร้างการกระทำ ใช้เครื่องมือ อัปเดตสถานะ และส่งผลลัพธ์กลับแบบวนซ้ำหลายรอบ รวมถึงขั้นตอนที่มีป้ายกำกับ ลูกศร และผลลัพธ์ตัวอย่างของเครื่องมือที่แสดงวงจรการให้เหตุผลของเอเจนต์

ซึ่งหมายความว่าเมื่อบทสนทนายาวขึ้น ความยาวของคำสั่งที่ใช้ในการสุ่มโมเดลก็จะเพิ่มขึ้นด้วย ความยาวนี้มีความสำคัญเพราะทุกโมเดลมี หน้าต่างบริบท ซึ่งเป็นจำนวนโทเค็นสูงสุดที่สามารถใช้ได้ในการเรียกใช้งานการอนุมานหนึ่งครั้ง โปรดทราบว่าหน้าต่างนี้รวมทั้งโทเค็นอินพุต และ โทเค็นเอาต์พุตด้วย อย่างที่คุณคงนึกภาพออก เอเจนต์อาจตัดสินใจเรียกใช้เครื่องมือหลายร้อยครั้งภายในเทิร์นเดียว ซึ่งอาจทำให้หน้าต่างบริบทถูกใช้จนหมดได้ ด้วยเหตุนี้ การจัดการหน้าต่างบริบท จึงเป็นหนึ่งในความรับผิดชอบมากมายของเอเจนต์ ตอนนี้มาดูกันว่า Codex ดำเนินการลูปเอเจนต์อย่างไร

การอนุมานของโมเดล

Codex CLI ส่งคำขอ HTTP ไปยัง Responses API(เปิดในหน้าต่างใหม่) เพื่อดำเนินการอนุมานโมเดล เราจะพิจารณาว่าข้อมูลถูกส่งผ่านใน Codex อย่างไร โดย Codex ใช้ Responses API ในการควบคุมลูปของเอเจนต์

ปลายทางของ Responses API ที่ Codex CLI ใช้นั้นสามารถกำหนดค่าได้(เปิดในหน้าต่างใหม่) จึงสามารถใช้ร่วมกับปลายทางใดๆ ที่ รองรับ Responses API(เปิดในหน้าต่างใหม่) ได้

มาสำรวจกันว่า Codex สร้างคำสั่งสำหรับการเรียกใช้งานการอนุมานครั้งแรกในบทสนทนาอย่างไร

การสร้างคำสั่งเริ่มต้น

ในฐานะผู้ใช้งานปลายทาง คุณไม่ได้เป็นคนระบุคำสั่งแบบคำต่อคำที่ใช้ในการสุ่มผลลัพธ์จากโมเดลเมื่อเรียกใช้ Responses API สิ่งที่คุณทำคือระบุชนิดของข้อมูลอินพุตในคำสั่ง ส่วนเซิร์ฟเวอร์ Responses API จะเป็นผู้กำหนดว่าจะจัดข้อมูลเหล่านี้เป็นคำสั่งในรูปแบบใดให้สอดคล้องกับการทำงานของโมเดล ให้คิดว่าคำสั่งเป็นเหมือน "ลิสต์ขององค์ประกอบต่างๆ" และในส่วนนี้จะอธิบายว่าคำขอของคุณถูกแปลงเป็นลิสต์นั้นอย่างไร

สำหรับคำสั้งเริ่มต้นแต่ละรายการในลิสต์จะถูกผูกเข้ากับบทบาทที่กำหนดไว้ บทบาทกำหนดระดับความสำคัญของเนื้อหาที่เชื่อมโยง และมีค่าได้เป็นหนึ่งในรายการต่อไปนี้ (เรียงจากความสำคัญสูงสุดไปน้อยที่สุด) ซึ่งประกอบด้วย ระบบ นักพัฒนา ผู้ใช้ และผู้ช่วย

Responses API(เปิดในหน้าต่างใหม่) รับเพย์โหลด JSON ที่มีพารามิเตอร์หลายตัว เราจะมุ่งเน้นที่สามประเด็นดังนี้

ใน Codex ช่อง คำสั่ง จะถูกอ่านจาก model_instructions_file(เปิดในหน้าต่างใหม่) ใน ~/.codex/config.toml หากระบุไว้มิฉะนั้นจะใช้ base_instructions (เปิดในหน้าต่างใหม่) ที่เกี่ยวข้องกับโมเดล คำแนะนำเฉพาะโมเดลอยู่ใน Codex ฑepo และถูกรวมไว้ใน CLI (เช่น gpt-5.2-codex_prompt.md(เปิดในหน้าต่างใหม่))

ฟิลด์เครื่องมือ คือรายการของคำจำกัดความเครื่องมือที่สอดคล้องกับสคีมาที่กำหนดโดย Responses API สำหรับ Codex สิ่งนี้รวมถึงเครื่องมือที่มาจาก Codex CLI เครื่องมือที่มาจาก Responses 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 Payload คือรายการของสิ่งต่างๆ Codex แทรกรายการต่อไปนี้(เปิดในหน้าต่างใหม่) ลงใน อินพุตก่อนเพิ่มข้อความของผู้ใช้

1 ข้อความที่มี role=developer ซึ่งอธิบายพื้นที่จำลองที่ ใช้กับเครื่องมือ shell ที่ Codex จัดเตรียมให้เท่านั้น ซึ่งกำหนดไว้ในส่วนของ เครื่องมือ กล่าวคือ เครื่องมืออื่น ๆ เช่นเครื่องมือที่มาจากเซิร์ฟเวอร์ MCP จะไม่ถูกแยกสภาพแวดล้อมโดย Codex และต้องรับผิดชอบในการบังคับใช้ข้อจำกัดความปลอดภัยของตนเอง

ข้อความถูกสร้างจากเทมเพลต โดยเนื้อหาสำคัญมาจาก Markdown Snippet ที่ถูกรวมอยู่ใน 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 Payload แบบเต็มเพื่อส่งไปยัง Responses API แล้ว จะทำการส่งคำขอ HTTP POST พร้อมกับส่วน Authorization โดยขึ้นอยู่กับการกำหนดค่า Endpoint ของ Responses API ใน ~/.codex/config.toml (จะมีการเพิ่มส่วนหัว HTTP และพารามิเตอร์ของคำขอเพิ่มเติมหากมีการระบุไว้)

เมื่อคำขอถูกส่งไปยังเซิร์ฟเวอร์ OpenAI Responses API เซิร์ฟเวอร์จะใช้ JSON เพื่อสร้างคำสั่งให้โมเดลตามรูปแบบนี้ (ทั้งนี้ การใช้งานแบบปรับแต่งเองอาจเลือกแนวทางอื่น)

ไดอะแกรมสแนปช็อตที่แสดงขั้นตอนเดียวในลูปของเอเจนต์ AI เมื่อคำขอของผู้ใช้เข้าสู่โมเดล โมเดลจะสร้างความคิด การกระทำที่มีชื่อเครื่องมือ และอินพุตสำหรับเครื่องมือนั้นออกมา แผนภาพนี้เน้นขั้นตอนการให้เหตุผลระดับกลางก่อนที่จะเรียกใช้เครื่องมือ

จะเห็นได้ว่าลำดับของสามรายการแรกในพรอมต์นั้นเป็นสิ่งที่เซิร์ฟเวอร์เป็นผู้กำหนด ไม่ใช่ฝั่งไคลเอนต์ อย่างไรก็ตามในบรรดาสามรายการนั้น มีเพียงเนื้อหาของข้อความระบบ เท่านั้นที่เซิร์ฟเวอร์ควบคุมด้วย เนื่องจาก เครื่องมือ และ คำสั่ง ถูกกำหนดโดยไคลเอนต์ จากนั้นตามด้วย อินพุต จาก JSON Payload เพื่อให้คำสั่งสมบูรณ์

เมื่อเราจัดเตรียมคำสั่งเสร็จแล้ว เราก็พร้อมที่จะเรียกใช้งานโมเดลเพื่อสร้างผลลัพธ์

เทิร์นแรก

การส่ง HTTP request ไปยัง Responses API ถือเป็นการเปิด "เทิร์นแรก" ของบทสนทนาใน Codex เซิร์ฟเวอร์จะตอบกลับด้วยสตรีม Server-Sent Events (SSE(เปิดในหน้าต่างใหม่)) ข้อมูลของแต่ละเหตุการณ์เป็น JSON Payload ที่มี "ประเภท" ซึ่งเริ่มต้นด้วย "การตอบกลับ" ซึ่งอาจมีลักษณะดังต่อไปนี้ (สามารถดูรายการเหตุการณ์ทั้งหมดได้ใน เอกสาร 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 ใช้เพื่อรองรับการสตรีมใน UI ในขณะที่อีเวนต์อื่นๆ เช่น response.output_item.added จะถูกแปลงเป็นออบเจ็กต์ที่ถูกต่อท้ายไปยังอินพุต สำหรับการเรียกใช้ Responses API ครั้งถัดไป

สมมติว่าคำขอแรกไปยัง Responses API มีอีเวนต์ response.output_item.done สองอีเวนต์: อีเวนต์หนึ่งมี type=การให้เหตุผล และอีกอีเวนต์หนึ่งมี 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
]

คำสั่งที่ได้ซึ่งใช้ในการสุ่มตัวอย่างโมเดลสำหรับคำค้นหาถัดไปจะมีลักษณะดังนี้:

แผนภาพที่มีป้ายกำกับว่า "สแนปช็อต 2" แสดงเอเจนต์ AI หลังจากการเรียกใช้เครื่องมือ เมื่อโมเดลได้รับข้อมูลการสังเกตจากเครื่องมือ มันจะสร้างความคิดและการกระทำชุดใหม่ขึ้นมา ลูกศรเชื่อมต่ออินพุต การสังเกต และเอาต์พุตเพื่อแสดงให้เห็นว่าเอเจนต์วนซ้ำลูปการให้เหตุผลของตนอย่างไร

จะสังเกตเห็นได้ว่าคำสั่งเก่า เป็นคำนำหน้าที่ตรงกันทุกประการ ของคำสั่งใหม่ สิ่งนี้ถูกออกแบบไว้โดยตั้งใจเพื่อช่วยให้คำขอถัดไปมีประสิทธิภาพมากขึ้นอย่างมาก เพราะช่วยให้เราใช้ประโยชน์จาก การแคชคำสั่ง (ซึ่งเราจะพูดถึงในหัวข้อถัดไปเกี่ยวกับประสิทธิภาพการทำงาน)

เมื่อย้อนกลับไปดูแผนภาพแรกของลูปเอเจนต์ เราจะเห็นว่าระหว่างการอนุมานและการเรียกใช้เครื่องมืออาจมีการวนซ้ำหลายครั้ง คำสั่งอาจขยายต่อไปจนกว่าเราจะได้รับข้อความจากผู้ช่วยซึ่งบ่งบอกว่าถึงจุดสิ้นสุดของรอบแล้ว

ข้อความธรรมดา

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 จึงเพิ่มขึ้นเรื่อยๆ

ไดอะแกรมที่มีป้ายกำกับว่า "สแนปช็อต 3" แสดงขั้นตอนสุดท้ายของลูปเอเจนต์ AI หลังจากได้รับผลลัพธ์จากเครื่องมือแล้ว โมเดลจะสร้างความคิดสรุปและคำตอบสุดท้ายเพื่อส่งกลับให้คุณ ลูกศรแสดงให้เห็นถึงการเปลี่ยนแปลงจากผลลัพธ์ของเครื่องมือไปสู่คำตอบที่สมบูรณ์

มาดูกันว่าคำสั่งที่เพิ่มขึ้นอย่างต่อเนื่องนี้มีความหมายต่อประสิทธิภาพอย่างไร

ข้อควรพิจารณาด้านประสิทธิภาพ

คุณอาจกำลังถามตัวเองว่า "เดี๋ยวนะ วงรอบของเอเจนต์มันมีความซับซ้อนเชิงกำลังสองตามปริมาณ JSON ที่ส่งไปยัง Responses API ตลอดการสนทนาหรือเปล่า" และคุณก็คิดถูกแล้ว แม้ว่า Responses API จะรองรับพารามิเตอร์ previous_response_id(เปิดในหน้าต่างใหม่) แบบไม่บังคับเพื่อบรรเทาปัญหานี้ แต่ Codex เลือกที่จะไม่ใช้มันในปัจจุบัน เพื่อให้คำขอคงความไร้สถานะ และรองรับโหมด Zero Data Retention (ZDR)

การหลีกเลี่ยง previous_response_id ทำให้ทุกอย่างง่ายขึ้นสำหรับผู้ให้บริการของ Responses API เพราะทำให้มั่นใจได้ว่าทุกคำขอเป็นแบบไร้สถานะ นอกจากนี้ยังทำให้การรองรับลูกค้าที่เลือกใช้ Zero Data Retention (ZDR)(เปิดในหน้าต่างใหม่) เป็นเรื่องที่ทำได้โดยตรง เนื่องจากการจัดเก็บข้อมูลที่จำเป็นเพื่อรองรับ previous_response_id จะขัดแย้งกับ ZDR โปรดทราบว่าลูกค้า ZDR ยังคงได้รับประโยชน์จากข้อความการให้เหตุผลที่เป็นกรรมสิทธิ์จากรอบก่อนหน้า เนื่องจาก encrypted_content ที่เกี่ยวข้องสามารถถอดรหัสได้บนเซิร์ฟเวอร์ (OpenAI เก็บรักษากุญแจถอดรหัสของลูกค้า ZDR ไว้ แต่ไม่เก็บข้อมูลของลูกค้า.) ดู PR #642(เปิดในหน้าต่างใหม่) และ #1641(เปิดในหน้าต่างใหม่) สำหรับการเปลี่ยนแปลงที่เกี่ยวข้องกับ Codex เพื่อรองรับ ZDR

โดยทั่วไปแล้ว ค่าใช้จ่ายจากการสุ่มโมเดลมีความสำคัญเหนือกว่าค่าใช้จ่ายด้านเครือข่าย ทำให้การสุ่มเป็นจุดที่เราต้องให้ความสำคัญในการเพิ่มประสิทธิภาพ นี่คือเหตุผลว่าทำไมการเก็บแคชคำสั่งจึงมีความสำคัญมาก เพราะช่วยให้เราสามารถนำการคำนวณจากการเรียกอนุมานครั้งก่อนกลับมาใช้ซ้ำได้ เมื่อมีการเข้าถึงข้อมูลจากแคชสำเร็จ การสุ่มตัวอย่างโมเดลจะเป็นเชิงเส้นแทนที่จะเป็นเชิงกำลังสอง เอกสาร การแคชคำสั่ง (เปิดในหน้าต่างใหม่)ของเราอธิบายเรื่องนี้ไว้อย่างละเอียด

การเข้าถึงแคชสำเร็จจะเกิดขึ้นได้ก็ต่อเมื่อมีการจับคู่ส่วนต้นของคำสั่งแบบตรงตัวเท่านั้น หากต้องการใช้ประโยชน์จากแคชให้เต็มที่ ให้จัดวางเนื้อหาคงที่อย่างคำแนะนำและตัวอย่างไว้ส่วนต้นของคำสั่ง แล้วนำข้อมูลที่เปลี่ยนไปตามผู้ใช้ไว้ส่วนท้าย สิ่งนี้ยังใช้กับภาพและเครื่องมือซึ่งต้องเหมือนกันทุกประการระหว่างคำขอ

เมื่อเข้าใจประเด็นนี้แล้ว มาดูกันว่ามีการทำงานแบบใดบ้างที่อาจทำให้เกิด "การเข้าถึงแคชไม่สำเร็จ" ใน Codex

  • การเปลี่ยนเครื่องมือที่มีให้กับโมเดลระหว่างการสนทนา
  • การเปลี่ยนโมเดลที่เป็นเป้าหมายของคำขอ Responses API (ในทางปฏิบัติจะเปลี่ยนรายการที่สามใน prompt เดิม เนื่องจากมีคำสั่งเฉพาะโมเดล)
  • การแก้ไขการตั้งค่าแซนด์บ็อกซ์ การเปลี่ยนโหมดการอนุมัติ หรือการเปลี่ยนไดเรกทอรีที่ใช้งานอยู่

ทีม Codex ต้องรอบคอบเมื่อแนะนำฟีเจอร์ใหม่ใน Codex CLI ที่อาจทำให้การแคชคำสั่งเสียหาย ยกตัวอย่างเช่น การสนับสนุนเครื่องมือ MCP ในระยะแรกของเราได้ก่อให้เกิด ข้อบกพร่องที่เราไม่สามารถจัดเรียงรายการเครื่องมือในลำดับที่สอดคล้องกันได้(เปิดในหน้าต่างใหม่) ส่งผลให้เกิดการเข้าถึงแคชไม่สำเร็จ โปรดทราบว่าเครื่องมือ MCP อาจซับซ้อนเป็นพิเศษ เนื่องจากเซิร์ฟเวอร์ MCP สามารถเปลี่ยนรายการเครื่องมือที่มีให้ได้ทันทีผ่านการแจ้งเตือน notifications/tools/list_changed(เปิดในหน้าต่างใหม่) การยอมรับการแจ้งเตือนนี้ในระหว่างการสนทนาที่ยาวนานอาจทำให้เกิดการเข้าถึงแคชไม่สำเร็จที่มีค่าใช้จ่ายสูง

เมื่อเป็นไปได้เราจะจัดการการเปลี่ยนแปลงการกำหนดค่าที่เกิดขึ้นระหว่างการสนทนาโดยการเพิ่มข้อความใหม่ ลงในอินพุตเพื่อสะท้อนการเปลี่ยนแปลง แทนที่จะแก้ไขข้อความก่อนหน้า

  • หากการกำหนดค่าแซนด์บ็อกซ์หรือโหมดการอนุมัติเปลี่ยนแปลง เราจะแทรก(เปิดในหน้าต่างใหม่)ข้อความ role=developer ใหม่ โดยใช้รูปแบบเดียวกับรายการ <permissions instructions> ต้นฉบับ
  • หากไดเรกทอรีการทำงานปัจจุบันเปลี่ยนแปลง เราจะแทรก(เปิดในหน้าต่างใหม่)ข้อความ role=user ใหม่ที่มีรูปแบบเดียวกันกับ <environment_context> ต้นฉบับ

เราทุ่มเทอย่างมากเพื่อให้มั่นใจว่าแคชถูกใช้งานได้อย่างมีประสิทธิภาพ มีทรัพยากรสำคัญอีกอย่างหนึ่งที่เราต้องจัดการ คือ ขอบเขตบริบท

กลยุทธ์ทั่วไปของเราเพื่อหลีกเลี่ยงการที่หน้าต่างบริบทหมดลงคือการบีบอัดบทสนทนาเมื่อจำนวนโทเค็นเกินเกณฑ์ที่กำหนดไว้ เราจะแทนที่ อินพุตด้วยรายการใหม่ที่มีขนาดเล็กลง ซึ่งเป็นตัวแทนของบทสนทนา ทำให้เอเจนต์สามารถดำเนินการต่อไปได้โดยมีความเข้าใจว่าเกิดอะไรขึ้นมาจนถึงตอนนี้ การนำการบีบอัดมาใช้งาน(เปิดในหน้าต่างใหม่)ในระยะแรก กำหนดให้ผู้ใช้ต้องเรียกใช้คำสั่ง /compact ด้วยตนเอง ซึ่งจะทำการเรียก Responses API โดยใช้บทสนทนาที่มีอยู่ร่วมกับคำสั่งแบบกำหนดเองสำหรับการสรุป(เปิดในหน้าต่างใหม่) Codex ใช้ข้อความจากผู้ช่วยที่มีบทสรุปเป็น อินพุต(เปิดในหน้าต่างใหม่)ใหม่สำหรับการสนทนาเทิร์นถัดไป

นับตั้งแต่นั้นมา Responses API ได้พัฒนาเพื่อรองรับ /responses/compact Endpoint(เปิดในหน้าต่างใหม่) พิเศษที่ทำการบีบอัดได้อย่างมีประสิทธิภาพมากขึ้น ระบบจะส่งคืนรายการของสิ่งต่างๆ(เปิดในหน้าต่างใหม่) ที่สามารถใช้แทนอินพุตก่อนหน้าเพื่อสนทนาต่อได้ พร้อมทั้งเพิ่มพื้นที่ว่างในหน้าต่างบริบท รายการนี้ประกอบด้วยรายการพิเศษอย่าง type=compaction พร้อมรายการ encrypted_content ที่รักษาความเข้าใจแฝงของโมเดลเกี่ยวกับบทสนทนาต้นฉบับ ขณะนี้ Codex ใช้ Endpoint นี้โดยอัตโนมัติเพื่อบีบอัดบทสนทนาเมื่อ auto_compact_limit(เปิดในหน้าต่างใหม่) เกินขีดจำกัด

สิ่งที่จะตามมาในลำดับถัดไป

เราได้แนะนำลูปของเอเจนต์ใน Codex และอธิบายว่า Codex สร้างและจัดการบริบทอย่างไรเมื่อเรียกใช้งานโมเดล ในระหว่างการอธิบาย เราได้ชี้ให้เห็นข้อควรระวังและแนวทางปฏิบัติที่เหมาะสมสำหรับผู้ที่สร้างลูปเอเจนต์บน Responses API

แม้ว่าลูปเอเจนต์จะเป็นรากฐานสำหรับ Codex แต่นี่ก็เป็นเพียงจุดเริ่มต้นเท่านั้น ในโพสต์ถัดไปเราจะเจาะลึกสถาปัตยกรรมของ CLI สำรวจวิธีการใช้งานเครื่องมือ และพิจารณาโมเดลการแซนด์บ็อกซ์ของ Codex อย่างละเอียด

ผู้เขียน

Michael Bolin

คำขอบคุณ

ขอแสดงความขอบคุณอย่างยิ่งแก่ทีมทั้งหมดที่อยู่เบื้องหลังการพัฒนา Codex CLI