Hỗ trợ Klien Agent Đa Lượt: Token Streaming và Gọi Công cụ trong NVIDIA Dynamo

Tương tác Agent Đa Lượt và Nhu cầu từ Phía Klien

Một tương tác agent hoàn chỉnh đòi hỏi việc duy trì thông tin phiên có cấu trúc. Mỗi lượt của trợ lý (assistant) thường xen kẽ giữa "suy luận" (reasoning) và một hoặc nhiều lời gọi công cụ (tool call). Lượt người dùng (user) tiếp theo sẽ điền các kết quả công cụ tương ứng vào ngữ cảnh của mô hình. Việc các đoạn suy luận có cần được phát lại trong lượt tiếp theo hay không không phải là một quy tắc cứng nhắc; nó phụ thuộc vào cả mô hình và loại lượt tương tác: một số suy luận cần được giữ lại, trong khi số khác phải bị loại bỏ.

Mô hình tương tác biểu cảm hơn này đòi hỏi một công cụ suy luận có khả năng tạo ra các phản hồi API được phân đoạn chính xác. **Việc phân tích lời gọi công cụ (tool-call parsing) và suy luận (reasoning parsing) phải hoàn tất trước khi bộ xử lý phía klien (client harness) nhận được phản hồi.** Đồng thời, các quy trình làm việc agent giá trị cao như tạo mã (code generation) phụ thuộc nhiều vào trải nghiệm phản hồi của bộ xử lý: các đoạn suy luận, sự kiện gọi công cụ và siêu dữ liệu yêu cầu phải được **truyền tải dần dần** trong quá trình tạo ra một lượt, thay vì chờ đợi toàn bộ văn bản phản hồi cuối cùng mới được gửi đi một lần.

Bài viết này tóm tắt những kinh nghiệm chúng tôi có được khi tích hợp klien agent thực tế với NVIDIA Dynamo: cách chúng tôi tăng cường các bộ phân tích và phạm vi API, cải thiện hành vi streaming, và tách các lớp phân tích này thành các crate có thể tái sử dụng độc lập.

Những thay đổi này được xây dựng dựa trên nền tảng hiệu suất đã được thảo luận trong bài blog đầu tiên của chúng tôi, tập trung vào kiến trúc dịch vụ cơ bản của suy luận agent — giao diện người dùng, bộ định tuyến và quản lý bộ đệm KV (KV cache). Bài viết này tiếp tục theo hướng đó, nhưng tập trung vào **tính đúng đắn, căn chỉnh trải nghiệm người dùng và hiệu suất.**

Klien agent (harness) vẫn đang phát triển nhanh chóng. Claude Code, Codex và OpenClaw bộc lộ nhiều điểm áp lực tương tự thông qua các giao diện API khác nhau, do đó, các ví dụ dưới đây tập trung vào "các hành vi cốt lõi mà một hệ thống dịch vụ tùy chỉnh phải triển khai để bắt kịp các klien này".

So sánh máy chủ suy luận tiêu chuẩn và Dynamo trong tương tác Agent hai lượt.
Hình 1: So sánh máy chủ suy luận tiêu chuẩn và Dynamo trong tương tác Agent hai lượt. Dynamo giảm đáng kể chi phí xây dựng prompt và phân tích lặp lại bằng cách sử dụng bộ đệm tiền tố ổn định và gửi lời gọi công cụ ngay sau khi phân tích.

Cấu hình Dynamo quan trọng cho klien

Các thử nghiệm của chúng tôi sử dụng mô hình nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4 vừa được phát hành, nhưng các vấn đề tương tự cũng xuất hiện với các mô hình, bộ phân tích suy luận và bộ phân tích lời gọi công cụ khác.

Để tái tạo kết quả trong bài viết này, hãy kích hoạt API tương thích với Anthropic trên giao diện người dùng và bật các tùy chọn sau để giữ lại trạng thái prompt, suy luận và công cụ:

  • --enable-anthropic-api: Hiển thị Anthropic Messages API cho klien. Nhiều klien có thể quay trở lại API Messages mặc định, nhưng trải nghiệm sẽ bị giảm sút đáng kể.
  • --strip-anthropic-preamble: Loại bỏ tiêu đề tính phí của Anthropic có thể phá vỡ việc tái sử dụng bộ đệm KV.
  • --enable-streaming-tool-dispatch: Gửi thực thi lời gọi công cụ ngay khi nó được giải mã hoàn chỉnh, thay vì chờ đợi kết thúc lượt.

