โค้ดเบสของเอเจนต์ส่วนใหญ่มักมีไฟล์ที่ไม่มีใครอยากดูแลรักษา ไฟล์นี้เก็บคำจำกัดความของเครื่องมือ (tool definitions) กว่าสี่สิบรายการ ซึ่งแต่ละรายการเป็น JSON schema ที่เขียนด้วยมือ อธิบายถึงปลายทาง (endpoint) ที่มี schema อยู่แล้วที่อื่น เมื่อทีม API ส่งฟิลด์ที่จำเป็นใหม่เข้ามา สเปกก็อัปเดต เอกสารก็อัปเดต และเอเจนต์ก็ยังคงส่งเพย์โหลดเก่าไปเรื่อยๆ จนกว่าจะมีคนสังเกตเห็นข้อผิดพลาด 400
คุณมีคำอธิบายที่เครื่องอ่านได้ของทุกปลายทางอยู่แล้ว นั่นคือเอกสาร OpenAPI หน้าที่คือการเปลี่ยนเอกสารนั้นให้เป็นคำจำกัดความของเครื่องมือที่โมเดลสามารถเรียกใช้ได้ และทำให้ทั้งสองสิ่งนี้ซิงค์กันโดยอัตโนมัติ แทนที่จะต้องจำและอัปเดตด้วยตนเอง
คู่มือนี้ครอบคลุมถึงวิธีที่การดำเนินการของ OpenAPI ถูกแมปไปยัง schema ของเครื่องมือ สิ่งที่ตัวสร้าง (generator) ต้องแก้ไขระหว่างทาง วิธีลดขนาดสเปกที่มี 200 ปลายทางให้เหลือสิ่งที่โมเดลสามารถทำความเข้าใจได้ และวิธีทดสอบว่าเครื่องมือที่สร้างขึ้นมานั้นทำงานได้ หากคุณอยู่ในขั้นตอนเริ่มต้น โพสต์ของเราเกี่ยวกับ คุณยังจำเป็นต้องมีเครื่องมือ API เมื่อเอเจนต์เขียนโค้ดหรือไม่ จะให้บริบทที่กว้างขึ้น
Apidog มีความสำคัญในที่นี้ เนื่องจากสเปกจะต้องถูกต้องก่อนที่สิ่งใดก็ตามที่สร้างจากมันจะสามารถใช้งานได้ คำจำกัดความของเครื่องมือจะได้รับข้อบกพร่องทุกอย่างจากเอกสารต้นฉบับ
ต้นทุนของการเขียนคำจำกัดความเครื่องมือด้วยมือ
การเขียนเครื่องมือด้วยมืออาจรู้สึกดีเมื่อมีปลายทางเพียงห้าแห่ง แต่จะเริ่มไม่ดีนักเมื่อมีประมาณยี่สิบแห่ง ด้วยเหตุผลสามประการ
คำจำกัดความเริ่มแตกต่างกัน (drift) สเปกถูกสร้างจากโค้ดหรือดูแลโดยทีม API ไฟล์เครื่องมือถูกดูแลโดยผู้ที่สร้างเอเจนต์ ไม่มีอะไรเชื่อมโยงกัน ทำให้เกิดความแตกต่างอย่างเงียบๆ และอาการแรกคือเอเจนต์ที่ "จู่ๆ" ก็หยุดทำงาน
คำอธิบายเริ่มสั้นลง เมื่อคนเขียน schema ด้วยมือถึงสี่สิบรายการ ยี่สิบรายการสุดท้ายก็จะได้คำอธิบายแค่บรรทัดเดียว โมเดลเลือกเครื่องมือโดยการอ่านคำอธิบายเหล่านั้น ดังนั้นข้อความที่สั้นจะลดประสิทธิภาพในการเลือกเครื่องมือโดยตรง โพสต์ของเราเกี่ยวกับ การออกแบบ tool schema สำหรับเอเจนต์ จะเจาะลึกว่าทำไมถ้อยคำจึงมีความสำคัญมากขนาดนี้
ข้อผิดพลาดมองไม่เห็นจนกว่าจะถึงเวลาที่รันไทม์ Schema ที่เขียนด้วยมือซึ่งระบุว่าฟิลด์เป็นสตริงในขณะที่ API ต้องการตัวเลขจำนวนเต็ม จะสร้างข้อผิดพลาด 422 ในครั้งแรกที่เอเจนต์พยายามใช้มัน ในสภาพแวดล้อมการทำงานจริง สำหรับภารกิจจริง
การสร้างจากสเปกแก้ไขปัญหาทั้งสามอย่างในคราวเดียว มีแหล่งข้อมูลความจริงเพียงแหล่งเดียว คำอธิบายมาจากข้อความเดียวกันกับที่เอกสารของคุณใช้ และประเภทข้อมูลมาจาก schema เดียวกันที่เซิร์ฟเวอร์ใช้ตรวจสอบความถูกต้อง
การดำเนินการของ OpenAPI กลายเป็นเครื่องมือได้อย่างไร
การแมปนั้นตรงไปตรงมามากกว่าที่คิด ลองดูการดำเนินการเดียว:
paths:
/orders/{orderId}/refund:
post:
operationId: refundOrder
summary: Refund an order
description: >
Issues a full or partial refund against a completed order.
Refunds are irreversible. Partial refunds require an amount
no greater than the remaining refundable balance.
parameters:
- name: orderId
in: path
required: true
schema: { type: string }
description: The order to refund.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [reason]
properties:
amount:
type: integer
description: Amount in cents. Omit for a full refund.
reason:
type: string
enum: [duplicate, fraudulent, requested_by_customer]
คำจำกัดความของเครื่องมือที่ได้จากสิ่งนี้:
{
"name": "refundOrder",
"description": "Issues a full or partial refund against a completed order. Refunds are irreversible. Partial refunds require an amount no greater than the remaining refundable balance.",
"input_schema": {
"type": "object",
"required": ["orderId", "reason"],
"properties": {
"orderId": { "type": "string", "description": "The order to refund." },
"amount": { "type": "integer", "description": "Amount in cents. Omit for a full refund." },
"reason": { "type": "string", "enum": ["duplicate", "fraudulent", "requested_by_customer"] }
}
}
}
สี่กฎต่อไปนี้เป็นส่วนสำคัญในการทำงาน:
operationIdกลายเป็นชื่อเครื่องมือ หากการดำเนินการไม่มีoperationIdให้สร้างชื่อที่เสถียรจากเมธอดบวกพาธ แล้วจึงเพิ่มเข้าไปในสเปก- พารามิเตอร์แบบ path, query และ body จะถูกรวมให้เป็นออบเจกต์ properties เดียวกัน โมเดลไม่สนใจว่าค่าจะถูกส่งไปที่ใดบนสาย แต่ตัวเรียกใช้งานของคุณสนใจ ดังนั้นให้เก็บตารางเสริมที่บันทึกว่าพารามิเตอร์แต่ละตัวไปที่ใด
summaryรวมกับdescriptionกลายเป็นคำอธิบายเครื่องมือ ทั้งสองส่วนนี้จะถูกรวมเข้าด้วยกัน โดยปกติแล้ว summary เพียงอย่างเดียวมักจะสั้นเกินไปที่จะช่วยในการเลือก- อาร์เรย์ที่จำเป็นจะถูกรวมเข้าด้วยกัน พารามิเตอร์พาธที่จำเป็นและฟิลด์ body ที่จำเป็นจะอยู่ในรายการ
requiredเดียวกัน
ตัวเรียกใช้งานเป็นอีกครึ่งหนึ่ง และมันมีขนาดเล็ก:
def execute(tool_name, args, spec_index, http):
op = spec_index[tool_name] # method, path template, param locations
path = op.path
query, body = {}, {}
for name, value in args.items():
location = op.locations[name] # "path" | "query" | "header" | "body"
if location == "path":
path = path.replace("{" + name + "}", str(value))
elif location == "query":
query[name] = value
elif location == "body":
body[name] = value
return http.request(op.method, path, params=query, json=body or None)
นั่นคือส่วนเชื่อมต่อทั้งหมด สิ่งอื่นที่เหลือคือการทำความสะอาดระหว่างทาง
สิ่งที่ตัวสร้างต้องแก้ไข
การดัมพ์สเปกเข้าสู่ schema ของเครื่องมือแบบดิบๆ จะสร้างเครื่องมือที่โมเดลจัดการได้ไม่ดี มีการปรับแต่งห้าอย่างที่สำคัญ
- **แก้ไข
$refpointers.** API ส่วนใหญ่ที่เรียกใช้เครื่องมือจะยอมรับ JSON Schema เพียงบางส่วนและจะไม่ตามอ้างอิงไปยังส่วนcomponentsให้ inline พวกมัน ระวัง recursive schemas ซึ่งการ inline จะขยายไปเรื่อยๆ ให้ตัดการเรียกซ้ำที่ความลึกที่กำหนดและอธิบายโครงสร้างที่ลึกกว่านั้นด้วยข้อความ - **ละทิ้งคีย์เวิร์ดที่ไม่รองรับ.**
oneOf,allOf,discriminatorและnullableเป็นคีย์เวิร์ดที่พบบ่อยในสเปกแต่ได้รับการรองรับไม่ดีใน tool schemas ให้รวมallOfโดยการผสานคุณสมบัติ สำหรับoneOfให้เลือกรูปแบบที่โดดเด่น หรือแยกการดำเนินการออกเป็นสองเครื่องมือ โดยแต่ละเครื่องมือสำหรับแต่ละรูปแบบ ตัวเลือกที่สองนี้มักจะให้การเลือกเครื่องมือที่ดีกว่าอยู่แล้ว - **ลดการซ้อนลึก.** Body ที่ซ้อนกันสามระดับเป็นเรื่องยากสำหรับโมเดลที่จะกรอกข้อมูลได้อย่างถูกต้อง หาก payload สำหรับการสร้างคำสั่งซื้อของคุณมีการซ้อน
customer.address.postal_codeให้พิจารณาใช้ tool surface ที่แบนราบกว่าและประกอบรูปทรงที่ซ้อนกันใหม่ในตัวเรียกใช้งาน - **ตัดแต่ง response schemas.** คำจำกัดความของเครื่องมืออธิบายถึงข้อมูลอินพุต Schema การตอบกลับแบบเต็มไม่ควรอยู่ในคำจำกัดความ และการรวมเข้าไว้จะทำให้สิ้นเปลืองบริบท รูปร่างของการตอบกลับมีความสำคัญเมื่อได้ผลลัพธ์กลับมา และนั่นเป็นปัญหาแยกต่างหากที่ครอบคลุมในโพสต์ของเราเกี่ยวกับ การรักษาการตอบกลับ API ภายใน context window ของเอเจนต์
- **ติดธงความปลอดภัย.** การดำเนินการเขียนควรถูกทำเครื่องหมายเพื่อให้ตัวเรียกใช้งานของคุณสามารถส่งผ่านช่องทางอนุมัติได้ หากสเปกของคุณใช้ส่วนขยาย เช่น
x-agent-requires-approvalให้อ่านและปฏิบัติตาม จับคู่สิ่งนี้กับรูปแบบในคู่มือ AI agent guardrails ของเรา
อย่าให้โมเดลเข้าถึงปลายทางทั้ง 200 แห่ง
ปัญหาในทางปฏิบัติที่ใหญ่ที่สุดไม่ใช่เรื่องการแปลง แต่เป็นเรื่องของปริมาณ API ที่สมบูรณ์แล้วมี การดำเนินการหลายร้อยรายการ และการคัดลอกทั้งหมดลงในรายการเครื่องมือจะทำให้เกิดความล้มเหลวสองอย่างพร้อมกัน: บริบทจะเต็มไปด้วย schema ก่อนที่งานจะเริ่ม และความแม่นยำในการเลือกจะลดลงเนื่องจากโมเดลกำลังเลือกจากตัวเลือกที่คล้ายคลึงกันเกือบทั้งหมด
สามวิธีในการลดจำนวนลง โดยเรียงตามประสิทธิภาพของการทำงานโดยประมาณ
- กรองด้วยแท็ก การดำเนินการของ OpenAPI มีแท็ก และแท็กมักจะแมปไปยังพื้นที่ผลิตภัณฑ์ เอเจนต์ที่จัดการการคืนเงินต้องการแท็ก
ordersและpaymentsไม่ใช่adminหรือanalyticsนี่คือการกรองเพียงบรรทัดเดียวและมักจะช่วยลดพื้นที่ส่วนใหญ่ได้ - จัดการรายการที่อนุญาต (allowlist) จดบันทึกการดำเนินการที่เอเจนต์นี้ได้รับอนุญาตให้เรียกใช้ โดยใช้
operationIdและสร้างเฉพาะรายการเหล่านั้น สิ่งนี้ทำหน้าที่เป็นมาตรการควบคุมความปลอดภัยด้วย เนื่องจากเอเจนต์ที่ไม่มีเครื่องมือสำหรับปลายทางจะไม่สามารถเรียกใช้โดยบังเอิญ โพสต์ของเราเกี่ยวกับ การป้องกันเอเจนต์ทำลาย API ของคุณ สนับสนุนพื้นผิวการทำงานที่จำกัดแบบนี้ - เรียกใช้เครื่องมือตามต้องการ สำหรับ API ขนาดใหญ่มาก ให้จัดทำดัชนีการดำเนินการและเลือกเพียงไม่กี่รายการต่อครั้งตามภารกิจ สิ่งนี้จะเพิ่มขั้นตอนการเรียกใช้และรูปแบบความล้มเหลวของมันเอง ดังนั้นจึงควรใช้เมื่อการกรองและการจัดการไม่เพียงพออีกต่อไป
นอกจากนี้ยังมีเส้นทางโปรโตคอล Model Context Protocol ได้กำหนดมาตรฐานว่าเซิร์ฟเวอร์จะเปิดเผยเครื่องมือแก่ไคลเอนต์ได้อย่างไร และเซิร์ฟเวอร์ MCP ที่รองรับโดยเอกสาร OpenAPI ของคุณจะให้จุดรวมระบบเพียงจุดเดียวแทนที่จะแยกตามแต่ละเฟรมเวิร์ก บทความอธิบายของเราเกี่ยวกับ MCP คืออะไร ครอบคลุมโมเดล และ การสร้างเซิร์ฟเวอร์ MCP ด้วย Apidog ครอบคลุมวิธีการสร้าง

