Gestire le chiamate agli strumenti nelle risposte in streaming
Analizzi le risposte in streaming che contengono argomenti di chiamate a funzioni ricevuti token per token, memorizzi temporaneamente i frammenti JSON e avvii l'esecuzione dello strumento solo quando la chiamata è completa.
Gestire le chiamate agli strumenti nelle risposte in streaming è una lezione AI Engineering Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Engineering Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Engineering Academy include 4 lezioni in totale.
Le chiamate agli strumenti arrivano in modo diverso negli stream
Quando un LLM decide di chiamare una funzione, la struttura della risposta cambia. Invece di una stringa content, il delta contiene un array tool_calls. Tuttavia, in una risposta trasmessa in streaming, gli argomenti della chiamata alla funzione arrivano token per token come stringa JSON parziale: non si riceve un oggetto JSON completo in un singolo chunk. Deve memorizzare questi frammenti e ricomporre il JSON completo prima di poter analizzare ed eseguire la chiamata allo strumento.
# 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 workRilevamento di una chiamata allo strumento nello stream
Controlli il finish_reason di ogni chunk per sapere quando aspettarsi le chiamate agli strumenti. Quando finish_reason è 'tool_calls', il modello ha deciso di chiamare una funzione e lo streaming sta terminando. Quando finish_reason è 'stop', il modello ha prodotto una normale risposta testuale. Durante lo streaming, verifichi se chunk.choices[0].delta.tool_calls è diverso da None per identificare i frammenti degli argomenti delle chiamate agli strumenti.
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_typeMemorizzare gli argomenti delle chiamate agli strumenti
Utilizzi un dizionario indicizzato dall'indice della chiamata allo strumento per accumulare i frammenti degli argomenti provenienti da ogni chunk. L'indice identifica quale chiamata allo strumento è in fase di streaming: il modello può chiamare più funzioni in un'unica risposta. Per ogni chunk con un delta tool_calls non nullo, aggiunga il frammento degli argomenti alla voce appropriata del buffer, identificata dall'indice della chiamata allo strumento.
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)Analizzare ed eseguire le chiamate agli strumenti
Dopo la fine dello streaming, quando dispone delle stringhe complete degli argomenti, analizzi ciascuna con json.loads e inoltri la chiamata alla funzione Python appropriata. Esegua le chiamate agli strumenti nell'ordine in cui sono state richieste (oppure in parallelo, se sono indipendenti), quindi formatti i risultati come messaggi di risposta degli strumenti da inviare nella successiva chiamata all'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_messagesIl ciclo completo delle chiamate agli strumenti in streaming
Il flusso completo delle chiamate agli strumenti in streaming richiede un ciclo di conversazione a più turni. La prima richiesta può restituire chiamate agli strumenti; lei le esegue e aggiunge i risultati alla cronologia dei messaggi; una seconda richiesta restituisce la risposta testuale finale. Questo ciclo può ripetersi più volte se il modello sceglie di effettuare ulteriori chiamate agli strumenti sulla base dei risultati precedenti.
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'Trasmettere il testo mentre si memorizzano le chiamate agli strumenti
Nella pratica, è opportuno trasmettere immediatamente il testo al client e, contemporaneamente, memorizzare nel buffer gli argomenti delle eventuali chiamate agli strumenti. È necessario distinguere tra i chunk che contengono content (da trasmettere immediatamente) e quelli che contengono tool_calls (da memorizzare per l'esecuzione successiva). Solo dopo la fine dello streaming, quando le chiamate agli strumenti sono complete, è possibile eseguire gli strumenti e proseguire.
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 neededEsecuzione parallela delle chiamate agli strumenti
Quando il modello restituisce più chiamate agli strumenti simultaneamente (una funzionalità chiamata parallel function calling), le esegua in parallelo con asyncio.gather invece che in sequenza. L'esecuzione sequenziale aggiunge latenza inutile: se il modello chiama contemporaneamente un'API meteorologica e una query al database, non c'è motivo di attendere il completamento di una prima di avviare l'altra.
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)Trasmettere la risposta finale dopo l'uso degli strumenti
Dopo aver eseguito le chiamate agli strumenti e aggiunto i risultati alla cronologia dei messaggi, effettui una seconda richiesta in streaming per ottenere la risposta finale del modello. Trasmetta questa risposta direttamente al client. Questo schema a due richieste (richiesta iniziale con chiamate agli strumenti e richiesta successiva con i risultati degli strumenti) è il ciclo standard di un turno di un agente; entrambe le richieste possono trasmettere il testo all'interfaccia.
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 tokenVisualizzare nell'interfaccia lo stato di avanzamento delle chiamate agli strumenti
Gli utenti dovrebbero vedere che cosa sta facendo l'agente mentre attendono il completamento delle chiamate agli strumenti. Prima di eseguire gli strumenti, trasmetta al client un evento di stato che indichi quale funzione viene chiamata e con quali argomenti. Al termine dell'esecuzione, trasmetta uno stato di completamento. Questa trasparenza migliora notevolmente la reattività percepita e aiuta gli utenti a eseguire il debug di un uso imprevisto degli strumenti.
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...Gestione degli errori nei flussi di chiamate agli strumenti
L'esecuzione degli strumenti può non riuscire: le API restituiscono errori, le funzioni generano eccezioni e l'analisi JSON può fallire. Intercetti sempre le eccezioni durante l'esecuzione degli strumenti e restituisca al modello una risposta di errore strutturata. Il modello potrà quindi decidere se riprovare con argomenti diversi, chiamare uno strumento alternativo oppure spiegare all'utente che l'azione richiesta non è riuscita. Non permetta mai che un'eccezione non gestita di uno strumento interrompa il ciclo di streaming.
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'])),
}Confronto tra streaming e non-streaming per gli agenti
Per le applicazioni basate su agenti, lo streaming aggiunge complessità, ma offre un valore significativo per l'esperienza utente. Senza streaming, l'utente non vede nulla durante un ciclo di chiamate agli strumenti in più passaggi, che potrebbe durare 10-30 secondi. Con lo streaming, vede il testo intermedio, le notifiche delle chiamate agli strumenti e la risposta finale comparire token dopo token. La maggiore complessità del codice vale generalmente la pena nelle applicazioni interattive, mentre gli agenti in background che operano autonomamente possono usare il non-streaming per ottenere un codice più semplice.
Verifica rapida
Verifichi la Sua comprensione delle chiamate agli strumenti nelle risposte trasmesse in streaming in questa lezione.
Riepilogo della lezione
In questa lezione ha imparato che gli argomenti delle chiamate agli strumenti arrivano come frammenti JSON nelle risposte in streaming e devono essere memorizzati in base all'indice della chiamata allo strumento prima dell'analisi; che occorre eseguire gli strumenti al termine dello streaming e poi effettuare una seconda richiesta in streaming per la risposta finale; e che l'esecuzione parallela con asyncio.gather riduce al minimo la latenza quando il modello chiama più funzioni simultaneamente. Intercetti sempre gli errori di esecuzione degli strumenti per evitare l'interruzione del ciclo dell'agente. Nella prossima lezione implementeremo la memorizzazione nella cache delle risposte per ridurre i costi delle API.
Impara Python con un tutor IA — gratis
Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.
- Corsi
- 30
- Lezioni
- 120
Domande Frequenti
La lezione «Gestire le chiamate agli strumenti nelle risposte in streaming» è gratuita?
Sì — il testo completo di «Gestire le chiamate agli strumenti nelle risposte in streaming» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Engineering Academy, passa a CoddyKit PRO. Il corso AI Engineering Academy include 4 lezioni in totale.
Cosa imparerò in «Gestire le chiamate agli strumenti nelle risposte in streaming»?
Analizzi le risposte in streaming che contengono argomenti di chiamate a funzioni ricevuti token per token, memorizzi temporaneamente i frammenti JSON e avvii l'esecuzione dello strumento solo quando… Eserciti AI Engineering Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare AI Engineering Academy?
Non è richiesta alcuna esperienza precedente. AI Engineering Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Gestire le chiamate agli strumenti nelle risposte in streaming»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione AI Engineering Academy?
Sì. Ogni lezione AI Engineering Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Comprendere lo streaming dei token
- Consumare gli stream con l'SDK Python
- Streaming in FastAPI con Server-Sent Events
- Gestire le chiamate agli strumenti nelle risposte in streaming