0Pricing
AI Agents · レッスン

CLIエージェントでのストリーミング出力

ターミナルインターフェースで、ストリーミングされるトークンを1文字ずつ表示します。

「CLIエージェントでのストリーミング出力」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。

CLIエージェントでストリーミングが重要な理由

ストリーミングを使わない場合、CLIエージェントはLLMの完全な応答が準備できるまで何も出力しません。これには5~30秒かかることがあり、ユーザーはプログラムがクラッシュしたのかと思いながら、何も表示されないターミナルを見つめることになります。

ストリーミングを使うと、生成されたトークンがすぐに表示されるため、即座にフィードバックが得られ、体験が大幅に向上します。

OpenAI SDKでストリーミングを有効にする

chat.completions.create()にstream=Trueを渡します。この呼び出しは、完全なレスポンスオブジェクトではなくジェネレーターを返します。ジェネレーターを反復処理して、チャンクが届くたびに処理します。

import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

stream = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'Explain Python generators in 3 sentences.'}],
    stream=True  # <-- enable streaming
)

# Each chunk arrives as it is generated
for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:
        print(delta.content, end='', flush=True)

print()  # newline after the response is complete

print()とsys.stdout.write()の比較

ストリーミング時は、print(text, end='', flush=True)を使うか、sys.stdout.write(text)の後にsys.stdout.flush()を呼び出します。flush=Trueがないと、Pythonが出力をバッファーにためて一度に表示することがあり、ストリーミングの目的が失われます。

import sys
import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

def stream_to_terminal(messages: list):
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        stream=True
    )

    full_response = ''
    for chunk in stream:
        token = chunk.choices[0].delta.content or ''
        full_response += token

        # Option 1: print with flush
        print(token, end='', flush=True)

        # Option 2: sys.stdout.write + flush
        # sys.stdout.write(token)
        # sys.stdout.flush()

    print()  # final newline
    return full_response

ストリーミング中に完全な応答を収集する

ストリーミングの完了後に、保存、追加処理、表示のために完全な応答テキストが必要になることはよくあります。出力しながらトークンを文字列に蓄積します。

import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

def stream_and_collect(messages: list) -> str:
    full_text = ''

    print('Agent: ', end='', flush=True)

    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        stream=True
    )

    for chunk in stream:
        token = chunk.choices[0].delta.content or ''
        full_text += token
        print(token, end='', flush=True)

    print()  # newline
    return full_text

# The return value contains the complete response for storage
# response_text = stream_and_collect(history)
# history.append({'role': 'assistant', 'content': response_text})

AsyncOpenAIによる非同期ストリーミング

非同期のエージェントアーキテクチャでは、AsyncOpenAIとasync forを使ってストリーミングチャンクを反復処理し、イベントループをブロックしないようにします。

import asyncio
import openai

async def async_stream_agent(query: str) -> str:
    client = openai.AsyncOpenAI(api_key='YOUR_API_KEY')

    stream = await client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': query}],
        stream=True
    )

    full_text = ''
    print('Agent: ', end='', flush=True)

    async for chunk in stream:
        token = chunk.choices[0].delta.content or ''
        full_text += token
        print(token, end='', flush=True)

    print()
    return full_text

# asyncio.run(async_stream_agent('What is asyncio?'))

finish_reasonでストリームの終了を検知する

ストリームの最後のチャンクには、nullではないfinish_reasonが含まれます。これを確認すると、ストリームが終了した理由を把握できます。'stop'は正常終了、'length'は途中で切り詰められたこと、'tool_calls'は関数呼び出しが必要なことを示します。

import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

def stream_with_finish_detection(messages: list):
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        stream=True
    )

    finish_reason = None
    for chunk in stream:
        delta = chunk.choices[0].delta
        if delta.content:
            print(delta.content, end='', flush=True)
        if chunk.choices[0].finish_reason:
            finish_reason = chunk.choices[0].finish_reason

    print()

    if finish_reason == 'length':
        print('[WARNING: Response was truncated. Try increasing max_tokens.]')
    elif finish_reason == 'stop':
        pass  # normal completion

    return finish_reason

ターミナル出力のANSIカラー

ANSIエスケープコードを使うと、ターミナル出力に色を付けられます。エージェントのプレフィックス、ユーザー入力のプロンプト、警告を視覚的に区別するために使用します。coloramaライブラリは、Windowsを含むクロスプラットフォーム環境をサポートします。

# pip install colorama
from colorama import Fore, Style, init
init(autoreset=True)  # reset color after each print

def print_colored_stream(messages: list, client):
    # Print agent prefix in cyan
    print(Fore.CYAN + 'Agent: ' + Style.RESET_ALL, end='', flush=True)

    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        stream=True
    )

    for chunk in stream:
        token = chunk.choices[0].delta.content or ''
        print(token, end='', flush=True)

    print()