สเปกต้องถูกต้องก่อนเป็นอันดับแรก
การสร้างจะผลักดันปัญหาคุณภาพไปสู่ต้นน้ำ คำอธิบายที่คลุมเครือในเอกสาร OpenAPI ของคุณจะกลายเป็นคำอธิบายเครื่องมือที่คลุมเครือ และโมเดลจะเลือกปลายทางผิด ฟิลด์เสริมที่เซิร์ฟเวอร์ต้องการจริงๆ จะกลายเป็นเครื่องมือที่เอเจนต์เรียกใช้ผิดพลาดตั้งแต่ครั้งแรก
ดังนั้น ควรตรวจสอบสเปกผ่านมุมมองของเอเจนต์ก่อนที่จะสร้างสิ่งใดๆ:
- ทุกการดำเนินการมี
operationIdและมันอ่านเหมือนคำกริยาบวกคำนาม - ทุกการดำเนินการมีคำอธิบายที่บอกว่ามันทำอะไร เปลี่ยนแปลงอะไร และไม่ควรใช้เมื่อใด “ลบผู้ใช้” ไม่เพียงพอ “ลบผู้ใช้และเซสชันทั้งหมดของพวกเขาอย่างถาวร ไม่สามารถย้อนกลับได้ ใช้ deactivateUser เพื่อปิดใช้งานการเข้าถึงชั่วคราว” เป็นตัวอย่างที่ดีกว่า
- ทุกพารามิเตอร์มีคำอธิบายพร้อมหน่วยและรูปแบบ
amountนั้นคลุมเครือ “จำนวนเงินเป็นเซ็นต์ ขั้นต่ำ 50” ไม่คลุมเครือ - Enums ถูกประกาศแทนที่จะอธิบายด้วยข้อความ เพื่อให้โมเดลได้รับชุดข้อมูลที่ปิดสนิทแทนที่จะต้องเดา
- Required (ที่จำเป็น) ต้องแม่นยำ สเปกมีแนวโน้มที่จะทำเครื่องหมายทุกอย่างว่าเป็น optional ซึ่งจะผลักดันความล้มเหลวในการตรวจสอบความถูกต้องไปสู่ช่วงรันไทม์
นี่คือสุขอนามัยของสเปกทั่วไป และมันให้ผลดีสองเท่า เพราะข้อความเดียวกันนี้ถูกใช้ในการสร้างเอกสารที่เผยแพร่ของคุณ ใน Apidog ทั้งสเปก เอกสาร เซิร์ฟเวอร์จำลอง และการทดสอบมาจากโปรเจกต์เดียวกัน ดังนั้นการปรับปรุงคำอธิบายให้รัดกุมขึ้นจะช่วยปรับปรุงทุกอย่างพร้อมกัน คู่มือของเราเกี่ยวกับการ จัดการเวอร์ชัน API ใน Apidog ครอบคลุมอีกครึ่งหนึ่งของการรักษาความถูกต้องของเครื่องมือที่สร้างขึ้นมาเมื่อเวลาผ่านไป
แชร์ชุดเครื่องมือ อย่าคัดลอก
ชุดเครื่องมือที่สร้างขึ้นมาคือการกำหนดค่า และการกำหนดค่าที่อยู่ในการเช็คเอาต์ของนักพัฒนาคนเดียวก็จะมีการเปลี่ยนแปลง (drift) เช่นเดียวกับ schema ที่เขียนด้วยมือ รายการตัวกรอง รายการที่อนุญาต (allowlist) และเวอร์ชันสเปกที่ปักหมุดไว้ควรเป็นสิ่งประดิษฐ์ที่ใช้ร่วมกัน ซึ่งมีการควบคุมเวอร์ชันอยู่ถัดจากสเปกที่มา
บางแพลตฟอร์มทำให้สิ่งนี้เป็นหน่วยเริ่มต้น ใน Sharkly เอเจนต์คือการกำหนดค่าการทำงานที่บันทึกไว้แทนที่จะเป็นพรอมต์แบบครั้งเดียว: คำสั่ง, รันไทม์, ทักษะ, ที่เก็บ และการตั้งค่าการรันจะติดไปกับมันและสามารถแชร์ข้าม Space ได้ ดังนั้นการตั้งค่าเครื่องมือที่ใช้งานได้จะกลายเป็นสิ่งที่ทีมนำกลับมาใช้ใหม่ แทนที่จะเป็นสิ่งที่แต่ละคนต้องสร้างใหม่ รันไทม์ที่อยู่เบื้องหลังยังคงเป็น Claude Code, Codex หรืออะไรก็ตามที่คุณใช้อยู่แล้ว สิ่งที่เปลี่ยนแปลงคือการกำหนดค่ารอบๆ ไม่ได้อยู่แค่ในเครื่องอีกต่อไป

