Đến cuối hướng dẫn này, bạn sẽ có thể định nghĩa một công cụ, gửi nó đến OpenAI, đọc lệnh gọi công cụ mà mô hình trả về, và chạy chức năng của riêng bạn với các đối số có cấu trúc mà nó cung cấp. Bạn cũng sẽ bật chế độ nghiêm ngặt (strict mode) và các lệnh gọi song song (parallel calls), sau đó xác nhận và mô phỏng phía công cụ bằng Apidog để bạn tin tưởng vào đầu ra trước khi đưa vào sản xuất. Hãy mở tài liệu gọi hàm của OpenAI trong một tab khác để tham khảo nguồn đáng tin cậy, và xem bài giới thiệu của chúng tôi về xây dựng tác nhân với OpenAI Agents SDK để có cái nhìn tổng quan hơn.
Những gì bạn cần trước khi bắt đầu
Gọi hàm (Function calling) (thường được gọi là gọi công cụ - tool calling) là cách một mô hình kết nối với mã của bạn và các hệ thống bên ngoài. Bạn mô tả các hàm mà ứng dụng của bạn cung cấp, mô hình đọc yêu cầu của người dùng, và khi một hàm phù hợp, nó sẽ trả về tên hàm cùng với một đối tượng JSON các đối số. Mô hình không bao giờ tự chạy bất cứ thứ gì. Nó đưa cho bạn một yêu cầu có cấu trúc, và mã của bạn sẽ thực hiện công việc.
Sự phân tách đó là điều cần ghi nhớ khi bạn xây dựng. Mô hình chọn ý định và điền các tham số. Bạn vẫn kiểm soát việc thực thi. Một thông báo “Lấy thời tiết ở Paris” biến thành một lệnh gọi rõ ràng get_weather({"location": "Paris, France"}) thay vì một đoạn văn mà bạn phải phân tích thủ công.
Để theo dõi, bạn cần một khóa API OpenAI và một hàm trong mã của riêng bạn mà bạn muốn mô hình có thể kích hoạt. Một điều nữa cần quyết định trước: bạn đang sử dụng điểm cuối nào. Cùng một tính năng hoạt động ở hai nơi. API Chat Completions cũ hơn hỗ trợ nó, và API Responses mới hơn cũng vậy, API này hợp nhất những gì trước đây được chia giữa Chat Completions và Assistants API. Các cấu trúc khác nhau một chút, và các bước dưới đây sẽ đề cập đến cả hai.
Bước 1: Định nghĩa công cụ của bạn
Một công cụ là một định nghĩa hàm mà mô hình có thể đọc. Bạn cung cấp cho nó một tên, một mô tả và một JSON Schema cho các đối số. Mô tả thực sự hữu ích ở đây. Nó cho mô hình biết khi nào cần sử dụng hàm, vì vậy hãy viết nó như một chỉ dẫn, không phải một nhãn.
Đây là một định nghĩa công cụ theo dạng Chat Completions, trong đó hàm nằm trong một trình bao bọc function:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city. Use when the user asks about temperature or conditions.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country, e.g. Bogotá, Colombia"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"],
"additionalProperties": false
}
}
}
API Responses làm phẳng cấu trúc này. Các trường name, description, parameters và strict nằm ở cấp cao nhất của đối tượng công cụ, không có khóa function lồng nhau. Cùng thông tin, ít lớp hơn.
Nếu bạn đã duy trì một đặc tả OpenAPI cho dịch vụ cơ bản, các cấu trúc tham số sẽ được chuyển trực tiếp. Hướng dẫn của chúng tôi về tạo bộ sưu tập kiểm thử từ đặc tả OpenAPI cho thấy công việc schema đó mang lại lợi ích gấp đôi như thế nào.
Bước 2: Thực hiện yêu cầu đầu tiên của bạn
Gửi công cụ của bạn đến mô hình cùng với tin nhắn của người dùng. Một yêu cầu Chat Completions đầy đủ thực hiện điều này trông như sau:
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4.1",
"messages": [
{"role": "user", "content": "What is the weather in Paris right now?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"],
"additionalProperties": false
}
}
}
]
}'
Mảng tools chứa mọi hàm bạn muốn hiển thị cho lượt này. Mô hình đọc tin nhắn người dùng, quyết định xem có công cụ nào phù hợp không và phản hồi. Khi nó chọn một công cụ, bạn sẽ nhận được một lệnh gọi công cụ thay vì văn bản, đây là điều bạn sẽ đọc ở bước tiếp theo.
Bước 3: Đọc lệnh gọi công cụ mà mô hình trả về
Khi mô hình quyết định gọi một hàm, nó không trả về văn bản. Nó trả về một lệnh gọi công cụ mà bạn đọc từ phản hồi.
Trong Chat Completions, tin nhắn trợ lý chứa một mảng tool_calls. Mỗi mục có một id, một type là function, và một đối tượng function với name và một chuỗi arguments:
{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
}
Trong API Responses, lệnh gọi xuất hiện trong mảng output với cấu trúc phẳng hơn: một type là function_call, một call_id, một name và arguments:
{
"type": "function_call",
"call_id": "call_12345xyz",
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
Một chi tiết khiến mọi người bối rối: arguments là một chuỗi được mã hóa JSON, không phải là một đối tượng đã được phân tích cú pháp. Bạn tự phân tích cú pháp, chạy hàm của mình, sau đó gửi kết quả trở lại để mô hình có thể hoàn tất câu trả lời của nó.
Bước 4: Trả lại kết quả cho mô hình
Sau khi bạn chạy hàm của mình, hãy trả lại kết quả để mô hình có thể đưa ra câu trả lời cuối cùng. Trong Chat Completions, bạn nối thêm một tin nhắn vai trò tool được khóa bằng id của lệnh gọi. Trong API Responses, bạn gửi một mục function_call_output được khóa bằng call_id. Dù bằng cách nào, vòng lặp vẫn giống nhau: mô hình hỏi, bạn chạy, bạn trả lại kết quả, mô hình phản hồi.
Bước 5: Thêm lệnh gọi song song và chế độ nghiêm ngặt
Hai cài đặt thay đổi độ tin cậy và tốc độ của quá trình này, và bạn thêm chúng vào khi vòng lặp cơ bản hoạt động.
Lệnh gọi công cụ song song. Theo mặc định, mô hình có thể trả về nhiều hơn một lệnh gọi công cụ trong một lượt. Nếu người dùng hỏi thời tiết ở ba thành phố, bạn có thể nhận được ba lệnh gọi cùng lúc và có thể chạy chúng đồng thời. Khi bạn muốn buộc chỉ có tối đa một lệnh gọi mỗi lượt, hãy đặt parallel_tool_calls thành false.
Chế độ nghiêm ngặt. Đặt strict: true trên định nghĩa hàm và các đối số của mô hình được đảm bảo sẽ khớp với JSON Schema của bạn thay vì chỉ là cố gắng hết sức. OpenAI khuyến nghị luôn bật chế độ này. Chế độ nghiêm ngặt có các quy tắc: mọi đối tượng cần additionalProperties: false, và mọi trường trong properties phải xuất hiện trong required. Để làm cho một trường tùy chọn, bạn không bỏ nó khỏi required; bạn thêm null vào danh sách các kiểu được phép của nó.
| Cài đặt | Nó kiểm soát điều gì | Mặc định | Khi nào nên thay đổi |
|---|---|---|---|
parallel_tool_calls |
Liệu nhiều lệnh gọi công cụ có thể quay lại trong một lượt | true |
Đặt false khi các lệnh gọi phụ thuộc vào nhau hoặc phải chạy theo thứ tự |
strict |
Liệu các đối số có bị buộc phải khớp với schema | cố gắng hết sức trừ khi được đặt; khuyến nghị bật | Bật cho bất kỳ lệnh gọi nào bạn phân tích mà không cần mã phòng thủ |
tool_choice |
Liệu mô hình có thể gọi hàm nào và có gọi hay không | auto |
required để buộc gọi, none để tắt, hoặc đặt tên để ghim nó |
Quy tắc về trường tùy chọn thường khiến mọi người bất ngờ. Giả sử unit là tùy chọn trong get_weather. Ở chế độ nghiêm ngặt, bạn vẫn liệt kê nó trong required, sau đó đánh dấu nó có thể nhận giá trị null trong schema, ví dụ: "unit": {"type": ["string", "null"], "enum": ["celsius", "fahrenheit"]}. Mô hình giờ đây có thể truyền một đơn vị thực hoặc một giá trị null rõ ràng, nhưng nó không bao giờ có thể bỏ qua khóa. Khả năng dự đoán đó là điểm mấu chốt: mã phân tích cú pháp của bạn biết chính xác các khóa nào cần mong đợi mỗi lần.
Chế độ nghiêm ngặt làm giảm JSON bị định dạng sai, nhưng nó không kiểm tra xem các giá trị có ý nghĩa kinh doanh hay không. Một vị trí có thể hợp lệ theo schema nhưng vẫn là một thành phố mà bạn không cung cấp dịch vụ. Đó là lúc kiểm thử phát huy tác dụng.
Cách kiểm thử trong Apidog
Mô hình cung cấp cho bạn một lệnh gọi công cụ. Trước khi bạn kết nối nó với một hàm thực tế, bạn muốn có hai đảm bảo: các đối số khớp với cấu trúc bạn mong đợi, và API phía sau mà hàm của bạn sẽ gọi hoạt động theo cách bạn giả định. Apidog bao quát cả hai mặt đó, và điều đáng nói là phải chính xác về mặt nào.

Apidog xác thực và mô phỏng phía API. Nó không thực thi các hàm của ứng dụng của bạn. Điều nó làm tốt là hợp đồng xung quanh chúng.
Xác nhận cấu trúc của các đối số. Lấy chuỗi arguments từ một lệnh gọi công cụ thực tế, coi nó như một phần thân yêu cầu, và chạy các xác nhận trên đó trong Apidog. Kiểm tra xem location có tồn tại và là một chuỗi, rằng một trường enum chỉ chứa một giá trị được phép, rằng các trường bắt buộc có mặt. Việc lấy các trường cụ thể ra khỏi tải trọng dễ dàng với biểu thức JSONPath, và đối với các kiểm tra cấu trúc sâu hơn có xác thực dựa trên JSON Schema, phản ánh chính schema mà bạn đã cung cấp cho OpenAI ở chế độ nghiêm ngặt. Nếu đầu ra của mô hình vượt qua cùng schema mà hàm của bạn mong đợi, bạn đã hoàn thành vòng lặp.
Mô phỏng API phía sau mà hàm sẽ gọi. Hàm get_weather của bạn có thể gọi một nhà cung cấp thời tiết. Trong quá trình phát triển, nhà cung cấp đó có thể bị giới hạn tốc độ, phải trả phí hoặc chưa được xây dựng. Tạo một API giả lập trong Apidog trả về một tải trọng thời tiết thực tế, trỏ hàm của bạn vào giả lập, và thực hiện toàn bộ đường dẫn gọi mà không tốn một yêu cầu nào trên dịch vụ thực. Bạn kiểm soát phản hồi, bao gồm cả các trường hợp lỗi mà API thực hiếm khi tạo ra theo yêu cầu, vì vậy bạn có thể xác nhận mã của mình xử lý thời gian chờ hoặc mã 429 trước khi người dùng phát hiện ra.
Tổng hợp lại, quy trình làm việc là: nắm bắt một lệnh gọi công cụ từ OpenAI, xác nhận các đối số của nó dựa trên schema của bạn trong Apidog, sau đó chạy hàm của bạn với một giả lập Apidog của API thực. Bạn xác minh hợp đồng ở cả hai đầu mà không tốn các cuộc gọi trực tiếp hoặc đoán các trường hợp ngoại lệ.
Câu hỏi thường gặp
Gọi hàm có hoạt động trong cả Chat Completions và Responses API không? Có. Cả hai điểm cuối đều hỗ trợ. Responses API hợp nhất các khả năng trước đây được phân chia giữa Chat Completions và Assistants API. Sự khác biệt chính là cấu trúc: Chat Completions lồng hàm dưới khóa function và trả về tool_calls, trong khi Responses API sử dụng định nghĩa công cụ phẳng và trả về các mục function_call trong mảng output.
Tại sao mô hình trả về các đối số dưới dạng chuỗi thay vì một đối tượng? Trường arguments là văn bản được mã hóa JSON. Bạn phân tích cú pháp nó trong mã của mình trước khi sử dụng. Điều này nhất quán trên cả hai API, vì vậy hãy luôn chạy nó qua trình phân tích cú pháp JSON của bạn và xác thực kết quả thay vì tin tưởng mù quáng. Chạy các đối số đó thông qua xác thực JSON Schema sẽ bắt được một tải trọng bị định dạng sai trước khi nó đến hàm của bạn.
Chế độ nghiêm ngặt có đảm bảo hàm sẽ thành công không? Không. Chế độ nghiêm ngặt đảm bảo các đối số khớp với JSON Schema của bạn, vì vậy cấu trúc là đáng tin cậy. Nó không kiểm tra xem các giá trị có chính xác cho logic nghiệp vụ của bạn hay không, và nó không chạy hàm của bạn. Bạn vẫn tự xác thực các giá trị và xử lý các lỗi của lệnh gọi phía sau.
Apidog có thể chạy hàm thực tế của tôi không? Không, và nó không cố gắng làm vậy. Apidog xác thực các đối số mà mô hình tạo ra và mô phỏng API mà hàm của bạn phụ thuộc vào. Ứng dụng của bạn vẫn thực thi các hàm của riêng nó. Apidog bao gồm hợp đồng ở cả hai phía để bạn tin tưởng vào đầu vào và các phụ thuộc trước khi triển khai.
Tổng kết
Giờ đây bạn đã có toàn bộ vòng lặp: định nghĩa rõ ràng các công cụ của mình, gửi chúng kèm theo yêu cầu, đọc đầu ra tool_calls hoặc function_call, trả về kết quả, sau đó bật chế độ nghiêm ngặt và quyết định xem các lệnh gọi song song có hữu ích hay gây hại. Kết thúc bằng việc kiểm thử bằng cách xác nhận các đối số khớp với schema của bạn và mô phỏng API phía sau để bạn tự tin trước khi đưa vào sản xuất.
Bạn muốn thử khía cạnh kiểm thử? Tải Apidog để xác nhận các đối số gọi công cụ dựa trên schema của bạn và mô phỏng các API mà hàm của bạn phụ thuộc, tất cả trong một nơi.
