AI Engineering Academy · Pelajaran

Menangani Pemanggilan Alat dalam Respons yang Dialirkan

Uraikan respons yang dialirkan dan berisi argumen pemanggilan fungsi yang tiba token demi token, tampung pecahan JSON, lalu jalankan alat hanya setelah pemanggilan selesai.

Pelajaran 4 dari 413 langkah

Menangani Pemanggilan Alat dalam Respons yang Dialirkan adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar AI Engineering Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Engineering Academy mencakup 4 pelajaran total.

Pemanggilan Alat Tiba dengan Cara Berbeda dalam Stream

Saat LLM memutuskan untuk memanggil suatu fungsi, struktur respons berubah. Alih-alih string content, delta berisi larik tool_calls. Namun, dalam respons streaming, argumen pemanggilan fungsi tiba token demi token sebagai string JSON parsial—Anda tidak menerima objek JSON lengkap dalam satu potongan. Anda harus menyangga fragmen-fragmen ini dan menyusun kembali JSON lengkap sebelum dapat mengurainya dan menjalankan pemanggilan alat.

# 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

Mendeteksi Pemanggilan Alat dalam Stream

Periksa finish_reason pada setiap potongan untuk mengetahui kapan harus mengharapkan panggilan alat. Jika finish_reason bernilai 'tool_calls', model telah memutuskan untuk memanggil suatu fungsi dan pengaliran akan berakhir. Jika finish_reason bernilai 'stop', model menghasilkan respons teks biasa. Selama pengaliran, periksa apakah chunk.choices[0].delta.tool_calls bukan None untuk mengidentifikasi fragmen argumen panggilan alat.

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

Menyimpan Sementara Argumen Panggilan Alat

Gunakan kamus dengan indeks panggilan alat sebagai kunci untuk mengumpulkan fragmen argumen dari setiap potongan. Indeks tersebut mengidentifikasi panggilan alat yang sedang dialirkan—model dapat memanggil beberapa fungsi dalam satu respons. Untuk setiap potongan dengan delta tool_calls yang tidak bernilai null, tambahkan fragmen argumen ke entri penyangga yang sesuai berdasarkan indeks panggilan alat.

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)

Mengurai dan Menjalankan Panggilan Alat

Setelah pengaliran berakhir dan Anda telah memperoleh string argumen yang lengkap, uraikan masing-masing string dengan json.loads dan teruskan ke fungsi Python yang sesuai. Jalankan panggilan alat sesuai urutan permintaannya (atau secara paralel jika tidak saling bergantung), lalu format hasilnya sebagai pesan respons alat untuk dikirim kembali dalam panggilan API lanjutan.

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

Siklus Panggilan Alat dengan Pengaliran Lengkap

Alur panggilan alat dengan pengaliran lengkap memerlukan siklus percakapan beberapa giliran. Permintaan pertama mungkin mengembalikan panggilan alat; Anda menjalankannya dan menambahkan hasilnya ke riwayat pesan; permintaan kedua mengembalikan jawaban teks akhir. Siklus ini dapat berulang beberapa kali jika model memilih untuk melakukan panggilan alat tambahan berdasarkan hasil panggilan sebelumnya.

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'

Mengalirkan Teks Sambil Menyimpan Panggilan Alat

Dalam praktiknya, Anda ingin segera mengalirkan teks ke klien sambil menyimpan sementara argumen panggilan alat. Hal ini memerlukan pembedaan antara potongan yang membawa content (segera dialirkan) dan potongan yang membawa tool_calls (disimpan untuk dijalankan nanti). Alat hanya dapat dijalankan dan proses dilanjutkan setelah pengaliran berakhir dan panggilan alat selesai.

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

Eksekusi Panggilan Alat secara Paralel

Saat model mengembalikan beberapa panggilan alat secara bersamaan (fitur yang disebut pemanggilan fungsi paralel), jalankan semuanya secara paralel dengan asyncio.gather, bukan secara berurutan. Eksekusi berurutan menambah latensi yang tidak perlu—jika model memanggil API cuaca dan pencarian basis data secara bersamaan, tidak ada alasan untuk menunggu salah satunya selesai sebelum memulai yang lain.

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)

Mengalirkan Jawaban Akhir Setelah Menggunakan Alat