การทดสอบเครื่องมือที่สร้างขึ้นมา
เครื่องมือที่สร้างขึ้นมาอาจล้มเหลวในแบบที่เครื่องมือที่เขียนด้วยมือไม่เป็น ดังนั้นจึงควรทดสอบทั้งการสร้างและการเรียกใช้
เริ่มต้นด้วยการตรวจสอบ schema round-trip สำหรับเครื่องมือที่สร้างขึ้นแต่ละรายการ ให้สร้างตัวอย่างที่ถูกต้องจาก schema และส่งไป หากมีการตอบกลับ 400 หรือ 422 หมายความว่า tool schema และเซิร์ฟเวอร์ไม่ตรงกัน และสเปกคือสิ่งที่ต้องแก้ไข
จากนั้นทดสอบการเลือก เขียนชุดพรอมต์งานขนาดเล็กที่มีเครื่องมือที่ถูกต้องที่ทราบกันดี รันมัน และบันทึกว่าโมเดลเลือกเครื่องมือใด นี่คือชุดการถดถอยราคาถูกที่ช่วยจับข้อผิดพลาดในวันที่มีคนเปลี่ยนชื่อการดำเนินการหรือทำให้คำอธิบายสั้นลง เนื่องจากผลลัพธ์ไม่สามารถคาดเดาได้ ให้ยืนยันที่ชื่อเครื่องมือแทนที่จะเป็นอาร์กิวเมนต์ที่แน่นอน ตามแนวทางในคู่มือของเราเกี่ยวกับการ ทดสอบเอเจนต์ที่ไม่สามารถคาดเดาได้
สุดท้าย ให้รันเอเจนต์กับ mock ก่อนที่จะนำไปใช้จริง เซิร์ฟเวอร์จำลองที่สร้างจากสเปกเดียวกันจะให้การตอบกลับที่สมจริงโดยไม่มีผลข้างเคียง และช่วยให้คุณสามารถจำลองข้อผิดพลาด 500 และการหมดเวลาที่ตรรกะการลองใหม่ของคุณควรจะจัดการได้
สิ่งที่คุณจะได้รับ
สเปกคือสัญญา และรายการเครื่องมือควรเป็นภาพสะท้อนของมัน ไม่ใช่สำเนาคู่ขนานที่ดูแลด้วยมือ สร้างเครื่องมือ กรองอย่างเข้มงวด รักษาคำอธิบายให้ถูกต้อง และทดสอบทั้งโครงสร้างและการเลือก
เริ่มต้นด้วยการส่งออกเอกสาร OpenAPI ของคุณและนับการดำเนินการที่ไม่มีคำอธิบาย ตัวเลขนั้นคือปริมาณงานที่คั่นกลางระหว่างคุณกับเครื่องมือเอเจนต์ที่คุณสามารถไว้วางใจได้ ดาวน์โหลด Apidog หากคุณต้องการให้สเปก, mocks และการทดสอบอยู่ในที่เดียวขณะที่คุณแก้ไข
คำถามที่พบบ่อย
- ฉันสามารถสร้างเครื่องมือจากเอกสาร Swagger 2.0 ได้หรือไม่? ได้ แต่ต้องแปลงเป็น OpenAPI 3.x ก่อน โมเดล body ของ 2.0 แตกต่างกันมากพอที่ตัวสร้างจะจัดการได้ไม่สอดคล้องกัน และ 3.x คือสิ่งที่เครื่องมือปัจจุบันมุ่งเป้าไปที่ ที่เก็บ OpenAPI Specification ได้บันทึกความแตกต่างไว้
- โมเดลสามารถจัดการเครื่องมือได้กี่ชิ้นพร้อมกัน? ความแม่นยำจะเริ่มลดลงก่อนที่จะถึงขีดจำกัดทางเทคนิค และเพดานในทางปฏิบัติมักจะอยู่ที่ประมาณสองสามสิบรายการ ให้ถือว่ารายการใดๆ ที่เกินกว่านั้นเป็นสัญญาณให้กรองด้วยแท็กหรือจัดทำรายการที่อนุญาต แทนที่จะเป็นข้อจำกัดที่ต้องทดสอบ
- ชื่อเครื่องมือควรตรงกับ
operationIdเป๊ะๆ เลยหรือไม่? ใช่ เมื่อoperationIdอ่านเข้าใจได้ มันช่วยให้คุณค้นหาโดยตรงจากการเรียกเครื่องมือกลับไปยังการดำเนินการในสเปก ซึ่งทำให้การติดตามและดีบักง่ายขึ้นมาก เปลี่ยนชื่อในสเปกหากชื่อไม่ดี ไม่ใช่ในตัวสร้าง - แล้ว GraphQL API ล่ะ? แนวคิดเดียวกันนี้ใช้ได้กับแหล่งที่มาที่แตกต่างกัน: ตรวจสอบ schema และสร้างเครื่องมือหนึ่งชิ้นต่อหนึ่ง query หรือ mutation ปัญหาปริมาณจะแย่กว่าเนื่องจาก GraphQL schema เปิดเผยพื้นผิวที่กว้างกว่า ดังนั้นการกรองจึงมีความสำคัญมากยิ่งขึ้น
- ฉันยังจำเป็นต้องเขียนเครื่องมือด้วยมืออยู่บ้างหรือไม่? เล็กน้อย เครื่องมือแบบคอมโพสิตที่เชื่อมโยงหลายการเรียกเข้าเป็นการกระทำเดียว และเครื่องมือที่ครอบคลุมสิ่งอื่นที่ไม่ใช่ HTTP ยังคงต้องเขียนด้วยมือ ประเด็นคือ wrapper แบบหนึ่งปลายทางที่ทำเป็นประจำจะหยุดเป็นงานที่ต้องทำด้วยมือ
- ฉันจะหยุดไม่ให้เอเจนต์เรียกใช้ปลายทางการเขียน (write endpoints) ระหว่างการทดสอบได้อย่างไร? สร้างชุดเครื่องมือแบบอ่านอย่างเดียวสำหรับการรันทดสอบโดยการกรองตามเมธอด HTTP และชี้เอเจนต์ไปที่ mock สำหรับสิ่งใดก็ตามที่มีการเขียน โพสต์ของเราเกี่ยวกับ เหตุใดเอเจนต์จึงควรเรียกใช้ mocks ไม่ใช่ production ครอบคลุมการตั้งค่า