Lệnh đầy đủ như sau:

python -m dynamo.frontend \
  --http-port 8000 \
  --enable-anthropic-api \
  --strip-anthropic-preamble \
  --enable-streaming-tool-dispatch

Hai tham số quan trọng nhất của worker trong thiết lập này là:

  • --dyn-tool-call-parser <parser>--dyn-reasoning-parser <parser>: Xây dựng lại lời gọi công cụ và các khối suy luận theo định dạng mong đợi của mô hình. Các bộ phân tích này cũng kiểm soát việc suy luận của lượt trước được giữ lại, ghi đè hay loại bỏ trong lượt tiếp theo.

Tính ổn định của Prompt là chìa khóa để tái sử dụng bộ đệm

Claude Code gửi hàng nghìn token prompt boilerplate với mỗi yêu cầu, phần này đáng lẽ phải hoàn toàn nhất quán giữa các người dùng và phiên. Tuy nhiên, mỗi prompt lại bắt đầu bằng một **tiêu đề tính phí liên quan đến phiên** — nếu không được loại bỏ, backend tùy chỉnh sẽ gặp lỗi cache miss:

x-anthropic-billing-header: cc_version=0.2.93; cch=abc123def456==;
You are Claude Code, an interactive CLI tool...

Tiêu đề này "làm ô nhiễm" bộ đệm KV, khiến nó không thể tái sử dụng ngay cả giữa các phiên khác nhau của cùng một người dùng. Việc có một dòng thay đổi theo phiên ở vị trí 0 có nghĩa là tiền tố token ở đầu mỗi phiên mới sẽ khác nhau, và do đó, các định nghĩa lệnh và công cụ ổn định sau đó không bao giờ có thể được căn chỉnh sạch sẽ với một tiền tố có thể tái sử dụng.

Để khôi phục khả năng tái sử dụng bộ đệm KV, Dynamo đã thêm tùy chọn --strip-anthropic-preamble. Cơ chế thay đổi nhỏ, nhưng ảnh hưởng khi chạy rất lớn: **loại bỏ tiêu đề tính phí không ổn định này trước khi tokenize, cho phép prompt ổn định bắt đầu từ token thứ 0.**

Kết quả thực tế rất đáng kể. Trên NVIDIA B200 chạy Dynamo, với prompt dài 52K token, TTFT (Time To First Token) cho tiền tố ổn định là 168 ms; nếu giữ lại tiêu đề thay đổi theo phiên trong tiền tố, thời gian này tăng lên 912 ms; loại bỏ tiêu đề này trước khi tokenize, TTFT trở lại 169 ms. Điều này có nghĩa là tiêu đề không ổn định này tiêu tốn trung bình 744 ms/yêu cầu cho khối lượng công việc này, đủ để biến một prompt hệ thống có thể tái sử dụng trở lại thành một lần điền trước "lạnh". Quy đổi ra, TTFT giảm khoảng **5 lần** khi người dùng mới truy cập cùng một triển khai hoặc người dùng cũ bắt đầu một phiên mới.

Biểu đồ so sánh TTFT với và không có tiêu đề tính phí, cho thấy việc loại bỏ tiêu đề giúp khôi phục bộ đệm tiền tố và giảm TTFT khoảng 5 lần.
Hình 2: Dữ liệu chuẩn cho thấy bộ đệm tiền tố được khôi phục và TTFT giảm khoảng 5 lần sau khi loại bỏ tiêu đề theo phiên.

Sự tinh tế của việc phân tích suy luận và công cụ

Khi các đoạn suy luận được "phát lại" trong lượt tiếp theo, **không có một cách tiếp cận đúng đắn nào áp dụng cho tất cả các trường hợp.** Một số mô hình cố ý loại bỏ các suy luận trước đó trong các lượt trợ lý thông thường; nhưng các lượt Agent có lời gọi công cụ thì hoàn toàn khác — các đoạn suy luận đó thường **phải** tiếp tục gắn liền với các lời gọi công cụ mà chúng giải thích. Hợp đồng thực sự được xác định theo từng mô hình, từng lượt.