# Also useful:
# print(Fore.GREEN + 'Success!') — green
# print(Fore.RED + 'Error!') — red
# print(Fore.YELLOW + 'Warning') — yellow

より豊かなターミナル出力のためのRichライブラリ

richライブラリは、ターミナル上でのMarkdownレンダリング、構文ハイライト付きコードブロック、テーブル、スピナーを提供します。ストリーミングするエージェント出力との相性も優れています。

# pip install rich
from rich.console import Console
from rich.live import Live
from rich.markdown import Markdown

console = Console()

def stream_with_rich(messages: list, client):
    full_text = ''

    with Live(console=console, refresh_per_second=10) as live:
        stream = client.chat.completions.create(
            model='gpt-4o-mini',
            messages=messages,
            stream=True
        )

        for chunk in stream:
            token = chunk.choices[0].delta.content or ''
            full_text += token
            # Render accumulated text as Markdown in real time
            live.update(Markdown(full_text))

    return full_text

ツール呼び出しとのストリーミング

ストリーミングと関数呼び出しを組み合わせると、tool_callsフィールドも分割された形でストリーミングされます。解析する前に、チャンクをまたいでJSON文字列を蓄積します。

import json
import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

def stream_with_tools(messages: list, tools: list) -> dict:
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        tools=tools,
        stream=True
    )

    tool_call_chunks = {}
    for chunk in stream:
        delta = chunk.choices[0].delta
        if delta.tool_calls:
            for tc in delta.tool_calls:
                idx = tc.index
                if idx not in tool_call_chunks:
                    tool_call_chunks[idx] = {'name': '', 'args': ''}
                if tc.function.name:
                    tool_call_chunks[idx]['name'] += tc.function.name
                if tc.function.arguments:
                    tool_call_chunks[idx]['args'] += tc.function.arguments

    # Parse accumulated tool calls
    return {v['name']: json.loads(v['args']) for v in tool_call_chunks.values()}

トークンカウンターの表示

ストリーミング中にトークン数をリアルタイムで表示すると、ユーザーが使用量を把握し、コストを理解するのに役立ちます。stream_options={'include_usage': True}を設定すると、OpenAIのストリームの最後のチャンクに使用量データが含まれます。

import openai

client = openai.OpenAI(api_key='YOUR_API_KEY')

def stream_with_token_count(messages: list):
    stream = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages,
        stream=True,
        stream_options={'include_usage': True}
    )

    for chunk in stream:
        delta = chunk.choices[0].delta
        if delta.content:
            print(delta.content, end='', flush=True)

        # Last chunk includes usage
        if chunk.usage:
            print(f'\n[Tokens: prompt={chunk.usage.prompt_tokens}, '
                  f'completion={chunk.usage.completion_tokens}, '
                  f'total={chunk.usage.total_tokens}]')

ストリーミングのベストプラクティス

CLIエージェントでストリーミング出力を扱う際のベストプラクティスをまとめます。

  • バッファリングを防ぐため、必ずflush=Trueまたはsys.stdout.flush()を使用します
  • ストリーミング後に保存できるよう、トークンを文字列に蓄積します
  • 途中で切り詰められたことを検知するため、finish_reasonを確認します
  • 視認性を高めるため、ANSIカラーまたはrichを使用します
  • ツール呼び出しのストリーミングでは、JSON引数のチャンクを蓄積して処理します

理解度チェック:ストリーミング出力

CLIエージェントのストリーミング出力について、理解度を確認します。

振り返り:CLIエージェントでのストリーミング出力

これで、応答性が高く現代的な印象のストリーミング対応CLIエージェントを構築できるようになりました。

  • OpenAI SDKからのストリーミングを有効にするには、stream=Trueを渡します
  • トークンをすぐに表示するには、print(token, end='', flush=True)を使用します
  • ストリーム終了後の処理に備えて、トークンを文字列に蓄積します
  • 切り詰めが発生したかどうかを検出するには、最後のチャンクでfinish_reasonを確認します
  • 非同期エージェントでは、AsyncOpenAIとasync forを使用します
  • 洗練されたターミナル体験を実現するには、ANSIカラーやrichを追加します

よくある質問

「CLIエージェントでのストリーミング出力」レッスンは無料ですか?

はい。「CLIエージェントでのストリーミング出力」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。

「CLIエージェントでのストリーミング出力」で何を学びますか?

ターミナルインターフェースで、ストリーミングされるトークンを1文字ずつ表示します。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Agentsを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「CLIエージェントでのストリーミング出力」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Agentsレッスンでコードを書いて実行できますか?

はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. コマンドラインエージェントインターフェースの構築
  2. 対話型REPLスタイルエージェント
  3. 引数解析とヘルプテキスト
  4. CLIエージェントでのストリーミング出力
← AI Agentsに戻る