120行代码实现一个极简 Agent

120行代码实现一个极简 Agent

标签:#Python #AI #Agent #LLM #编程实战 日期:2026-06-04 摘要:本文介绍一个仅 120 行代码的极简 Agent 实现,包含记忆系统、工具系统,支持 LLM Function Calling,不依赖第三方 Agent 框架。适合作为 Agent 开发入门示例。


前言

最近在学习 Agent 开发,翻遍了市面上的框架文档,不是依赖 LangChain 就是需要安装各种 SDK。对于想理解 Agent 本质的新手来说,这些框架反而增加了理解成本。

于是我决定自己动手,用最朴素的方式写一个极简 Agent。120 行代码,涵盖了 Agent 的核心要素:记忆系统工具系统LLM 对接


一、运行效果

先看一下实际运行效果:

可以看到:

  • 对话循环:用户输入 → LLM 思考 → 工具调用 → 结果返回 → LLM 总结
  • 工具调用 :LLM 自动识别需要调用 list_dir 工具
  • 记忆保持:多轮对话上下文被完整保留

二、整体架构

Agent 的核心逻辑其实很简单:

复制代码
用户输入 → 记忆存储 → LLM 思考 → 工具调用(可选)→ LLM 总结 → 返回结果

核心组件

组件 职责
LLM 大脑,负责理解和生成
Memory 记忆,管理对话历史
Tool 工具,扩展 LLM 能力
ToolRegistry 工具注册表,管理所有可用工具
Agent 粘合剂,协调以上组件工作

三、核心代码解读

1. LLM 调用

python 复制代码
class LLM:
    def __init__(self, config: dict):
        self.api_key = config["api_key"]
        self.base_url = config["base_url"]
        self.model = config["model"]
        self.max_tokens = config.get("max_tokens", 4096)
        self.temperature = config.get("temperature", 0.1)
    
    def chat(self, messages: list[dict], tools: Optional[list] = None) -> dict:
        headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
        payload = {"model": self.model, "messages": messages, "max_tokens": self.max_tokens, "temperature": self.temperature}
        if tools: payload["tools"] = tools
        resp = requests.post(f"{self.base_url}/chat/completions", headers=headers, json=payload, timeout=120)
        resp.raise_for_status()
        return resp.json()

要点

  • 兼容 OpenAI 格式 API(如 MiniMax、DeepSeek 等)
  • tools 参数用于 Function Calling,让 LLM 知道有哪些工具可用

2. 记忆系统

python 复制代码
class Memory:
    def __init__(self, max_msgs: int = 50):
        self.max_msgs = max_msgs
        self.messages: list[dict] = []
    
    def add(self, role: str, content: str):
        self.messages.append({"role": role, "content": content})
        # 自动修剪:保留系统消息 + 最近 50 条
        if len(self.messages) > self.max_msgs:
            self.messages = [m for m in self.messages if m["role"] == "system"] + self.messages[-self.max_msgs:]
    
    def add_user(self, content: str): self.add("user", content)
    def add_assistant(self, content: str): self.add("assistant", content)
    def add_tool(self, tool_call_id: str, content: str):
        self.messages.append({"role": "tool", "tool_call_id": tool_call_id, "content": content})

要点

  • 支持 userassistanttool 三种角色
  • 自动修剪超过限制的旧消息
  • 保留系统消息不被修剪

3. 工具系统

python 复制代码
class Tool:
    def __init__(self, name: str, desc: str, schema: dict, func: Callable):
        self.name, self.desc, self.schema, self.func = name, desc, schema, func
    
    def execute(self, **kwargs): return self.func(**kwargs)
    def to_schema(self): return {"type": "function", "function": {"name": self.name, "description": self.desc, "parameters": self.schema}}
python 复制代码
class ToolRegistry:
    def __init__(self): self.tools: dict[str, Tool] = {}
    def register(self, tool: Tool): self.tools[tool.name] = tool
    def get(self, name: str): return self.tools.get(name)
    def get_schemas(self): return [t.to_schema() for t in self.tools.values()]

要点

  • 每个 Tool 有 namedescriptionschemafunc
  • to_schema() 生成 OpenAI Function Calling 格式
  • 注册表统一管理工具的注册和查找

4. 注册内置工具

python 复制代码
registry = ToolRegistry()

registry.register(Tool("list_dir", "列出目录下的文件", {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]},
    lambda path: "\n".join([f"{'📁' if p.is_dir() else '📄'} {p.name}" for p in sorted(Path(path).iterdir())]) if Path(path).exists() else "目录不存在"))

registry.register(Tool("read_file", "读取文件内容", {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]},
    lambda path: Path(path).read_text(encoding="utf-8", errors="replace")[:5000] if Path(path).exists() else "文件不存在"))