Anthropic đã cung cấp một trường hợp sản xuất cụ thể trong bài tổng kết Claude Code ngày 23 tháng 4: khi bộ đệm prompt hết hạn, việc khôi phục phiên có thể xóa suy nghĩ của các lượt trước đó để giảm áp lực điền trước.

Các mô hình suy luận hiện đại thường tạo ra hai loại lượt trợ lý:

  • Suy luận sau đó trực tiếp đưa ra câu trả lời cho người dùng.
  • Suy luận sau đó đưa ra một hoặc nhiều lời gọi công cụ.

Các mô hình dạng Agent đặc biệt giỏi trong việc xen kẽ **nhiều lần** suy luận và lời gọi công cụ trong một phản hồi, có dạng:

<think>reasoning_0</think> tool_call_0 <think>reasoning_1</think> tool_call_1

Đến lượt tiếp theo, mỗi đoạn suy luận phải tiếp tục gắn liền với lời gọi công cụ mà nó giải thích. Dynamo hiện đã hỗ trợ đầy đủ định dạng xen kẽ này. Trước đây, nội dung của cùng một lượt có thể được xây dựng lại thành dạng "dẹt" như sau:

<think>reasoning_0 reasoning_1</think> tool_call_0 tool_call_1

Nếu lượt trợ lý được xây dựng lại thành dạng "một đoạn suy luận chung + một nhóm lời gọi công cụ", số lượng token mà mô hình nhận được bề ngoài không thay đổi, nhưng **thông tin về thứ tự và dấu phân cách** — hai yếu tố làm cho token thực sự có ý nghĩa — đã bị mất. Thứ tự nhóm này bắt nguồn từ các mô hình ban đầu — chúng chỉ xuất ra một đoạn suy luận và một lần gọi công cụ trong một lượt.

Ngoài thứ tự sai lệch, chúng tôi còn phát hiện ra rằng suy luận thường bị "làm sạch quá mức" trước khi đi vào lượt tiếp theo. Đối với một số mô hình, việc loại bỏ suy nghĩ trước đó trong các lượt không có lời gọi công cụ thực sự là một hành vi đã định sẵn, và là một phần của việc tinh chỉnh (fine-tuning) mô hình (DeepSeek-R1 là ví dụ điển hình nhất). Nhưng chiến lược tương tự khi áp dụng cho các lượt Agent có suy luận xen kẽ thì lại sai — ở đó, suy luận được dùng để giải thích toàn bộ chuỗi công cụ. Điểm khó nhất để phát hiện lỗi này là: người dùng có thể thấy suy luận được giải mã chính xác trong phản hồi hiện tại, nhưng nó lại bị **biến dạng hoặc loại bỏ một cách âm thầm** trước khi đi vào lượt tiếp theo.

Chúng tôi đã xác minh điều này trên một triển khai Dynamo + TRT-LLM: Nemotron-3-Super-120B-A12B-NVFP4, 4× B200, TP=4, bật --enable-anthropic-api, --strip-anthropic-preamble, --enable-streaming-tool-dispatch, bộ phân tích suy luận sử dụng nemotron_deci, bộ phân tích lời gọi công cụ sử dụng qwen3_coder.

Kết hợp Suy luận và Lời gọi Công cụ

Khi một mô hình thực hiện suy luận trước khi gọi công cụ, phản hồi của nó sẽ có nội dung <think> trước, sau đó là XML <tool_call>. Trong ví dụ Nemotron này, hai bộ phân tích khác nhau — nemotron_deci chịu trách nhiệm về suy luận và qwen3_coder chịu trách nhiệm về lời gọi công cụ — phải cắt luồng giống nhau thành các khối nội dung Anthropic Messages API chính xác mà không làm nhiễu lẫn nhau.

Chúng tôi đã gửi cùng một prompt 5 lần qua Anthropic Messages API: một prompt hệ thống yêu cầu mô hình "suy nghĩ từng bước", hai định nghĩa công cụ (calculator và weather), và tin nhắn người dùng "Think carefully about what 15 * 23 equals, then use the calculator to verify.". Một cấu trúc phản hồi tiêu biểu như sau:

