AI Agent 是近两年大模型应用开发里最容易被误解,也最值得投入的方向。很多人以为它是另一个聊天窗口,其实 Agent 是一个由大语言模型驱动、能够调用外部工具、维护上下文、按计划执行任务的程序。真正进入 AI Agent 开发后,你会发现核心问题不是背 API,而是把模型能力、工具接口和任务流程编排到一起。这篇文章会把概念、架构、代码和排错放在同一条主线上,用一个最小可运行的 Agent 项目来演示:让 Agent 通过 ES REST API 智能分析日志。文章面向有 Python 或后端基础、准备入手 AI Agent 开发,但不想被冗长视频清单淹没的读者。
很多教程会给出几百集的学习清单,但对入门者来说,最有效的做法是先跑通一个真实的小项目,再逐步扩展。下面从 Agent 的本质开始讲起,最终落到一个可以复用的日志分析 Agent 模板。整个项目只需要一个 mock ES 接口、一个模型 API、一个 Python 文件,就能完整跑通“用户提问 -> 模型规划 -> 调用工具 -> 分析结果”的全过程。
1. 先搞清楚 AI Agent 是什么,它和普通对话程序差在哪里
学习 Agent 开发之前,最值得花时间的是先把概念边界划清楚。不要把 Agent 理解成一个“更聪明的模型”,它本质上是一套把模型、工具、记忆和任务编排组合起来的程序结构。
1.1 用日志分析场景说清 Agent 的边界
假设用户问:order-service 最近 30 分钟的 error 日志有哪些?
普通 ChatBot 能做到的是给出通用回答,比如“建议去查看日志文件”或者“可以使用 grep 命令”。它无法真正访问 Elasticsearch,也不能返回这条服务线的真实错误。
Agent 的做法是:先理解用户想查日志,再选择一个叫query_es_logs的工具,把service=order-service、level=error、minutes=30作为参数传给工具,拿到 Elasticsearch 返回的日志列表,最后基于这些真实数据回答。用户看到的是结论,但过程中的“理解意图 -> 选择工具 -> 填写参数 -> 执行工具 -> 解析结果 -> 生成回答”全部由 Agent 编排完成。
这就是 Agent 和普通对话程序的核心区别:普通程序的行为由开发者在代码里写死,Agent 的行为由模型根据当前输入和上下文动态决策。
1.2 术语梳理:LLM、Tool、Memory、Planning 和 Agent
在 HuggingFace 等公开课程中,Agent 通常被拆成几个核心组件,初学时不需要背复杂术语,但要先记住这些角色分别解决什么问题。
| 组件 | 通俗解释 | 在日志分析项目里的体现 |
|---|---|---|
| LLM | 做决策的大脑 | 理解用户问题、决定是否调用工具 |
| Tool | 能执行外部动作的函数 | 封装 ES REST API 的查询接口 |
| Memory | 存放对话上下文和历史结果 | 多轮工具调用中的 messages 列表 |
| Planning | 决定调用哪个工具、按什么顺序 | 模型在每一步给出的 tool_calls |
| Execution | 真正执行工具并返回结果 | Python 函数发出 HTTP 请求拿到日志 |
这里最容易混淆的是 “Agent” 和 “Tool”。Tool 是一个具体动作,Agent 是负责调度这些动作的主体。用一个不合适但容易理解的类比:Tool 是手,Agent 是大脑,手不能自己决定抓什么,大脑需要根据任务目标持续指挥。
1.3 Agent 与 Skills 的关系
很多教程会同时出现 Agent、Tools 和 Skills 三个词。Tools 是一个可被调用的函数接口,Skills 则更接近“带提示词、代码和工具定义的打包能力单元”。你可以把一个 Skill 理解成“完成某类任务的可复用技能包”,Agent 可以直接加载这个技能包来获得对应能力。
初学阶段不用纠结两者定义在社区里如何演进。实践中的判断标准很简单:如果一个能力只有“执行”价值,就封装成 Tool;如果它包含“该怎么做、用什么参数、输出什么格式”的完整行为描述,就可以进一步沉淀为 Skill。本文的日志分析 Agent 先把查询接口做成 Tool,后续想复用整套“日志分析行为”,再把它升级成 Skill。
1.4 入门 Agent 开发的最低技术前提
入门 Agent 开发不需要先学完几百集视频。真正需要的基础可以被压缩到一张表里:
| 前置知识 | 具体要求 | 不满足时怎么补 |
|---|---|---|
| Python 基础 | 会写函数、字典、json 解析 | 补一个 Python 速成练习 |
| HTTP 基础 | 知道 GET/POST、状态码、JSON 响应 | 用 curl 请求一个公开接口 |
| Prompt 基础 | 理解 system/user/assistant 三种角色 | 先手动调一次模型 API |
| 模型 API | 有 OpenAI 兼容接口的 Key 或本地模型 | 用服务商通用接口即可 |
这些基础一周内可以补齐。真正花费时间的是后续对工具失败、参数偏差、模型乱答这类工程问题的处理能力。
2. Agent 完整架构与框架选型,不要一开始就陷入工具对比
很多新手上来就在 LangChain、AutoGen、CrewAI、Dify 之间纠结,其实 Agent 的完整架构并不复杂。先理解模块,再选择工具,顺序不能反。
2.1 一个完整 Agent 项目包含哪些模块
一个能上线的 Agent 项目,通常包含以下几个模块:
- 用户入口:命令行、Web 页面、IM 机器人、内部工单系统。
- Agent Runtime:负责模型调用、工具注册、上下文管理、循环控制。
- Tool 层:封装 HTTP API、数据库查询、文件读写、脚本执行等外部能力。
- Memory 层:短期上下文存放在 messages,长期记忆可落到向量数据库或 KV 存储。
- 可观测层:记录工具调用、模型输出、耗时、token 消耗,用于排查和评估。
在学习环境里,我们只需要实现 Runtime + Tool 层,Memory 直接用 messages 列表模拟。生产环境再逐步加入可观测层和长期记忆。
2.2 单 Agent 与多 Agent 的取舍
单 Agent 架构里,一个模型负责规划、调用工具和生成最终回答。优点是逻辑简单、调试容易、token 开销可控。绝大多数业务场景,比如日志分析、报表问答、订单查询,单 Agent 足够。
多 Agent 架构会把角色拆开,比如一个 Planner 负责拆任务,一个 Executor 负责调工具,一个 Critic 负责审查结果。它适合复杂流水线,但代价也很明显:状态管理复杂、调试困难、token 成本成倍增加。
给新手的建议是:先用单 Agent 跑通业务闭环。只有当单 Agent 出现“任务步骤过多、单条上下文放不下、需要不同角色视角”的问题时,再考虑多 Agent。
2.3 主流框架与平台选型对比
当前 Agent 开发工具很多,选型时可以先按“代码框架”和“可视化平台”两个维度区分。
| 框架/平台 | 语言 | 编排方式 | 常见适用场景 | 上手成本 |
|---|---|---|---|---|
| LangChain / LangGraph | Python | 链式、图编排 | 自定义 Agent、复杂流程 | 中等 |
| AutoGen / AG2 | Python | 多 Agent 对话 | 多智能体协同研究 | 中等偏高 |
| CrewAI | Python | 角色化多 Agent | 团队协作式任务 | 中等 |
| Dify | Web 可视化 | 工作流编排 | 快速搭建业务应用 | 低 |
| Coze | Web 可视化 | 工作流编排 | 快速搭建对话 Bot | 低 |
| LangChain4j / Spring AI | Java | 链式、注解 | Java 后端集成 | 中等 |
这些工具的生态变化速度很快,落地前要去对应仓库查看最新版本和文档。框架本身没有绝对的“最强”,只有“适合当前项目”和“不适合当前项目”。
2.4 针对日志分析项目的选型建议
日志分析场景的核心链路是“自然语言 -> 查询参数 -> ES REST API -> 结果分析”。这个链路用最基础的工具调用循环就能实现,不一定要引入重量级编排框架。
我的建议是先用 OpenAI 兼容 SDK 手写一个 Tool Calling 循环。这样做有三个好处:第一,你能看清楚 Agent 的内部机制,而不是被框架封装;第二,日志查询逻辑简单,框架带来的状态管理优势体现不出来;第三,后续如果项目变复杂,你是在理解原理的基础上选择 LangGraph 或 LangChain4j,而不是盲目堆依赖。
如果团队是 Java 技术栈,可以关注 LangChain4j 和 Spring AI 的 function calling 支持;如果团队希望业务人员也能编排流程,再引入 Dify 这类可视化平台。先把查询逻辑和工具边界设计好,才是项目成败的关键。
3. 准备环境并搭出一个最小 Agent 项目结构
现在进入可运行阶段。这一节会准备整个项目的目录、依赖、配置和一个 mock ES 服务。目标是在本机把 Agent 运行环境完整搭起来,不依赖真实 Elasticsearch 集群。
3.1 项目目录与依赖
在本地建立一个agent-demo目录,结构保持最小:
agent-demo/ ├── .env.example ├── requirements.txt ├── mock_es_server.py └── agent.pyrequirements.txt内容如下:
fastapi==0.111.0 uvicorn==0.30.0 openai==1.35.0 python-dotenv==1.0.1 requests==2.32.0这里并不要求所有版本完全一致。实际安装时如果和其他项目冲突,可以适当调整版本范围,但要注意openaiSDK 版本直接影响后面client.chat.completions.create的调用方式。
安装依赖:
pip install -r requirements.txt3.2 模型 API 配置
在.env.example中写入以下配置:
OPENAI_API_KEY=sk-your-key OPENAI_BASE_URL=https://api.openai.com/v1 MODEL_NAME=gpt-4o-mini ES_BASE_URL=http://127.0.0.1:9200使用前复制成.env文件,并填入真实 Key:
cp .env.example .envOPENAI_BASE_URL写成https://api.openai.com/v1是因为 SDK 会在后面拼接chat/completions。如果使用其他大模型服务商的 OpenAI 兼容接口,通常只需要更换OPENAI_BASE_URL和MODEL_NAME。某些本地部署服务还可能要求关闭 SSL 校验,这属于联网层配置,生产环境要谨慎处理。
3.3 用 FastAPI 模拟 Elasticsearch REST 接口
本地不一定有真实 ES 集群,为了先验证 Agent 的工具调用逻辑,我用 FastAPI 模拟一个/logs/_search接口,返回结构和 Elasticsearch 搜索响应尽量保持一致。
from datetime import datetime, timedelta from typing import Optional import uvicorn from fastapi import FastAPI, Query app = FastAPI() BASE_MESSAGES = [ "DB connection pool exhausted for service order-service", "order-service timeout after 3 retries calling payment-service", "disk usage above 85% on node-01, elasticsearch data node", "Kafka consumer lag exceeds threshold for order_events", "user auth service returns 401 unexpectedly, token expired", ] @app.get("/logs/_search") def search_logs( service: Optional[str] = Query(default="order-service"), level: Optional[str] = Query(default="error"), minutes: int = Query(default=60, ge=1, le=1440), size: int = Query(default=5, ge=1, le=50), ): end = datetime.utcnow() start = end - timedelta(minutes=minutes) hits = [] for idx in range(size): ts = end - timedelta(minutes=idx * 7) if ts < start: continue hits.append({ "_index": "logs-service-2026.01.01", "_source": { "timestamp": ts.isoformat() + "Z", "service": service, "level": level, "message": BASE_MESSAGES[idx % len(BASE_MESSAGES)], "request_id": f"req-{idx:04d}", }, }) return { "took": 12, "timed_out": False, "hits": { "total": {"value": len(hits)}, "max_score": 1.0, "hits": hits, }, } if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=9200)这个 mock 接口接收service、level、minutes、size四个查询参数,返回一个类似 ES 搜索响应的 JSON。真实 ES 的响应字段会更多,但这里保留最关键的hits.total和hits.hits[]._source结构,已经足够让 Agent 完成日志分析。
注意:mock 服务只用于学习和本地调试。接入真实 ES 时,还需要处理索引名、认证方式、分页、聚合查询和网络策略,不能直接照搬。
3.4 环境检查点
启动 mock 服务:
python mock_es_server.py另开一个终端验证接口:
curl "http://127.0.0.1:9200/logs/_search?service=order-service&level=error&minutes=30&size=3"如果看到 JSON 响应,说明 mock 服务正常。此时再进行 Agent 代码编写,后面的工具调用才有数据来源。
4. 核心实现:让 Agent 通过工具调用自动查询并分析日志
这一节是整篇文章的核心。我会把 ES REST API 封装成 Tool,定义 JSON Schema,再实现一个完整的 Tool Calling 主循环。跑通之后,模型就能自己决定“何时查询日志、查哪些参数、如何分析结果”。
4.1 把 ES REST API 封装成 Tool
工具函数不只是简单地发一个 HTTP 请求,它还要对模型暴露清晰的名称、描述和参数结构。模型看不到 Python 函数内部实现,它只依赖函数描述来决定是否调用。
import json import os import sys import requests from dotenv import load_dotenv from openai import OpenAI load_dotenv() OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1") MODEL_NAME = os.getenv("MODEL_NAME", "gpt-4o-mini") ES_BASE_URL = os.getenv("ES_BASE_URL", "http://127.0.0.1:9200") def query_es_logs(service: str, level: str = "error", minutes: int = 60, size: int = 5) -> str: params = {"service": service, "level": level, "minutes": minutes, "size": size} resp = requests.get(f"{ES_BASE_URL}/logs/_search", params=params, timeout=10) resp.raise_for_status() return json.dumps(resp.json(), ensure_ascii=False)这个函数的返回值必须是字符串,因为 Tool Calling 协议中,工具结果最终作为一条字符串消息传回给模型。如果你返回一个 Python 对象,后续拼接消息时还要再转一次。
4.2 设计系统 Prompt 与 Tool Schema
Tool Schema 是模型理解工具的关键。它使用 JSON Schema 描述工具参数,模型会根据字段描述自动补全参数。
[ { "type": "function", "function": { "name": "query_es_logs", "description": "从 Elasticsearch 查询最近一段时间内指定服务和日志级别的日志,用于分析故障和异常。返回 Elasticsearch 搜索响应 JSON。", "parameters": { "type": "object", "properties": { "service": { "type": "string", "description": "服务名,例如 order-service" }, "level": { "type": "string", "enum": ["debug", "info", "warn", "error"], "description": "日志级别" }, "minutes": { "type": "integer", "description": "查询最近多少分钟,默认 60" }, "size": { "type": "integer", "description": "返回多少条日志,默认 5" } }, "required": ["service", "level"] } } } ]系统 Prompt 的作用是约束模型行为。