Setelah menjalankan panggilan alat dan menambahkan hasilnya ke riwayat pesan, buat permintaan kedua dengan pengaliran untuk mendapatkan jawaban akhir model. Alirkan respons ini langsung ke klien. Pola dua permintaan ini (permintaan awal dengan panggilan alat + permintaan lanjutan dengan hasil alat) merupakan siklus giliran agen standar, dan kedua permintaan dapat mengalirkan teksnya ke antarmuka pengguna.

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

Menampilkan Kemajuan Panggilan Alat di Antarmuka Pengguna

Pengguna seharusnya dapat melihat apa yang sedang dilakukan agen saat menunggu panggilan alat selesai. Sebelum menjalankan alat, alirkan peristiwa status ke klien yang menunjukkan fungsi mana yang dipanggil dan argumen yang digunakan. Setelah eksekusi selesai, alirkan status penyelesaian. Transparansi ini sangat meningkatkan persepsi kecepatan respons dan membantu pengguna menelusuri penggunaan alat yang tidak terduga.

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...

Penanganan Kesalahan dalam Pengaliran Panggilan Alat

Eksekusi alat dapat gagal—API mengembalikan kesalahan, fungsi menimbulkan pengecualian, dan penguraian JSON gagal. Selalu tangkap pengecualian saat menjalankan alat dan kembalikan respons kesalahan terstruktur kepada model. Model kemudian dapat memutuskan untuk mencoba lagi dengan argumen berbeda, memanggil alat cadangan, atau menjelaskan kepada pengguna bahwa tindakan yang diminta gagal. Jangan pernah membiarkan pengecualian alat yang tidak tertangkap menghentikan siklus pengaliran.

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'])),
}

Membandingkan Pengaliran dan Tanpa Pengaliran untuk Agen

Untuk aplikasi berbasis agen, pengaliran menambah kompleksitas, tetapi memberikan manfaat besar bagi pengalaman pengguna. Tanpa pengaliran, pengguna tidak melihat apa pun selama siklus pemanggilan alat yang terdiri dari beberapa langkah dan mungkin memerlukan waktu 10–30 detik. Dengan pengaliran, pengguna dapat melihat teks perantara, pemberitahuan panggilan alat, dan jawaban akhir yang muncul token demi token. Kompleksitas kode tambahan biasanya sepadan untuk aplikasi interaktif, tetapi agen latar belakang yang berjalan secara mandiri dapat menggunakan mode tanpa pengaliran agar kodenya lebih sederhana.

Pemeriksaan Singkat

Uji pemahaman Anda tentang panggilan alat dalam respons yang dialirkan dari pelajaran ini.

Rangkuman Pelajaran

Dalam pelajaran ini Anda mempelajari bahwa argumen panggilan alat tiba sebagai fragmen JSON dalam respons yang dialirkan dan harus disimpan sementara berdasarkan indeks panggilan alat sebelum diuraikan, jalankan alat setelah pengaliran selesai lalu buat permintaan kedua dengan pengaliran untuk mendapatkan jawaban akhir, dan eksekusi paralel dengan asyncio.gather meminimalkan latensi saat model memanggil beberapa fungsi secara bersamaan. Selalu tangkap kesalahan eksekusi alat agar siklus agen tidak terhenti. Selanjutnya, kita akan menerapkan penyimpanan respons sementara untuk mengurangi biaya API.

Gratis untuk memulai

Belajar Python dengan tutor AI — gratis

Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.

Kursus
30
Pelajaran
120

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Menangani Pemanggilan Alat dalam Respons yang Dialirkan” gratis?

Ya — teks lengkap “Menangani Pemanggilan Alat dalam Respons yang Dialirkan” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Engineering Academy, upgrade ke CoddyKit PRO. Kursus AI Engineering Academy mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Menangani Pemanggilan Alat dalam Respons yang Dialirkan”?

Uraikan respons yang dialirkan dan berisi argumen pemanggilan fungsi yang tiba token demi token, tampung pecahan JSON, lalu jalankan alat hanya setelah pemanggilan selesai. Kamu berlatih AI Engineering Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai AI Engineering Academy?

Tidak diperlukan pengalaman sebelumnya. AI Engineering Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.

Berapa lama pelajaran “Menangani Pemanggilan Alat dalam Respons yang Dialirkan” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran AI Engineering Academy ini?

Ya. Setiap pelajaran AI Engineering Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Memahami Pengaliran Token
  2. Mengonsumsi Aliran dengan Python SDK
  3. Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server
  4. Menangani Pemanggilan Alat dalam Respons yang Dialirkan
← Kembali ke AI Engineering Academy