{
  "content": [
    {
      "type": "thinking",
      "thinking": "I need to calculate 15 * 23. Let me think: 15 * 20 = 300, and 15 * 3 = 45, so 300 + 45 = 345. I'll use the calculator to verify.\n"
    },
    {
      "type": "tool_use",
      "id": "call-a3364797-3160-4e84-b567-5c495694d502",
      "name": "calculator",
      "input": { "expression": "15 * 23" }
    }
  ],
  "stop_reason": "tool_use",
  "usage": { "input_tokens": 403, "output_tokens": 95 }
}

Hai bộ phân tích hoạt động streaming đồng thời

Khi đi qua đường dẫn streaming, sự tương tác giữa hai bộ phân tích càng rõ ràng hơn. Một yêu cầu streaming sẽ tạo ra một chuỗi sự kiện SSE, và trình tự thời gian của các loại sự kiện này cho thấy cách hai bộ phân tích chia luồng token:

  1ms  message_start
 82ms  content_block_start  type=thinking
 82ms  content_block_delta  (thinking tokens stream here, ~7ms apart)
  ...  (~70 thinking deltas over ~520ms)
602ms  content_block_stop
602ms  content_block_start  type=text
602ms  content_block_delta
800ms  content_block_stop
800ms  content_block_start  type=tool_use
800ms  content_block_delta
800ms  content_block_stop
814ms  message_delta        stop_reason=tool_use
814ms  message_stop

Khối thinking được truyền tải từng token trong khoảng từ 82 ms đến 602 ms; sau đó là một khối text rất ngắn (tương ứng với khoảng trắng giữa thinking và tool_call trong luồng token gốc); tiếp theo là khối tool_use đến như một đơn vị cấu trúc hoàn chỉnh ở 800 ms; cuối cùng, message_stop đóng toàn bộ phản hồi ở 814 ms.

Trước PR #7358, luồng này không tạo ra chuỗi sự kiện Anthropic chính xác. Việc sửa lỗi được thực hiện qua ba bước:

  1. Sự phân công rõ ràng cho phân tích suy luận: Trước đây, phân tích suy luận cạnh tranh ở nhiều cấp độ. Bộ phân tích backend sẽ chia đầu ra của mô hình thành reasoning_contentcontent thông thường, đồng thời bộ chuyển đổi streaming của Anthropic, khi ánh xạ cùng một luồng vào khối nội dung Anthropic, lại tự suy luận ranh giới của <think>. PR #7358 đã làm rõ sự phân công: nếu đường dẫn backend đã tạo ra các phần tăng suy luận có cấu trúc, bộ chuyển đổi Anthropic sẽ tin tưởng nó và chỉ chịu trách nhiệm ánh xạ nó vào định dạng phản hồi.
  2. Ưu tiên hỗ trợ suy luận gốc của template: Dynamo hiện kiểm tra xem template chat hiện tại có hiểu cách đọc reasoning_content hay không. Với các template như Nemotron và Qwen3 có thể đọc trực tiếp trường này, Dynamo sẽ không thay đổi gì, để template quyết định "giữ lại bao nhiêu suy luận". Nếu template chỉ hiểu content, Dynamo sẽ quay lại cách biểu diễn cũ — bằng cách chèn khối <think> vào content để giữ lại suy luận, hoặc loại bỏ suy luận trực tiếp theo chiến lược của mô hình/bộ phân tích. Đường dẫn tiền xử lý Rust (ModelInput::Tokens) và đường dẫn worker Python (ModelInput::Text) sử dụng cùng một quy tắc điều kiện.
  3. Tôn trọng công tắc thinking theo yêu cầu: Nhiều template mặc định truncate_history_thinking=true để tiết kiệm ngữ cảnh. Điều này hợp lý cho các cuộc hội thoại thông thường, nhưng đối với các quy trình làm việc của Agent, nó sẽ xóa bỏ suy luận đằng sau các lời gọi công cụ trước đó. Dynamo hiện chỉ thay đổi hành vi này trên các yêu cầu mà "suy luận thực sự có tác dụng": khi bộ phân tích suy luận được cấu hình và klien không tắt thinking một cách rõ ràng, đường dẫn Anthropic sẽ đặt enable_thinking=truetruncate_history_thinking=false. Điều này vừa bảo toàn ngữ cảnh cần thiết cho Agent trong lượt tiếp theo, vừa không ảnh hưởng đến các mô hình và yêu cầu không nên bật thinking.