registry.register(Tool("exec", "执行 Shell 命令", {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]},
    lambda command: subprocess.run(command, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace").stdout or "(无输出)"))

内置三个工具

  • list_dir:列出目录文件
  • read_file:读取文件内容
  • exec:执行 Shell 命令

5. Agent 核心逻辑

python 复制代码
class Agent:
    def __init__(self, config: dict):
        self.llm = LLM(config)
        self.memory = Memory()
    
    def chat(self, user_input: str) -> str:
        self.memory.add_user(user_input)
        messages = [{"role": "system", "content": SYSTEM_PROMPT}] + self.memory.messages
        
        # 第一次 LLM 调用
        result = self.llm.chat(messages, registry.get_schemas())
        msg = result["choices"][0]["message"]
        
        # 如果 LLM 调用了工具
        if "tool_calls" in msg and msg["tool_calls"]:
            self.memory.messages.append(msg)  # 保存 LLM 的调用
            for tc in msg["tool_calls"]:
                tool = registry.get(tc["function"]["name"])
                args = eval(tc["function"]["arguments"])
                result = tool.execute(**args) if tool else f"工具不存在: {tc['function']['name']}"
                self.memory.add_tool(tc["id"], result)  # 保存工具结果
            
            # 第二次 LLM 调用,传入工具结果
            result = self.llm.chat([{"role": "system", "content": SYSTEM_PROMPT}] + self.memory.messages, registry.get_schemas())
            msg = result["choices"][0]["message"]
        
        content = msg.get("content", "")
        self.memory.add_assistant(content)
        return content

核心流程

markdown 复制代码
用户输入
    ↓
记忆存储(add_user)
    ↓
第一次 LLM 调用(带工具 Schema)
    ↓
LLM 返回 ← 工具调用(可选)
    ↓
保存 LLM 回复
    ↓
执行工具(如果有)
    ↓
保存工具结果(add_tool)
    ↓
第二次 LLM 调用(传入工具结果)
    ↓
返回最终回复(add_assistant)

四、配置说明

只需要一个 config.json

json 复制代码
{
    "api_key": "你的 API Key",
    "base_url": "https://api.minimaxi.com/v1",
    "model": "MiniMax-M2.7",
    "max_tokens": 8192,
    "temperature": 0.1
}

支持任何 OpenAI 兼容 API(MiniMax、DeepSeek、Qwen 等)。


五、完整代码

整个项目只有两个文件:

文件 行数 说明
miniagent.py 120 全部代码
config.json 1 配置文件
python 复制代码
"""极简 Agent 原型"""

import json, requests, subprocess

from pathlib import Path

from typing import Optional, Callable

  

# ============ 配置 ============

def load_config():

    with open(Path(__file__).parent / "config.json", encoding="utf-8") as f:

        return json.load(f)

  

# ============ LLM 调用 ============

class LLM:

    def __init__(self, config: dict):

        self.api_key = config["api_key"]

        self.base_url = config["base_url"]

        self.model = config["model"]

        self.max_tokens = config.get("max_tokens", 4096)

        self.temperature = config.get("temperature", 0.1)

    def chat(self, messages: list[dict], tools: Optional[list] = None) -> dict:

        headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}

        payload = {"model": self.model, "messages": messages, "max_tokens": self.max_tokens, "temperature": self.temperature}

        if tools: payload["tools"] = tools

        resp = requests.post(f"{self.base_url}/chat/completions", headers=headers, json=payload, timeout=120)

        resp.raise_for_status()

        return resp.json()

  

# ============ 记忆管理 ============

class Memory:

    def __init__(self, max_msgs: int = 50):

        self.max_msgs = max_msgs

        self.messages: list[dict] = []

    def add(self, role: str, content: str):

        self.messages.append({"role": role, "content": content})

        if len(self.messages) > self.max_msgs:

            self.messages = [m for m in self.messages if m["role"] == "system"] + self.messages[-self.max_msgs:]

    def add_user(self, content: str): self.add("user", content)

    def add_assistant(self, content: str): self.add("assistant", content)

    def add_tool(self, tool_call_id: str, content: str):

        self.messages.append({"role": "tool", "tool_call_id": tool_call_id, "content": content})

    def clear(self):

        self.messages = [m for m in self.messages if m["role"] == "system"]

  

# ============ 工具系统 ============

class Tool:

    def __init__(self, name: str, desc: str, schema: dict, func: Callable):

        self.name, self.desc, self.schema, self.func = name, desc, schema, func

    def execute(self, **kwargs): return self.func(**kwargs)

    def to_schema(self): return {"type": "function", "function": {"name": self.name, "description": self.desc, "parameters": self.schema}}

  

class ToolRegistry:

    def __init__(self): self.tools: dict[str, Tool] = {}

    def register(self, tool: Tool): self.tools[tool.name] = tool

    def get(self, name: str): return self.tools.get(name)

    def list(self): return list(self.tools.values())

    def get_schemas(self): return [t.to_schema() for t in self.tools.values()]

  

registry = ToolRegistry()

  

# 注册内置工具(每个一行,避免歧义)

registry.register(Tool("list_dir", "列出目录下的文件", {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}, lambda path: "\n".join([f"{'📁' if p.is_dir() else '📄'} {p.name}" for p in sorted(Path(path).iterdir())]) if Path(path).exists() else "目录不存在"))

registry.register(Tool("read_file", "读取文件内容", {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}, lambda path: Path(path).read_text(encoding="utf-8", errors="replace")[:5000] if Path(path).exists() else "文件不存在"))

registry.register(Tool("exec", "执行 Shell 命令", {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}, lambda command: subprocess.run(command, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace").stdout or "(无输出)"))

  

# ============ Agent 核心 ============

SYSTEM_PROMPT = """你是一个有用的 AI 助手。

  

可用工具:

- list_dir(path): 列出目录文件

- read_file(path): 读取文件

- exec(command): 执行命令"""

  

class Agent:

    def __init__(self, config: dict):

        self.llm = LLM(config)

        self.memory = Memory()

    def chat(self, user_input: str) -> str:

        self.memory.add_user(user_input)

        messages = [{"role": "system", "content": SYSTEM_PROMPT}] + self.memory.messages

        result = self.llm.chat(messages, registry.get_schemas())

        msg = result["choices"][0]["message"]

        if "tool_calls" in msg and msg["tool_calls"]:

            self.memory.messages.append(msg)

            for tc in msg["tool_calls"]:

                tool = registry.get(tc["function"]["name"])

                args = eval(tc["function"]["arguments"])

                result = tool.execute(**args) if tool else f"工具不存在: {tc['function']['name']}"

                self.memory.add_tool(tc["id"], result)

            result = self.llm.chat([{"role": "system", "content": SYSTEM_PROMPT}] + self.memory.messages, registry.get_schemas())

            msg = result["choices"][0]["message"]

        content = msg.get("content", "")

        self.memory.add_assistant(content)

        return content

    def reset(self): self.memory.clear()

  

# ============ 入口 ============

if __name__ == "__main__":

    config = load_config()

    agent = Agent(config)

    print("="*50, "\nMyAgent 已启动 (输入 q 退出, reset 清空历史)\n", "="*50)

    while True:

        try:

            user_input = input("\n> ").strip()

            if user_input.lower() == "q": break

            if user_input.lower() == "reset": agent.reset(); print("已清空"); continue

            if not user_input: continue

            print(f"\n{agent.chat(user_input)}\n")

        except KeyboardInterrupt: break

        except Exception as e: print(f"错误: {e}")

六、总结

📌 要点回顾

  1. Agent 本质:一个 LLM + 记忆 + 工具的循环调用机制
  2. 工具调用:通过 Function Calling 让 LLM 知道有哪些工具可用
  3. 记忆系统:管理对话历史,自动修剪
  4. 朴素实现:120 行代码,不依赖任何第三方框架

💡 扩展方向

  • 添加更多工具(搜索、数据库、API 调用等)
  • 实现技能(Skill)系统,支持复杂任务
  • 添加流式输出支持
  • 接入向量数据库实现 RAG

本文为本人原创,首发于掘金。

代码仓库:github.com/你的仓库

如果你有任何问题或想法,欢迎在评论区交流!

相关推荐
XIAOHEZIcode1 小时前
进程、会话与终端——一次真实的 Linux Session 解剖
linux·后端·命令行
枕星而眠1 小时前
【数据结构】树与二叉树基础知识点总结
数据结构·c++·后端·算法·运维开发
极光技术熊1 小时前
从零构建在线Excel:一个Java全栈工程师的实战记录
前端·后端
小谢小哥1 小时前
68-持续集成详解
java·后端·架构
foggyprojects1 小时前
列表里要带子表统计值时,为什么需要 QM 聚合型 JOIN
后端
用户925807911481 小时前
redission原理
java·后端
小旭95271 小时前
Spring Cloud 集成分布式日志 ELK+Swagger 接口文档实战
java·分布式·后端·elk·spring cloud
属鼠哥1 小时前
HDFS 短路读取:mmap 与 Unix Domain Socket 铸就的零拷贝艺术
后端
好好风格1 小时前
Scrapling:现代 Web 抓取,正在从“写选择器”走向“自适应”
linux·后端