0Pricing
AI Agents · レッスン

対話型REPLスタイルエージェント

ターミナルエージェントでの入力ループ、履歴、マルチターン会話を学びます。

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

REPL形式のエージェントとは

REPL(Read-Eval-Print Loop)とは、入力を入力すると出力が返され、そのまま次の操作を続けられる対話型のターミナルセッションです。ターミナル上のチャットエージェントはまさにこの方式で動作します。ユーザーがメッセージを入力し、エージェントが応答し、ユーザーが終了するまで会話が続きます。

基本的なREPLループ

最も単純なREPLは、input()でユーザー入力を読み取り、処理して結果を出力するwhile Trueループです。ユーザーが終了を指示するまで、ループは無限に実行されます。

def run_agent_repl(inputs):
    print('Agent started. Type /exit to quit.')
    conversation_history = []
    for user_input in inputs:
        user_input = user_input.strip()
        if not user_input:
            continue
        if user_input == '/exit':
            print('Goodbye!')
            break
        conversation_history.append({'role': 'user', 'content': user_input})
        agent_reply = f'[Agent reply to: {user_input}]'
        conversation_history.append({'role': 'assistant', 'content': agent_reply})
        print(f'Agent: {agent_reply}')

run_agent_repl(['Hello agent', 'What can you do?', '/exit'])

KeyboardInterruptを適切に処理する

ユーザーがCtrl+Cを押すと、PythonはKeyboardInterruptを発生させます。トップレベルでこれを捕捉し、スタックトレースの代わりに丁寧な終了メッセージを表示します。

def run_agent_repl():
    print('Agent ready. Press Ctrl+C to exit.')
    conversation_history = []

    try:
        while True:
            user_input = input('You: ').strip()
            if not user_input:
                continue

            conversation_history.append({'role': 'user', 'content': user_input})
            reply = '[mocked agent response]'
            conversation_history.append({'role': 'assistant', 'content': reply})
            print(f'Agent: {reply}')

    except KeyboardInterrupt:
        print('\nSession ended. Goodbye!')
    except EOFError:
        # Triggered when stdin is closed (piped input ends)
        print('\nEnd of input. Exiting.')

if __name__ == '__main__':
    import builtins
    _demo_inputs = iter(['What agents can you build?', EOFError])
    def _fake_input(prompt=''):
        val = next(_demo_inputs)
        if val is EOFError:
            raise EOFError
        print(prompt + val)
        return val
    builtins.input = _fake_input
    run_agent_repl()

複数ターンの会話履歴を維持する

エージェントに会話の記憶を持たせるには、メッセージのリストを維持し、各ターンで完全な履歴をLLMに渡します。まず、エージェントのペルソナを設定するシステムメッセージから始めます。

import openai

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

def run_chat_repl():
    history = [
        {'role': 'system', 'content': 'You are a helpful AI assistant.'}
    ]

    print('Chat started. Type /exit to quit.')

    try:
        while True:
            user_input = input('You: ').strip()
            if not user_input or user_input == '/exit':
                break

            history.append({'role': 'user', 'content': user_input})

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

            reply = response.choices[0].message.content
            history.append({'role': 'assistant', 'content': reply})
            print(f'Agent: {reply}')

    except KeyboardInterrupt:
        print('\nGoodbye!')

readline:入力履歴と編集

readlineモジュール(Unix/Mac)を使うと、実際のシェルと同じように、矢印キーで過去の入力をたどれます。ユーザーは上矢印キーを押して、以前のメッセージを呼び出せます。

try:
    import readline  # available on Unix/Mac, not Windows
    # Enable persistent history between sessions
    import os
    HISTORY_FILE = os.path.expanduser('~/.agent_history')
    try:
        readline.read_history_file(HISTORY_FILE)
    except FileNotFoundError:
        pass

    import atexit
    atexit.register(readline.write_history_file, HISTORY_FILE)
    readline.set_history_length(500)
    print('History enabled (use up/down arrows)')
except ImportError:
    pass  # readline not available on Windows

# After this, input() automatically has history support

組み込みスラッシュコマンド

よく使う操作のために、REPLエージェントへ組み込みスラッシュコマンドを追加します。/help、/exit、会話をリセットする/clear、過去のメッセージを表示する/historyなどです。

def handle_command(cmd: str, history: list) -> bool:
    'Returns True if the command was handled, False if it is a regular message'
    if cmd == '/exit' or cmd == '/quit':
        print('Goodbye!')
        raise SystemExit(0)

    elif cmd == '/help':
        print('Commands: /exit, /clear, /history, /help')
        return True

    elif cmd == '/clear':
        # Keep only the system message
        system = history[0] if history and history[0]['role'] == 'system' else None
        history.clear()
        if system:
            history.append(system)
        print('[Conversation cleared]')
        return True

    elif cmd == '/history':
        for i, msg in enumerate(history):
            if msg['role'] != 'system':
                print(f'  [{msg["role"]}] {msg["content"][:80]}')
        return True

    return False  # not a command, treat as regular message

if __name__ == '__main__':
    demo_history = [
        {'role': 'system', 'content': 'You are a helpful agent.'},
        {'role': 'user', 'content': 'Hi there'},
        {'role': 'assistant', 'content': 'Hello! How can I help?'},
    ]
    handle_command('/help', demo_history)
    handle_command('/history', demo_history)

会話履歴の長さを制限する