Trong thí nghiệm B200 của chúng tôi, với prompt hệ thống 52K token cộng thêm khoảng 500 token nội dung suy luận của lượt trợ lý, nếu tiền tố của lượt tiếp theo không thay đổi, TTFT là 167 ms; nếu nội dung suy luận bị ghi đè trước khi đi vào lượt tiếp theo, TTFT sẽ tăng lên 322 ms — tăng khoảng 1,9 lần, tương đương khoảng 155 ms/yêu cầu.

Kết luận cốt lõi là: **klien, bộ phân tích và đường dẫn template phải nhất quán về hành vi suy luận mà mô hình mong đợi.** Việc loại bỏ thinking trong các lượt thông thường có thể đúng với mô hình này, nhưng sai với mô hình khác; việc giữ lại suy luận xen kẽ trong các lượt có lời gọi công cụ có thể cực kỳ quan trọng, ngay cả khi các lượt thông thường cho phép cắt bỏ. Trong thực tế, đừng giả định rằng các token được tạo ra ở lượt N sẽ nguyên vẹn đến lượt N+1 dưới dạng tiền tố — điều này phụ thuộc vào bộ phân tích suy luận, bộ phân tích công cụ và template chat của mô hình bạn đang phục vụ.

Gọi công cụ dạng Streaming

Token streaming giúp trải nghiệm người dùng phản hồi nhanh hơn và linh hoạt hơn. Thách thức là làm thế nào để duy trì khả năng phản hồi này trong khi vẫn gửi các lời gọi công cụ ra bên ngoài dưới dạng một **khối hoàn chỉnh và nhất quán**. Trong các đường dẫn Dynamo ban đầu, token suy luận được truyền tải bình thường, nhưng lời gọi công cụ sẽ được đệm cho đến khi kết thúc một lượt mới gửi đi một lần cho klien. Điều này vừa làm giảm khả năng phản hồi, vừa trì hoãn việc thực thi công cụ — mặc dù mô hình thực ra đã quyết định sẽ gọi gì từ lâu.

Trạng thái Klien thấy gì Khi nào có thể nhận biết công cụ đã sẵn sàng
Đệm (Buffered) Các đoạn lời gọi công cụ bị giữ lại không gửi Chỉ khi finish_reason: "tool_calls"
Streaming nội tuyến (Inline streaming) Các phần tăng dần lời gọi công cụ thông thường Ngay khi mô hình xuất ra
Phân phối (Dispatch) Kênh phụ event: tool_call_dispatch đã được định kiểu Tại cùng thời điểm hoàn thành cấu trúc, nhưng đã được phân tích

Sự chuyển đổi quan trọng nhất là từ dòng đầu tiên sang hai dòng sau — đây chính là ranh giới mà klien không còn "chờ đợi luồng kết thúc mới biết phải hành động". Không có cơ chế phân phối, klien chỉ có thể thấy luồng token thông thường, phải tự tích lũy các phần tăng dần, chờ đến khi cấu trúc đủ hoàn chỉnh rồi mới suy ra lời gọi công cụ đã kết thúc hay chưa. Khi bật phân phối, Dynamo có thể trực tiếp phát ra một sự kiện phụ SSE đã được định kiểu:

event: tool_call_dispatch
data: {"choice_index":0,"tool_call":{"index":0,"id":"call-...","type":"function","function":{"name":"calculator","arguments":"{\"expression\":\"42 * 17\"}"}}}

Sự kiện này ngay lập tức thông báo cho klien: lời gọi công cụ đã sẵn sàng để thực thi. Klien không còn cần tự ghép các phần tăng dần, không còn cần đoán xem các tham số đã đầy đủ hay chưa, và cũng không cần nhúng một bộ phân tích bên trong bộ xử lý. Điều này giúp việc tích hợp Dynamo với các klien tùy chỉnh trở nên liền mạch hơn.

Biểu đồ so sánh thời gian cho thấy Dynamo gửi lời gọi công cụ ngay sau khi phân tích hoàn tất, thay vì chờ đợi luồng phản hồi kết thúc.
Hình 3: So sánh thời gian cho thấy Dynamo gửi (dispatch) ngay sau khi phân tích lời gọi công cụ hoàn tất, thay vì chờ đợi luồng phản hồi kết thúc.

Khả năng tương thích API Anthropic cho Claude Code và OpenClaw

Claude Code và OpenClaw đều gọi trực tiếp Anthropic Messages API, thay vì chỉ sử dụng tạo văn bản thuần túy qua các điểm cuối. Để tái tạo trải nghiệm của bộ xử lý gốc, cần một tập hợp các hành vi nhỏ dễ bị bỏ qua trong các thử nghiệm tạm thời:

  • Phải trả về siêu dữ liệu mô hình trên cả GET /v1/modelsGET /v1/models/{model_id}.
  • Xử lý đúng các ID mô hình có dấu gạch chéo.
  • Trả về input_tokens hợp lệ trong message_start.
  • Chấp nhận trường cache_control.

Sau khi giao diện người dùng có thể truy cập và hành vi tương thích, cả hai bộ xử lý đều có thể trỏ đến điểm cuối tương thích Anthropic của Dynamo:

ANTHROPIC_API_KEY=local-dev-token \
ANTHROPIC_BASE_URL=http://localhost:8000 \
ANTHROPIC_CUSTOM_MODEL_OPTION=nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4 \
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME="Dynamo NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4" \
claude --model nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4
ANTHROPIC_API_KEY=local-dev-token \
ANTHROPIC_BASE_URL=http://localhost:8000 \
npx openclaw agent --local -m "Say ok" --json

Loạt sửa lỗi này giúp hành vi triển khai tùy chỉnh gần hơn với backend gốc. Một ví dụ cụ thể có thể minh họa vấn đề tốt hơn một danh sách dài. Trong quá trình khởi động, bộ xử lý sẽ trực tiếp hỏi chi tiết về mô hình đã chọn, nhưng Dynamo trước đây chưa hiển thị điểm cuối này:

GET /v1/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4
HTTP/1.1 404 Not Found

Một ví dụ khác: input_tokens trong message_start báo 0, ngay cả khi số lượng thực tế xuất hiện trong phản hồi cuối cùng. Điều này khiến bộ xử lý tạm thời đặt lại số token về 0 mỗi khi bắt đầu một lượt mới. PR #7234 đã sửa đường dẫn Anthropic để điền input_tokens trước khi luồng bắt đầu. Các số liệu này vẫn là dữ liệu mặt phẳng điều khiển cho các phiên dài — bộ xử lý sử dụng chúng để xác định khi nào nên nén cuộc hội thoại trước khi yêu cầu tiếp theo vượt quá cửa sổ mô hình. Công việc dịch vụ tokenizer tổng quát hơn đã được triển khai riêng trong PR #7699, bổ sung các điểm cuối /v1/tokenize/v1/detokenize, có thể cung cấp số lượng token chính xác trước khi yêu cầu đi vào engine.

API Responses và khả năng tương thích với Codex

