การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง
แยกวิเคราะห์การตอบกลับแบบต่อเนื่องที่มีอาร์กิวเมนต์การเรียกฟังก์ชันเข้ามาทีละโทเค็น เก็บชิ้นส่วน JSON ไว้ในบัฟเฟอร์ และเริ่มเรียกใช้เครื่องมือเมื่อการเรียกเสร็จสมบูรณ์เท่านั้น
การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง เป็นบทเรียน AI Engineering Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Engineering Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
การเรียกใช้เครื่องมือมาถึงแตกต่างกันในสตรีม
เมื่อ LLM ตัดสินใจเรียกใช้ฟังก์ชัน โครงสร้างการตอบกลับจะเปลี่ยนไป แทนที่จะมีสตริง content เดลตาจะมีอาร์เรย์ tool_calls แต่ในการตอบกลับแบบสตรีม อาร์กิวเมนต์ของการเรียกใช้ฟังก์ชันจะมาทีละส่วน ทีละโทเค็นในรูปแบบสตริง JSON บางส่วน คุณจะไม่ได้รับออบเจ็กต์ JSON ที่สมบูรณ์ภายในชังก์เดียว ดังนั้นต้องบัฟเฟอร์ส่วนย่อยเหล่านี้และประกอบ JSON ที่สมบูรณ์ขึ้นมาใหม่ก่อนจึงจะแยกวิเคราะห์และเรียกใช้เครื่องมือได้
# In a non-streaming response, tool call is complete:
# choice.message.tool_calls[0].function.arguments = '{"city": "Paris"}'
# In a streaming response, arguments arrive in pieces:
# chunk 1: delta.tool_calls[0].function.arguments = '{'
# chunk 2: delta.tool_calls[0].function.arguments = '"city"'
# chunk 3: delta.tool_calls[0].function.arguments = ': "'
# chunk 4: delta.tool_calls[0].function.arguments = 'Paris'
# chunk 5: delta.tool_calls[0].function.arguments = '"}'
# You must concatenate these before JSON.parse can workการตรวจจับการเรียกใช้เครื่องมือในสตรีม
ตรวจสอบ finish_reason ของแต่ละส่วนย่อยเพื่อดูว่าควรคาดว่าจะมีการเรียกใช้เครื่องมือเมื่อใด เมื่อ finish_reason เป็น 'tool_calls' แสดงว่าโมเดลตัดสินใจเรียกใช้ฟังก์ชันและสตรีมกำลังจะสิ้นสุดลง เมื่อ finish_reason เป็น 'stop' แสดงว่าโมเดลสร้างคำตอบข้อความตามปกติ ระหว่างการสตรีม ให้ตรวจสอบว่า chunk.choices[0].delta.tool_calls ไม่ใช่ค่า None เพื่อระบุส่วนย่อยของอาร์กิวเมนต์ในการเรียกใช้เครื่องมือ
async def detect_stream_type(messages, tools):
stream = await async_client.chat.completions.create(
model='gpt-4o-mini',
messages=messages,
tools=tools,
stream=True,
)
response_type = 'text'
async for chunk in stream:
choice = chunk.choices[0]
if choice.delta.tool_calls: # tool call fragment arriving
response_type = 'tool_call'
if choice.finish_reason == 'tool_calls':
print('Model wants to call a function')
elif choice.finish_reason == 'stop':
print('Normal text response')
return response_typeการบัฟเฟอร์อาร์กิวเมนต์การเรียกใช้เครื่องมือ
ใช้พจนานุกรมที่มีดัชนีการเรียกใช้เครื่องมือเป็นคีย์ เพื่อรวบรวมส่วนย่อยของอาร์กิวเมนต์จากแต่ละส่วนย่อย ดัชนีจะระบุว่ากำลังสตรีมการเรียกใช้เครื่องมือรายการใดอยู่ เนื่องจากโมเดลอาจเรียกใช้หลายฟังก์ชันในคำตอบเดียว สำหรับแต่ละส่วนย่อยที่มีเดลตาของ tool_calls ซึ่งไม่เป็นค่าว่าง ให้เพิ่มส่วนย่อยของอาร์กิวเมนต์ลงในรายการบัฟเฟอร์ที่ตรงกัน โดยใช้ดัชนีการเรียกใช้เครื่องมือเป็นคีย์
from collections import defaultdict
async def collect_streamed_tool_calls(messages, tools):
stream = await async_client.chat.completions.create(
model='gpt-4o-mini',
messages=messages,
tools=tools,
stream=True,
)
tool_call_buffers = defaultdict(lambda: {'name': '', 'id': '', 'arguments': ''})
text_buffer = ''
async for chunk in stream:
delta = chunk.choices[0].delta
if delta.content: # text content
text_buffer += delta.content
if delta.tool_calls:
for tc in delta.tool_calls:
idx = tc.index
if tc.id:
tool_call_buffers[idx]['id'] = tc.id
if tc.function.name:
tool_call_buffers[idx]['name'] += tc.function.name
if tc.function.arguments:
tool_call_buffers[idx]['arguments'] += tc.function.arguments
return text_buffer, dict(tool_call_buffers)การแยกวิเคราะห์และดำเนินการเรียกใช้เครื่องมือ
หลังจากสตรีมสิ้นสุดลงและคุณมีสตริงอาร์กิวเมนต์ที่สมบูรณ์แล้ว ให้แยกวิเคราะห์แต่ละรายการด้วย json.loads และส่งต่อไปยังฟังก์ชัน Python ที่เหมาะสม ดำเนินการเรียกใช้เครื่องมือตามลำดับที่ร้องขอ หรือดำเนินการแบบขนานหากไม่ขึ้นต่อกัน จากนั้นจัดรูปแบบผลลัพธ์เป็นข้อความตอบกลับจากเครื่องมือเพื่อส่งกลับไปในการเรียก API ครั้งถัดไป
import json
# Example tool registry
tools_registry = {
'get_weather': lambda city, unit='celsius': {'temp': 22, 'desc': 'sunny', 'city': city},
'search_docs': lambda query, top_k=3: [{'title': 'Doc 1', 'snippet': 'Relevant info...'}],
}
def execute_tool_calls(tool_call_buffers: dict) -> list[dict]:
tool_messages = []
for idx in sorted(tool_call_buffers.keys()):
tc = tool_call_buffers[idx]
func_name = tc['name']
args = json.loads(tc['arguments'])
if func_name in tools_registry:
result = tools_registry[func_name](**args)
else:
result = {'error': f'Unknown function: {func_name}'}
tool_messages.append({
'role': 'tool',
'tool_call_id': tc['id'],
'content': json.dumps(result),
})
return tool_messagesลูปการเรียกใช้เครื่องมือแบบสตรีมที่สมบูรณ์
กระบวนการเรียกใช้เครื่องมือแบบสตรีมที่สมบูรณ์จำเป็นต้องมี ลูปการสนทนาหลายรอบ คำขอแรกอาจส่งคืนการเรียกใช้เครื่องมือ คุณจะดำเนินการเรียกใช้เหล่านั้นและเพิ่มผลลัพธ์ลงในประวัติข้อความ จากนั้นคำขอที่สองจะส่งคืนคำตอบข้อความสุดท้าย ลูปนี้อาจทำซ้ำหลายครั้งหากโมเดลเลือกเรียกใช้เครื่องมือเพิ่มเติมโดยอิงจากผลลัพธ์ก่อนหน้า
async def streaming_agent_loop(initial_messages, tools):
messages = list(initial_messages)
max_iterations = 5
for iteration in range(max_iterations):
text, tool_calls = await collect_streamed_tool_calls(messages, tools)
if tool_calls:
# Append assistant message with tool calls
assistant_msg = {
'role': 'assistant',
'content': text or None,
'tool_calls': [
{'id': tc['id'], 'type': 'function',
'function': {'name': tc['name'], 'arguments': tc['arguments']}}
for tc in tool_calls.values()
]
}
messages.append(assistant_msg)
# Execute tools and append results
tool_results = execute_tool_calls(tool_calls)
messages.extend(tool_results)
else:
# No more tool calls — final text response
print('Final answer:', text)
return text
return 'Max iterations reached'การสตรีมข้อความขณะบัฟเฟอร์การเรียกใช้เครื่องมือ
ในทางปฏิบัติ คุณควร สตรีมข้อความไปยังไคลเอ็นต์ทันที พร้อมกับบัฟเฟอร์อาร์กิวเมนต์ของการเรียกใช้เครื่องมือไปพร้อมกัน ซึ่งจำเป็นต้องแยกแยะระหว่างส่วนย่อยที่มี content ซึ่งควรสตรีมทันที กับส่วนย่อยที่มี tool_calls ซึ่งควรบัฟเฟอร์ไว้เพื่อดำเนินการภายหลัง คุณจะดำเนินการเครื่องมือและทำงานต่อได้ก็ต่อเมื่อสตรีมสิ้นสุดลงและการเรียกใช้เครื่องมือเสร็จสมบูรณ์แล้ว
async def stream_with_tools(messages, tools):
stream = await async_client.chat.completions.create(
model='gpt-4o-mini', messages=messages, tools=tools, stream=True
)
tool_buffers = defaultdict(lambda: {'name': '', 'id': '', 'arguments': ''})
text_parts = []
async for chunk in stream:
delta = chunk.choices[0].delta
finish = chunk.choices[0].finish_reason
if delta.content:
text_parts.append(delta.content)
yield ('text', delta.content) # stream to client immediately
if delta.tool_calls:
for tc in delta.tool_calls:
if tc.id: tool_buffers[tc.index]['id'] = tc.id
if tc.function.name: tool_buffers[tc.index]['name'] += tc.function.name
if tc.function.arguments: tool_buffers[tc.index]['arguments'] += tc.function.arguments
if finish == 'tool_calls':
yield ('tool_calls', dict(tool_buffers)) # signal tool execution neededการดำเนินการเรียกใช้เครื่องมือแบบขนาน
เมื่อโมเดลส่งคืนการเรียกใช้เครื่องมือหลายรายการพร้อมกัน ซึ่งเป็นความสามารถที่เรียกว่า การเรียกใช้ฟังก์ชันแบบขนาน ให้ดำเนินการแบบขนานด้วย asyncio.gather แทนการดำเนินการตามลำดับ การดำเนินการตามลำดับเพิ่มความหน่วงโดยไม่จำเป็น หากโมเดลเรียกใช้ API สภาพอากาศและค้นหาฐานข้อมูลพร้อมกัน ก็ไม่มีเหตุผลที่จะต้องรอให้รายการหนึ่งเสร็จก่อนจึงเริ่มอีกรายการ
import asyncio
async def execute_tool_calls_parallel(tool_call_buffers: dict) -> list[dict]:
async def execute_one(idx, tc):
func_name = tc['name']
args = json.loads(tc['arguments'])
if func_name in async_tools_registry:
result = await async_tools_registry[func_name](**args)
else:
result = {'error': f'Unknown function: {func_name}'}
return {
'role': 'tool',
'tool_call_id': tc['id'],
'content': json.dumps(result),
}
tasks = [execute_one(idx, tc) for idx, tc in sorted(tool_call_buffers.items())]
return await asyncio.gather(*tasks)การสตรีมคำตอบสุดท้ายหลังใช้เครื่องมือ
หลังจากดำเนินการเรียกใช้เครื่องมือและเพิ่มผลลัพธ์ลงในประวัติข้อความแล้ว ให้ส่ง คำขอแบบสตรีมครั้งที่สอง เพื่อรับคำตอบสุดท้ายจากโมเดล สตรีมคำตอบนี้ไปยังไคลเอ็นต์โดยตรง รูปแบบสองคำขอนี้ ซึ่งประกอบด้วยคำขอเริ่มต้นที่มีการเรียกใช้เครื่องมือและคำขอติดตามผลที่มีผลลัพธ์จากเครื่องมือ เป็นวงรอบการทำงานมาตรฐานของเอเจนต์ และคำขอทั้งสองรายการสามารถสตรีมข้อความไปยังส่วนติดต่อผู้ใช้ได้
async def full_tool_calling_stream(question: str, tools: list):
messages = [{'role': 'user', 'content': question}]
# First request: may produce tool calls
tool_buffers = {}
text1 = ''
async for event_type, data in stream_with_tools(messages, tools):
if event_type == 'text':
text1 += data
yield data # stream partial text if any
elif event_type == 'tool_calls':
tool_buffers = data
if tool_buffers:
# Execute tools, then get final streaming answer
tool_results = await execute_tool_calls_parallel(tool_buffers)
messages += [{ # assistant tool call message
'role': 'assistant',
'tool_calls': [
{'id': tc['id'], 'type': 'function',
'function': {'name': tc['name'], 'arguments': tc['arguments']}}
for tc in tool_buffers.values()
]
}] + tool_results
# Second request: final answer streams directly
async for token in token_stream(messages): # from earlier lesson
yield tokenการแสดงความคืบหน้าของการเรียกใช้เครื่องมือในส่วนติดต่อผู้ใช้
ผู้ใช้ควรเห็น สิ่งที่เอเจนต์กำลังดำเนินการ ระหว่างรอให้การเรียกใช้เครื่องมือเสร็จสิ้น ก่อนดำเนินการเครื่องมือ ให้สตรีมเหตุการณ์สถานะไปยังไคลเอ็นต์ โดยระบุว่ากำลังเรียกใช้ฟังก์ชันใดและใช้อาร์กิวเมนต์อะไร หลังดำเนินการเสร็จแล้ว ให้สตรีมสถานะเสร็จสมบูรณ์ ความโปร่งใสนี้ช่วยเพิ่มความรู้สึกว่าระบบตอบสนองได้ดีขึ้นอย่างมาก และช่วยให้ผู้ใช้ตรวจสอบการใช้เครื่องมือที่ไม่คาดคิดได้
import json
async def stream_with_progress(question, tools):
messages = [{'role': 'user', 'content': question}]
tool_buffers = {}
async for event_type, data in stream_with_tools(messages, tools):
if event_type == 'tool_calls':
tool_buffers = data
for tc in tool_buffers.values():
args = json.loads(tc['arguments'])
yield f'data: {json.dumps({"type": "tool_start", "function": tc["name"], "args": args})}\n\n'
result = tools_registry.get(tc['name'], lambda **kw: {})(** args)
yield f'data: {json.dumps({"type": "tool_done", "function": tc["name"]})}\n\n'
# Then stream final answer...การจัดการข้อผิดพลาดในสตรีมการเรียกใช้เครื่องมือ
การดำเนินการเครื่องมืออาจล้มเหลวได้ เช่น API ส่งคืนข้อผิดพลาด ฟังก์ชันโยนข้อยกเว้น หรือการแยกวิเคราะห์ JSON ล้มเหลว ให้ดักจับข้อยกเว้นระหว่างการดำเนินการเครื่องมือเสมอ และส่งคืนคำตอบข้อผิดพลาดที่มีโครงสร้างให้โมเดล จากนั้นโมเดลอาจตัดสินใจลองใหม่ด้วยอาร์กิวเมนต์อื่น เรียกใช้เครื่องมือสำรอง หรืออธิบายให้ผู้ใช้ทราบว่าการดำเนินการที่ร้องขอล้มเหลว อย่าปล่อยให้ข้อยกเว้นจากเครื่องมือที่ไม่ได้ดักจับทำให้ลูปการสตรีมล้มเหลว
def safe_execute_tool(func_name: str, args: dict) -> str:
try:
if func_name not in tools_registry:
return json.dumps({'error': f'Function {func_name!r} not found'})
result = tools_registry[func_name](**args)
return json.dumps(result)
except TypeError as e:
return json.dumps({'error': f'Invalid arguments: {str(e)}'})
except Exception as e:
return json.dumps({'error': f'Execution failed: {str(e)}'})
# Tool result message with error handled
tool_message = {
'role': 'tool',
'tool_call_id': tc['id'],
'content': safe_execute_tool(tc['name'], json.loads(tc['arguments'])),
}การเปรียบเทียบแบบสตรีมกับไม่สตรีมสำหรับเอเจนต์
สำหรับแอปพลิเคชันแบบเอเจนต์ การสตรีมเพิ่มความซับซ้อน แต่ก็มอบคุณค่าด้านประสบการณ์ผู้ใช้เป็นอย่างมาก หากไม่ใช้การสตรีม ผู้ใช้จะไม่เห็นสิ่งใดระหว่างลูปการเรียกใช้เครื่องมือหลายขั้นตอนที่อาจใช้เวลา 10–30 วินาที แต่เมื่อใช้การสตรีม ผู้ใช้จะเห็นข้อความระหว่างทาง การแจ้งเตือนการเรียกใช้เครื่องมือ และคำตอบสุดท้ายปรากฏขึ้นทีละโทเค็น โดยทั่วไป ความซับซ้อนของโค้ดที่เพิ่มขึ้นคุ้มค่าสำหรับแอปพลิเคชันแบบโต้ตอบ แต่เอเจนต์เบื้องหลังที่ทำงานโดยอัตโนมัติสามารถใช้แบบไม่สตรีมเพื่อให้โค้ดเรียบง่ายขึ้นได้
ตรวจสอบความเข้าใจ
ทดสอบความเข้าใจเกี่ยวกับการเรียกใช้เครื่องมือในคำตอบแบบสตรีมจากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า อาร์กิวเมนต์การเรียกใช้เครื่องมือจะมาถึงในรูปแบบ ส่วนย่อยของ JSON ในคำตอบแบบสตรีม และต้องบัฟเฟอร์ตามดัชนีการเรียกใช้เครื่องมือก่อนแยกวิเคราะห์ จากนั้น ดำเนินการเครื่องมือหลังสตรีมเสร็จสิ้น แล้วส่งคำขอแบบสตรีมครั้งที่สองเพื่อรับคำตอบสุดท้าย และการ ดำเนินการแบบขนาน ด้วย asyncio.gather ช่วยลดความหน่วงเมื่อโมเดลเรียกใช้หลายฟังก์ชันพร้อมกัน ให้ดักจับข้อผิดพลาดจากการดำเนินการเครื่องมือเสมอเพื่อป้องกันไม่ให้ลูปของเอเจนต์ล้มเหลว ต่อไป เราจะนำการแคชคำตอบมาใช้เพื่อลดค่าใช้จ่ายของ API
คำถามที่พบบ่อย
บทเรียน “การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Engineering Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง”
แยกวิเคราะห์การตอบกลับแบบต่อเนื่องที่มีอาร์กิวเมนต์การเรียกฟังก์ชันเข้ามาทีละโทเค็น เก็บชิ้นส่วน JSON ไว้ในบัฟเฟอร์ และเริ่มเรียกใช้เครื่องมือเมื่อการเรียกเสร็จสมบูรณ์เท่านั้น คุณปฏิบัติ AI Engineering Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Engineering Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Engineering Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Engineering Academy นี้ได้ไหม
ได้ บทเรียน AI Engineering Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง
- การรับข้อมูลแบบต่อเนื่องด้วย Python SDK
- การส่งข้อมูลแบบต่อเนื่องใน FastAPI ด้วยเหตุการณ์ที่ส่งจากเซิร์ฟเวอร์
- การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง