从零开始实现一个 AI Agent CLI

1. 引言

AI Agent 是当前大模型应用中最热门的方向之一。它不再局限于简单的"问答",而是能够自主规划任务、调用工具、观察结果并迭代执行,最终完成一个相对复杂的目标。

而 CLI(命令行界面)是 Agent 最自然的载体之一:它轻量、无界面依赖、易于脚本化,非常适合用来演示 Agent 的核心运行机制。本文将带你从零开始,用 Python 手写一个最小可用的 AI Agent CLI,不依赖任何 Agent 框架,只借助大模型 API 和标准库,让你彻底理解 Agent 的内部工作原理。

2. 什么是 AI Agent

在动手写代码之前,我们先明确概念。一个典型的 AI Agent 通常包含以下几个核心组件:

  • 大模型(LLM):负责理解用户意图、进行推理和决策。
  • 工具(Tools):Agent 可以调用的外部能力,如搜索、计算器、文件读写、执行 Shell 命令等。
  • 规划(Planning):将复杂任务拆解为多个步骤。
  • 记忆(Memory):保存对话历史与中间结果,供后续步骤参考。
  • 循环(Loop):Agent 的核心运行机制------思考、调用工具、观察结果、再思考,直到任务完成。

本文实现的 CLI 将聚焦于"循环 + 工具调用"这一最小闭环,这也是 Agent 最本质的部分。

3. 整体架构设计

我们先设计整体流程,再逐步实现。
#mermaid-svg-WZMwhH9wM3qAIwf0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WZMwhH9wM3qAIwf0 .error-icon{fill:#552222;}#mermaid-svg-WZMwhH9wM3qAIwf0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WZMwhH9wM3qAIwf0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .marker.cross{stroke:#333333;}#mermaid-svg-WZMwhH9wM3qAIwf0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WZMwhH9wM3qAIwf0 p{margin:0;}#mermaid-svg-WZMwhH9wM3qAIwf0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .cluster-label text{fill:#333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .cluster-label span{color:#333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .cluster-label span p{background-color:transparent;}#mermaid-svg-WZMwhH9wM3qAIwf0 .label text,#mermaid-svg-WZMwhH9wM3qAIwf0 span{fill:#333;color:#333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .node rect,#mermaid-svg-WZMwhH9wM3qAIwf0 .node circle,#mermaid-svg-WZMwhH9wM3qAIwf0 .node ellipse,#mermaid-svg-WZMwhH9wM3qAIwf0 .node polygon,#mermaid-svg-WZMwhH9wM3qAIwf0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WZMwhH9wM3qAIwf0 .rough-node .label text,#mermaid-svg-WZMwhH9wM3qAIwf0 .node .label text,#mermaid-svg-WZMwhH9wM3qAIwf0 .image-shape .label,#mermaid-svg-WZMwhH9wM3qAIwf0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-WZMwhH9wM3qAIwf0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WZMwhH9wM3qAIwf0 .rough-node .label,#mermaid-svg-WZMwhH9wM3qAIwf0 .node .label,#mermaid-svg-WZMwhH9wM3qAIwf0 .image-shape .label,#mermaid-svg-WZMwhH9wM3qAIwf0 .icon-shape .label{text-align:center;}#mermaid-svg-WZMwhH9wM3qAIwf0 .node.clickable{cursor:pointer;}#mermaid-svg-WZMwhH9wM3qAIwf0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .arrowheadPath{fill:#333333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WZMwhH9wM3qAIwf0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WZMwhH9wM3qAIwf0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WZMwhH9wM3qAIwf0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WZMwhH9wM3qAIwf0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WZMwhH9wM3qAIwf0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WZMwhH9wM3qAIwf0 .cluster text{fill:#333;}#mermaid-svg-WZMwhH9wM3qAIwf0 .cluster span{color:#333;}#mermaid-svg-WZMwhH9wM3qAIwf0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WZMwhH9wM3qAIwf0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WZMwhH9wM3qAIwf0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-WZMwhH9wM3qAIwf0 .icon-shape,#mermaid-svg-WZMwhH9wM3qAIwf0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WZMwhH9wM3qAIwf0 .icon-shape p,#mermaid-svg-WZMwhH9wM3qAIwf0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WZMwhH9wM3qAIwf0 .icon-shape .label rect,#mermaid-svg-WZMwhH9wM3qAIwf0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WZMwhH9wM3qAIwf0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WZMwhH9wM3qAIwf0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WZMwhH9wM3qAIwf0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

用户输入任务
构造系统提示词
调用大模型
模型是否请求调用工具?
解析工具调用参数
执行本地工具函数
将结果返回给模型
输出最终回答
结束

整个 CLI 的核心就是一个 while 循环:不断把"对话历史 + 工具结果"喂给模型,直到模型不再请求调用工具为止。

4. 环境准备

本文使用 Python 3.10+,并借助 OpenAI 兼容接口调用大模型(可适配 OpenAI、DeepSeek、通义千问等任意兼容服务)。

bash 复制代码
# 安装依赖
pip install openai

然后设置环境变量:

bash 复制代码
export OPENAI_API_KEY="sk-xxxx"
export OPENAI_BASE_URL="https://api.openai.com/v1"

如果你使用国内模型服务,只需将 OPENAI_BASE_URL 指向对应服务的兼容地址即可。

5. 定义工具

Agent 的能力上限取决于它能调用哪些工具。我们先实现两个最基础的工具:一个执行 Shell 命令,一个做简单的文件读取。

python 复制代码
# tools.py
import subprocess
import os


def run_shell(command: str) -> str:
    """执行 Shell 命令并返回输出结果。"""
    try:
        result = subprocess.run(
            command, shell=True, capture_output=True, text=True, timeout=30
        )
        output = result.stdout + result.stderr
        return output.strip() or "(无输出)"
    except subprocess.TimeoutExpired:
        return "(命令执行超时)"
    except Exception as e:
        return f"(执行出错: {e})"


def read_file(path: str) -> str:
    """读取指定文本文件的内容。"""
    try:
        if not os.path.isfile(path):
            return f"(文件不存在: {path})"
        with open(path, "r", encoding="utf-8") as f:
            return f.read()[:3000]  # 限制长度,避免上下文溢出
    except Exception as e:
        return f"(读取失败: {e})"


# 工具注册表:Agent 通过这个名字找到对应函数
TOOLS = {
    "run_shell": {
        "function": run_shell,
        "description": "执行 Shell 命令,参数 command 为要执行的命令字符串。",
        "parameters": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "要执行的 Shell 命令"}
            },
            "required": ["command"],
        },
    },
    "read_file": {
        "function": read_file,
        "description": "读取文本文件内容,参数 path 为文件路径。",
        "parameters": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "文件路径"}
            },
            "required": ["path"],
        },
    },
}

这里的关键设计是工具注册表:每个工具包含函数本身、描述和参数 Schema。描述会随系统提示词一起发给模型,让模型知道"在什么情况下该调用哪个工具"。

6. 实现 Agent 核心循环

接下来是 Agent 的心脏------主循环。我们创建一个 agent.py,负责与大模型对话、解析工具调用、执行工具并回传结果。

python 复制代码
# agent.py
import json
from openai import OpenAI
from tools import TOOLS

client = OpenAI()  # 自动读取 OPENAI_API_KEY 和 OPENAI_BASE_URL

SYSTEM_PROMPT = """你是一个运行在终端里的 AI 助手,可以通过调用工具来完成任务。
你可以使用的工具如下:
{}

请根据用户需求,一步步调用工具完成任务。每次只调用一个工具,观察结果后再决定下一步。
当任务完成时,直接给出最终回答,不要再调用工具。
""".format(
    "\n".join(
        f"- {name}: {info['description']}" for name, info in TOOLS.items()
    )
)


def run_agent(user_input: str, max_steps: int = 10) -> None:
    """Agent 主循环:思考 -> 调用工具 -> 观察 -> 再思考,直到完成。"""
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_input},
    ]

    for step in range(max_steps):
        print(f"\n[Step {step + 1}] 调用大模型...")

        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=[
                {
                    "type": "function",
                    "function": {
                        "name": name,
                        "description": info["description"],
                        "parameters": info["parameters"],
                    },
                }
                for name, info in TOOLS.items()
            ],
        )

        message = response.choices[0].message

        # 模型没有请求调用工具 -> 任务完成,输出最终回答
        if not message.tool_calls:
            print(f"\n[完成] {message.content}")
            return

        # 模型请求调用工具 -> 把这条消息加入历史
        messages.append(message)

        # 逐个执行工具调用
        for tool_call in message.tool_calls:
            fn_name = tool_call.function.name
            fn_args = json.loads(tool_call.function.arguments)

            print(f"[调用工具] {fn_name}({fn_args})")

            # 从注册表找到并执行工具
            if fn_name in TOOLS:
                result = TOOLS[fn_name]["function"](**fn_args)
            else:
                result = f"(未知工具: {fn_name})"

            print(f"[工具结果] {result[:200]}...")

            # 把工具结果作为 role=tool 的消息回传给模型
            messages.append(
                {
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": result,
                }
            )

    print("\n[达到最大步数,强制结束]")


if __name__ == "__main__":
    task = input("请输入任务: ")
    run_agent(task)

这段代码的核心逻辑只有三步:

  1. messages(含系统提示词、用户输入、历史工具结果)发给模型;
  2. 判断模型返回的是最终回答 还是工具调用请求
  3. 若是工具调用,执行工具并把结果以 role=tool 的消息追加回历史,然后继续循环。

7. 运行与测试

保存以上两个文件后,在终端运行:

bash 复制代码
python agent.py

输入一个需要多步操作的任务,例如:

复制代码
请输入任务: 帮我创建一个名为 demo.txt 的文件,内容写入 "hello agent",然后读取它并告诉我内容。

你会看到 Agent 自动完成以下步骤:

text 复制代码
[Step 1] 调用大模型...
[调用工具] run_shell({'command': 'echo "hello agent" > demo.txt'})
[工具结果] (无输出)...

[Step 2] 调用大模型...
[调用工具] read_file({'path': 'demo.txt'})
[工具结果] hello agent...

[Step 3] 调用大模型...
[完成] 文件 demo.txt 已创建,内容为 "hello agent"。

可以看到,Agent 自主完成了"创建文件 -> 读取文件 -> 汇总回答"的完整闭环,全程无需人工干预。

8. 进阶优化方向

当前实现是一个最小可用的 Agent,距离生产级还有不少距离。你可以从以下几个方向继续完善:

  • 多工具并行调用:让模型一次请求调用多个工具,减少交互轮次。
  • 记忆管理:引入滑动窗口或向量数据库,处理超长对话历史。
  • 错误重试机制:工具执行失败时,自动把错误信息回传给模型让其修正。
  • 任务规划:在循环前先让模型输出一份执行计划,再逐步执行。
  • 流式输出 :使用 stream=True 让回答逐字显示,提升交互体验。
  • 安全沙箱:对 Shell 工具做白名单限制,避免危险命令。

9. 总结

本文从零实现了一个 AI Agent CLI,核心只有两个文件、一个 while 循环。你可能会惊讶于它的简洁------Agent 的本质并不神秘,就是"模型 + 工具 + 循环"三者的组合。

理解了这个最小闭环,再去学习 LangChain、AutoGPT 等框架时,你会发现它们只是在"工具管理、记忆、规划"等外围能力上做了增强,核心骨架与本文完全一致。希望这篇文章能帮你彻底打通 Agent 的实现原理。

相关推荐
user-猴子1 小时前
钛媒体测五款、光锥智能测WorkBuddy、用户测AiPy——三组实测交叉对比,哪款AI办公工具最值得下载?
人工智能
IT古董1 小时前
AI 资讯日报 | 2026年8月29日:开源大模型三连发,DeepSeek 500 亿融资落地
人工智能·开源
IT_陈寒1 小时前
Vite打包时的静态资源坑,我帮你踩过了
前端·人工智能·后端
IJCAST2 小时前
IJCAST最新一期已经发布
人工智能·深度学习·神经网络
熊野君3 小时前
附录与 Codex 实操手册
开发语言·人工智能·产品经理
Zaimmm3 小时前
AI医学研究工具助力肝癌术后急性脑梗早期护理实践研究|证元芳 2026
人工智能
木卫四科技3 小时前
AgentAntibody:Prompt 注入防御开始“长记忆”,LLM Agent 如何构建自进化免疫系统?
人工智能·prompt·智能体安全
Djvu6773 小时前
让 Codex / Claude Code / Hermes 共享同一个搜索+抓取工具:一份 SKILL.md 走天下的取舍
人工智能
chunmiao30323 小时前
OpenAI 官宣断供 Cursor,AI 编程迎来第一次模型断供
人工智能·深度学习