Về phía Codex, vấn đề tương đương nằm ở giao diện v1/responses. **Việc vượt qua các bài kiểm tra tuân thủ không có nghĩa là đạt được sự nhất quán trong trải nghiệm người dùng.** Chúng tôi nhận thấy: sau khi một yêu cầu API Responses đi qua Dynamo, các trường ban đầu khiến nó "là một yêu cầu Responses chứ không phải yêu cầu chat completions" sẽ bị loại bỏ. Để giữ lại các trường này, cần có những thay đổi kiến trúc cho đường dẫn ResponseParams của Dynamo, cùng với việc căn chỉnh kiểu dữ liệu upstream (PR #6089).

Codex nên trỏ đến Dynamo thông qua API Responses tương thích OpenAI và bật nén yêu cầu:

OPENAI_API_KEY=local-dev-token \
codex exec \
  -c 'openai_base_url="http://localhost:8000/v1"' \
  -c 'features.enable_request_compression=true' \
  -m nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4 \
  "Say ok"

Siêu dữ liệu mô hình của Codex định hình yêu cầu

Công việc căn chỉnh của Codex thực ra bắt đầu trước yêu cầu POST /v1/responses đầu tiên. CLI sẽ phân tích chuỗi mô hình đã cấu hình thành một bản ghi danh mục mô hình cục bộ. Từ đó, ModelInfo thu được sẽ xác định toàn bộ trạng thái klien được xây dựng xung quanh mô hình đó: các hướng dẫn cơ bản, định dạng lịch sử, sổ đăng ký công cụ, tham số suy luận, kiểm soát mức độ chi tiết, hỗ trợ hình ảnh, tính toán ngữ cảnh, chiến lược cắt bỏ đầu ra công cụ, parallel_tool_calls, và thậm chí cả payload Responses cuối cùng.

Hai điểm cuối hoàn toàn có thể phục vụ cùng một mô hình cơ bản, nhưng hành vi của Agent lại hoàn toàn khác nhau do Codex gắn các siêu dữ liệu danh mục khác nhau cho chúng. Yêu cầu có thể vượt qua xác thực schema, nhưng bộ xử lý xung quanh yêu cầu đã thay đổi.

Cắt bỏ đầu ra công cụ là một ví dụ rất điển hình. Codex sẽ không nhét nguyên vẹn đầu ra lệnh không giới hạn vào ngữ cảnh mô hình của lượt tiếp theo: chiến lược danh mục của mô hình đã chọn sẽ cắt bỏ kết quả quan sát của shell và công cụ theo chiến lược, sau đó mới đưa lại vào ngữ cảnh. Trong bản chụp danh mục chúng tôi đã thử nghiệm, gpt-5.5 sử dụng:

{ "mode": "tokens", "limit": 10000 }

Trong khi đó, openai/openai/gpt-5.5 trên điểm cuối tùy chỉnh sử dụng siêu dữ liệu fallback:

{ "mode": "bytes", "limit": 10000 }

Hai cái này không tương đương. Đối với đầu ra mã chứa đầy ASCII, giới hạn 10000 byte sẽ cắt bỏ nhật ký có cấu trúc, dấu vết ngăn xếp, JSON hoặc đầu ra kiểm tra sớm hơn nhiều so với giới hạn 10000 token. Đối với một Agent lập trình, điều này làm thay đổi mức độ ngữ cảnh nó có thể xem lại sau khi kiểm tra thất bại, lệnh tìm kiếm hoặc lỗi biên dịch. Mô hình có thể cần thêm lời gọi công cụ để lấy lại những thông tin mà lẽ ra hồ sơ danh mục đã giữ lại cho nó.

Cài đặt suy luận cũng do danh mục quyết định. Codex chỉ gửi đối tượng reasoning của Responses khi siêu dữ liệu mô hình đã chọn tuyên bố hỗ trợ tóm tắt suy luận; trên đường dẫn này, Codex cũng sẽ yêu cầu reasoning.encrypted_content, để trạng thái suy luận có thể được phát lại giữa các lượt. Siêu dữ liệu fallback sẽ loại bỏ hoàn toàn đường dẫn này.

Prompt cũng sẽ thay đổi. Khi Codex chuyển từ hồ sơ fallback/mặc định sang hồ sơ danh mục gpt-5.5, prompt hệ thống cũng sẽ chuyển đổi theo. Prompt fallback được tổ chức xung quanh các hoạt động Codex chung (# How you work, # AGENTS.md spec, # Tool Guidelines), nhấn mạnh ưu tiên của AGENTS.md, lập kế hoạch, xác minh và thói quen tìm kiếm shell; prompt của gpt-5.5 là một tài liệu hướng dẫn khác (# Personality, # General, # Working with the user), mô tả Agent như một kỹ sư phần mềm thực tế, và đưa ra các hướng dẫn mạnh mẽ hơn về việc đọc mã, tái sử dụng chế độ cục bộ, sửa đổi có kiểm soát phạm vi, xử lý không gian làm việc bẩn, apply_patch, cập nhật hợp tác và định dạng câu trả lời cuối cùng. Nói cách khác, **bí danh danh mục không chỉ ảnh hưởng đến các trường yêu cầu như cắt bỏ và suy luận, mà còn ảnh hưởng đến chiến lược hành vi cơ bản của Agent.**

Chúng tôi đã trực tiếp quan sát sự khác biệt này trên một tập con 50 tác vụ của SWE-Bench Verified. Trong thí nghiệm này, cả hai đường dẫn đều cuối cùng dẫn đến GPT-5.5 do OpenAI quản lý — sự khác biệt nằm ở bản thân điểm cuối, và bản ghi danh mục mô hình mà Codex gắn vào. Khi điểm cuối tùy chỉnh sử dụng ID mô hình openai/openai/gpt-5.5, nhưng không liên kết với hồ sơ danh mục gpt-5.5, Codex sẽ quay trở lại hành vi fallback chung. Trong một lần chạy, hồ sơ fallback đã khởi tạo **chỉ khoảng một nửa** số lời gọi công cụ:

Hồ sơ danh mục Tổng số lời gọi công cụ Trung bình mỗi tác vụ
Hồ sơ gpt-5.5 2,087 41.7
Hồ sơ Fallback 1,048 21.0
Chênh lệch -1,039 -20.8

Kết luận so sánh từng cặp nhiệm vụ cho thấy cùng một hướng: hồ sơ gpt-5.5 sử dụng nhiều công cụ hơn trên 50/50 nhiệm vụ, hồ sơ fallback nhiều hơn trên 0/50 nhiệm vụ. Kiểm định hoán vị cho kết quả p < 0.001.

Chúng tôi sau đó đã thêm một bí danh danh mục mô hình, cho phép openai/openai/gpt-5.5 kế thừa hồ sơ gpt-5.5 mong muốn ban đầu. Với cùng thiết lập 50 nhiệm vụ, hai đường dẫn trở nên gần nhau hơn nhiều:

Hồ sơ danh mục Tổng số lời gọi công cụ Trung bình mỗi tác vụ
Hồ sơ gpt-5.5 2,081 41.6
Hồ sơ tùy chỉnh sau khi ánh xạ bí danh 2,205 44.1
Chênh lệch +124 +2.5

Tại thời điểm này, sự khác biệt còn lại không còn đáng kể về mặt thống kê: kiểm định hoán vị cho kết quả khoảng p = 0.22, và hướng so sánh từng cặp cũng hỗn hợp (20/50 nhiệm vụ thiên về hồ sơ gốc, 28/50 thiên về hồ sơ bí danh, 2/50 ngang bằng).

Đối với Dynamo, kết luận là: **khả năng tương thích với Codex phải được đánh giá ở lớp danh mục và định hình yêu cầu, chứ không chỉ ở lớp schema HTTP.** Nếu Codex không thể phân tích một ID mô hình thành hồ sơ mong đợi, các giá trị mặc định fallback có thể thay đổi chiến lược cắt bỏ, khả năng sử dụng công cụ tìm kiếm, kiểm soát mức độ chi tiết, hỗ trợ tóm tắt suy luận và hỗ trợ lời gọi công cụ song song trước khi yêu cầu đến Dynamo.

Các bước tiếp theo

Dynamo hiện đã hỗ trợ nvext.agent_hints, bao gồm các trường như latency_sensitivity, priority, oslspeculative_prefill. Các trường này cho phép klien truyền tải nhiều thông tin hơn về "bản chất của lượt này" đến backend, ngoài prompt: một phiên đang chờ phản hồi của người dùng không giống với một phiên đang chạy một chuỗi công cụ nền dài, và API hiện có thể mang tải sự khác biệt này.

Trên nhánh chính v1.1.0, Dynamo cũng đã mở thêm các thành phần của ngăn xếp Agent dưới dạng **các crate độc lập có thể tái sử dụng**: các giao thức, bộ phân tích, lớp tokenizer lần lượt trở thành các phiên bản dynamo-protocols, dynamo-parsersdynamo-tokenizers, cho phép các nhóm xây dựng hoặc tùy chỉnh các đường dẫn dịch vụ hướng klien của riêng họ mà không cần sao chép các triển khai nội bộ của Dynamo.

Đây cũng là cầu nối dẫn đến các hệ thống chạy dài hạn như AutoResearch. Bài blog đầu tiên đã giải thích tại sao khối lượng công việc của Agent lại gây áp lực lớn như vậy lên ngăn xếp dịch vụ; bài viết này tập trung vào "hợp đồng hướng klien" cần thiết để chạy đúng các khối lượng công việc này, và đặt nền tảng cho các Agent chạy dài hạn hiệu quả dựa trên các điểm cuối của Dynamo.

Thẻ: NVIDIA Dynamo LLM agent Token Streaming Tool Calling

Đăng vào ngày 3 tháng 7 lúc 18:32