0Pricing
AI Agents · 课时

交互式 REPL 风格代理

终端代理中的输入循环、历史记录和多轮对话。

交互式 REPL 风格代理 是 CoddyKit 上的免费 AI Agents 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Agents 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Agents 课程共包含 4 节课。

什么是 REPL 风格的代理

REPL(读取—求值—输出循环)是一种交互式终端会话:您输入内容、获得输出,然后继续操作。终端中的聊天代理正是以这种方式工作:用户输入消息,代理作出响应,对话持续进行,直到用户退出。

基本 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)支持使用方向键浏览之前的输入,就像真正的 Shell 一样。用户可以按向上箭头调出之前的消息。

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 调用可能需要数秒才能完成。等待期间显示“正在思考……”消息,让用户知道代理正在处理请求。响应到达后将其清除。

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() 作为核心读取循环
  • 捕获 KeyboardInterrupt,实现干净的 Ctrl+C 退出
  • 维护 conversation_history 列表,实现多轮记忆
  • 使用 readline 管理方向键输入历史
  • 实现 /help、/clear 和 /exit 斜杠命令
  • 裁剪历史,防止令牌数量无限增长
  • 在 LLM 调用期间显示旋转指示器或进度指示器

常见问题解答

「交互式 REPL 风格代理」课时是免费的吗?

是的 — 「交互式 REPL 风格代理」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Agents 课程的其余内容,请升级到 CoddyKit PRO。 AI Agents 课程共包含 4 节课。

「交互式 REPL 风格代理」这节课中我会学到什么?

终端代理中的输入循环、历史记录和多轮对话。 你通过在浏览器中直接运行的动手代码来练习 AI Agents,全天候 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