各ターンで会話履歴全体をLLMに渡すと、トークン数が線形に増加します。コストを抑えるには、システムメッセージを残したまま、履歴を直近N件のメッセージに絞り込みます。

def trim_history(history: list, max_turns: int = 10) -> list:
    system_messages = [m for m in history if m['role'] == 'system']
    non_system = [m for m in history if m['role'] != 'system']

    # Keep the last max_turns messages (each turn = 1 user + 1 assistant)
    max_messages = max_turns * 2
    trimmed = non_system[-max_messages:]

    return system_messages + trimmed

# Usage in the REPL loop:
# history.append({'role': 'user', 'content': user_input})
# trimmed = trim_history(history, max_turns=10)
# response = client.chat.completions.create(model='gpt-4o-mini', messages=trimmed)
print('Trimming history prevents token count from growing unboundedly')

ウェルカムバナーを表示する

ウェルカムバナーを表示すると、REPLが洗練された印象になります。起動時に一度だけ、エージェントの名前、バージョン、利用可能なコマンドを表示します。シンプルなASCIIアートや、色を付けられるrichライブラリを使用します。

def print_banner():
    banner = '''
====================================
  AI Research Agent v1.0
====================================
  Type your question to begin.
  Commands: /help  /clear  /exit
====================================
    '''
    print(banner)

# Or with rich colors:
# from rich.console import Console
# from rich.panel import Panel
# console = Console()
# console.print(Panel('[bold cyan]AI Research Agent[/] v1.0', subtitle='Type /help for commands'))

print_banner()

入力中インジケーターを表示する

LLMの呼び出しには数秒かかることがあります。待機中に「thinking...」というメッセージを表示して、エージェントが処理中であることをユーザーに知らせます。応答が届いたら、そのメッセージを消去します。

import sys
import threading
import time

def thinking_spinner(stop_event: threading.Event):
    frames = ['|', '/', '-', '\\']
    i = 0
    while not stop_event.is_set():
        sys.stdout.write(f'\rAgent is thinking... {frames[i % 4]}')
        sys.stdout.flush()
        time.sleep(0.1)
        i += 1
    sys.stdout.write('\r' + ' ' * 30 + '\r')  # clear the line
    sys.stdout.flush()

def call_agent_with_spinner(query: str) -> str:
    stop = threading.Event()
    spinner = threading.Thread(target=thinking_spinner, args=(stop,))
    spinner.start()

    # result = agent.run(query)  # the actual slow call
    time.sleep(1)  # simulating work
    result = 'The answer is 42.'

    stop.set()
    spinner.join()
    return result

if __name__ == '__main__':
    answer = call_agent_with_spinner('What is 6 * 7?')
    print(f'\nFinal answer: {answer}')

セッションをファイルに保存する

セッションの終了時に、ユーザーが会話をファイルへ保存できるようにします。エージェントが見つけた内容を記録として残したい調査セッションで便利です。

import json
import datetime
import os

def save_session(history: list, directory: str = 'sessions'):
    os.makedirs(directory, exist_ok=True)
    timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
    filename = os.path.join(directory, f'session_{timestamp}.json')

    with open(filename, 'w') as f:
        json.dump({
            'saved_at': timestamp,
            'messages': history
        }, f, indent=2)

    print(f'Session saved to {filename}')

# Usage in REPL on /save command:
# save_session(conversation_history)

if __name__ == '__main__':
    import tempfile
    demo_dir = tempfile.mkdtemp()
    demo_history = [
        {'role': 'user', 'content': 'Hello'},
        {'role': 'assistant', 'content': 'Hi! How can I help?'},
    ]
    save_session(demo_history, directory=demo_dir)

完全なREPLエージェントのテンプレート

ここまでの内容をまとめ、履歴管理、スラッシュコマンド、キーボード割り込み処理、セッション保存に対応した、本番利用可能なREPLテンプレートを作成します。

def run_repl(agent):
    history = [{'role': 'system', 'content': 'You are a helpful AI assistant.'}]
    print('Agent ready. Type /help for commands.')
    try:
        while True:
            user_input = input('You: ').strip()
            if not user_input:
                continue
            if user_input.startswith('/'):
                handle_command(user_input, history)
                continue
            history.append({'role': 'user', 'content': user_input})
            trimmed = trim_history(history, max_turns=10)
            reply = agent.run(trimmed)
            history.append({'role': 'assistant', 'content': reply})
            print(f'Agent: {reply}')
    except KeyboardInterrupt:
        print('\nSession ended.')
        save_session(history)

理解度チェック:REPL形式のエージェント

対話型REPLエージェントについて、理解度を確認します。

まとめ:対話型REPL形式のエージェント

これで、ターミナル向けの機能豊富な対話型エージェントを構築できるようになりました。

  • 読み取りループの中心にwhile True: input()を使用します
  • きれいにCtrl+Cで終了できるよう、KeyboardInterruptを捕捉します
  • 複数ターンの記憶のためにconversation_historyリストを維持します
  • 矢印キーによる入力履歴にはreadlineを使用します
  • /help、/clear、/exitのスラッシュコマンドを実装します
  • トークン数が際限なく増加しないよう、履歴を絞り込みます
  • LLMの呼び出し中はスピナーや進捗インジケーターを表示します

よくある質問

「対話型REPLスタイルエージェント」レッスンは無料ですか?

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

「対話型REPLスタイルエージェント」で何を学びますか?

ターミナルエージェントでの入力ループ、履歴、マルチターン会話を学びます。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「対話型REPLスタイルエージェント」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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