【L1阶段总结】Agent应用开发零基础入门:我是怎么从0开始理解的
这篇文章适合谁
如果你是Java后端开发,想转型Agent应用开发,但不知道从哪里下手------这篇文章是我的学习笔记,记录我从"听不懂Agent是什么"到"能跑通第一次LLM调用"的全过程。
不是百科全书式整理,是我自己消化后的理解,所以会有我的疑问和思考过程。
一、Agent到底是什么?
1.1 第一次听到"Agent",我在想什么
听到"Agent"这个词,我脑子里第一个反应是:这是什么?特工?电影里的AI机器人?
后来听到"AI Agent"这个词,又想:是不是就是接入了AI的系统?比如加了个GPT-4的系统?
如果你也有类似想法,恭喜,你和我一开始一样------理解跑偏了。
1.2 普通微服务是怎么工作的
在解释Agent之前,先想清楚一个普通Java微服务是怎么处理请求的:
用户发起请求
↓
Controller 接收(@RequestMapping)
↓
Service 处理(写死的业务逻辑)
↓
数据库查数据 / 调用其他微服务
↓
返回结果
这个流程的特点是什么?每一步都是程序员写死的 。你写 if (status == "已发货") { refund() },它就永远这样执行,不会自己判断。
1.3 Agent的本质:让模型决定下一步做什么
好,现在换个思路:
用户:"我想退货"
↓
LLM(大脑)分析:用户想要退货,需要先查订单状态
↓
LLM决定:调用"查询订单"工具,参数是订单号A12345
↓
拿到订单状态:已发货,可以退款
↓
LLM决定:调用"执行退款"工具
↓
返回:"退款成功"
核心区别在这里:
- 普通微服务:程序员写死 if-else,代码决定下一步
- Agent:LLM(大模型)自己推理出下一步该做什么
自测一下:Agent和普通微服务的本质区别是什么?花10秒钟自己想一下,再往下看。
我的理解:Agent有一个"大脑"(LLM),它能根据用户输入自己决定调用什么工具、怎么组合,而普通微服务所有逻辑都是写死在代码里的。
二、Agent核心四件套:我花了一整天才理清楚
刚开始学的时候,我听到四个词:LLM、System Prompt、Function Calling、Memory。一脸懵。后来用Java类比,一下就通了。
2.1 第一件:LLM------Agent的大脑
类比 :想象你的Spring Boot应用里内置了一个超级 Drools 规则引擎。
Drools你知道,用规则描述业务逻辑。但Drools需要你预先把所有规则一条一条写出来。LLM不一样------你用自然语言描述,它自己学会规则,而且能处理你没见过的场景。
2.2 第二件:System Prompt------Agent的配置文件
类比 :这就像Spring的 application.yml。
yaml
# application.yml 定义了一个UserService的行为边界
spring:
application:
name: user-service
datasource:
url: jdbc:mysql://...
hibernate:
ddl-auto: validate
System Prompt的作用一模一样:定义这个Agent是谁、能做什么、不能做什么、输出格式是什么。
python
# System Prompt示例:定义一个"代码审查Agent"
system_prompt = """
你是一个资深Java架构师,负责代码审查。
【你能做什么】
- 发现性能问题(N+1查询、内存泄漏)
- 发现安全隐患(SQL注入、硬编码密码)
- 发现并发缺陷(线程安全)
【你不能做什么】
- 不纠结格式问题(空行、命名风格)
- 不回答非代码相关问题
- 每次最多指出5个问题,聚焦最重要
【输出格式】
每个问题:位置 → 原因 → 建议方案
"""
2.3 第三件:Function Calling------Agent调用工具的方式
类比 :这就像Spring Cloud的 @FeignClient。
用Feign Client调用其他微服务,你需要:
- 定义接口
- 声明方法签名
- 写好URL和参数
Function Calling类似,你需要:
- 定义函数签名(名称、描述、参数)
- 告诉LLM"你有这些工具可用"
- LLM自己决定该调哪个、参数填什么
python
# 定义两个工具
functions = [
{
"name": "查询订单",
"description": "根据订单ID查询订单详情",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "订单ID"}
},
"required": ["order_id"]
}
},
{
"name": "执行退款",
"description": "对指定订单执行退款",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"amount": {"type": "number"}
},
"required": ["order_id", "amount"]
}
}
]
# 用户说:"我想退货"
# LLM自动推理:先查订单 → 判断可退 → 执行退款
# 你不需要写 if-else,LLM自己决定
2.4 第四件:Memory------Agent的记忆
类比:这就像 Redis(短期记忆)+ MySQL(长期记忆)。
python
memory = {
# 短期记忆:当前对话,类似Redis
# 用户刚说了什么,Agent正在处理什么
"short_term": [
{"role": "user", "content": "我要查订单"},
{"role": "assistant", "content": "请问订单号是多少?"},
{"role": "user", "content": "A12345"},
],
# 长期记忆:历史积累,类似MySQL
# 用户偏好、公司政策、过往交互记录
"long_term": [
{"type": "policy", "content": "退货政策:收货7天内可退"},
{"type": "history", "content": "用户A12345历史订单5笔,退货2次"},
]
}
四件套串联起来
用户输入
↓
LLM接收 + System Prompt约束(你是谁/能做什么)
↓
LLM推理:"用户想退货,需要先查订单状态"
↓
Function Calling执行工具(调用查询订单API)
↓
结果写回Memory
↓
LLM根据结果决定下一步(执行退款)
↓
返回结果给用户
三、环境搭建:我踩过的那些坑
3.1 搭建过程我走了弯路
我的机器上其实早就装了Python 3.13.7 ,安装在 D:\AAAProgram Files\Python\。但我一开始不知道,装了另一个Python到默认位置,后来清理了很久。
教训 :安装软件之前,一定要先检查 D:\AAAProgram Files\ 等常见自定义安装目录 ,不能只靠 where python 查PATH。
3.2 最终环境状态
| 组件 | 版本 | 安装位置 | 备注 |
|---|---|---|---|
| Python | 3.13.7 | D:\AAAProgram Files\Python\python\ | 用户原有安装 |
| Java | 17.0.17 LTS | 系统默认 | Spring Boot要求满足 |
| Node.js | v20.19.0 | 系统默认 | JS开发用 |
| DeepSeek API | deepseek-chat | - | Key已配置 |
3.3 DeepSeek API Key:我选它的原因
我选DeepSeek有三个理由:
- 便宜:¥0.1/千Token输入,¥0.28/千Token输出,注册送额度
- 国内可访问:不需要梯子
- 中文支持好:比OpenAI API在国内的体验好
申请地址:https://platform.deepseek.com/
3.4 pip清华镜像配置
因为在国内,直接用pip安装包会非常慢。需要配置国内镜像:
powershell
python -m pip install pip --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
四、第一次LLM调用:我的第一个"Hello World"
4.1 先搞懂Token是什么
API按Token计费,不是按字数。简单记:
- 英文:1 Token ≈ 4个字符
- 中文:1 Token ≈ 1-2个汉字
一个中等句子 ≈ 20-50 tokens。不需要精确计算,但要有量级概念,否则发一条请求几十块钱就没了(夸张了,但确实要心里有数)。
4.2 我的第一个Python版本
python
# -*- coding: utf-8 -*-
# 设定文件编码为utf-8,防止中文乱码
# 导入HTTP请求库,用于发送网络请求调用大模型接口
import httpx
# 导入系统环境变量模块,用于安全读取密钥
import os
# 优先从系统环境变量读取API密钥,没有则使用默认备用密钥
# 避免密钥硬编码泄露,提升安全性
API_KEY = os.environ.get("你的key的名称", "你的key")
# DeepSeek 对话模型官方接口请求地址
url = "https://api.deepseek.com/chat/completions"
# 构造请求头信息
headers = {
# 指定请求体数据格式为JSON
"Content-Type": "application/json",
# 接口身份鉴权,Bearer模式携带密钥
"Authorization": f"Bearer {API_KEY}"
}
# 构造请求体核心参数
payload = {
# 指定调用的模型:deepseek-chat 通用对话模型
"model": "deepseek-chat",
# 对话消息列表,遵循大模型标准对话格式
"messages": [
# role角色:user用户,content为用户提问内容
{"role": "user", "content": "用一句话解释什么是Agent"}
],
# 限制模型最大输出token数量,控制回复长度
"max_tokens": 200,
# 温度系数:控制回答随机性,0严谨固定,1发散创意
"temperature": 0.7,
# 非流式返回,一次性返回完整结果(默认关闭)
# "stream": False
}
# 发送POST请求,传入地址、请求头、json参数,设置30秒超时时间
response = httpx.post(url, headers=headers, json=payload, timeout=30)
# 主动抛出HTTP错误,4xx/5xx请求异常直接报错
response.raise_for_status()
# 将接口返回的json字符串转为字典,方便解析
result = response.json()
"""
接口返回的 JSON 长这样(简化版)
{
"id": "xxx",
"object": "chat.completion",
"created": 123456,
"model": "deepseek-chat",
"choices": [ <-- 第一层:choices 是一个【列表】
{ <-- 列表里的第 0 个元素(第一个)
"index": 0,
"message": { <-- 第二层:message 是一个【字典】
"role": "assistant",
"content": "Agent就是能自主思考、调用工具完成任务的AI智能体" <-- 你要的内容
},
"finish_reason": "stop"
}
],
"usage": {...}
}
"""
# 解析提取大模型返回的回答内容
reply = result["choices"][0]["message"]["content"]
# 获取本次调用的token消耗统计数据
usage = result["usage"]
# 打印AI回复内容
print(f"回复: {reply}")
# 打印详细token消耗:输入、输出、总消耗,用于计费和调试
print(f"消耗: 输入={usage['prompt_tokens']} | 输出={usage['completion_tokens']} | 总={usage['total_tokens']}")
运行结果:
回复: Agent是一种能够感知环境、自主决策并执行动作的智能系统...
Token消耗: 输入=18 | 输出=45
几个参数我是这样理解的:
model:用哪个模型,就像选择用哪个JDK版本max_tokens:限制输出长度,防止LLM废话太多temperature:0=固定输出(适合测试),1=随机(适合创意写作)
4.3 我的第一个Java版本
Java没有内置的OpenAI Client,但Java 11的HttpClient够用:
java
package com.agentstudy;
import java.net.URI;
import java.net.http.*;
import java.util.*;
public class FirstLLMCall {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("DEEPSEEK_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
System.out.println("请设置 DEEPSEEK_API_KEY 环境变量");
return;
}
// 构建请求体
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("model", "deepseek-chat");
payload.put("max_tokens", 200);
payload.put("temperature", 0.7);
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of(
"role", "user",
"content", "用一句话解释什么是Agent"
));
payload.put("messages", messages);
// 发送请求
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.deepseek.com/chat/completions"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey)
.POST(HttpRequest.BodyPublishers.ofString(toJson(payload)))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("状态码: " + response.statusCode());
System.out.println("响应: " + response.body());
}
// 简单JSON序列化(演示用,生产环境用Jackson)
static String toJson(Map<String, Object> map) {
StringBuilder sb = new StringBuilder("{");
var it = map.entrySet().iterator();
while (it.hasNext()) {
var e = it.next();
sb.append("\"").append(e.getKey()).append("\":");
Object v = e.getValue();
if (v instanceof String s) {
sb.append("\"").append(s.replace("\\", "\\\\").replace("\"", "\\\"")).append("\"");
} else if (v instanceof List l) {
sb.append("[");
for (int i = 0; i < l.size(); i++) {
if (i > 0) sb.append(",");
Map<?, ?> m = (Map<?, ?>) l.get(i);
sb.append("{\"role\":\"").append(m.get("role"))
.append("\",\"content\":\"").append(m.get("content")).append("\"}");
}
sb.append("]");
} else {
sb.append(v);
}
if (it.hasNext()) sb.append(",");
}
sb.append("}");
return sb.toString();
}
}
运行:
bash
javac -encoding UTF-8 com/agentstudy/FirstLLMCall.java
java -cp . com.agentstudy.FirstLLMCall
4.4 流式输出是什么
普通调用:等LLM全部生成完 → 返回
流式调用:LLM边生成边返回 → 就像打字机,一个字一个字出来
python
# ========== Python 流式调用 ==========
import httpx
API_KEY = "你的key" # 硬编码测试
payload = {
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "写一个快排算法"}],
"max_tokens": 500,
"stream": True # 开启流式
}
"""
with语法
它帮你自动做两件事:
1. 打开资源
2. 用完自动关闭资源(不用你手动写关闭代码)
eg:
with 打开这个连接 as 给它起个名字叫 resp:
在里面使用它
"""
# with 打开流式请求
with httpx.stream("POST", "https://api.deepseek.com/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}"}, # 身份认证
json=payload, timeout=60) as resp: # 把问题发过去,并接收服务器的返回
# 在里面使用它
for line in resp.iter_lines(): # "服务器一段一段发回来的数据流" 这就是流式输出的核心。服务器边生成、边分段发,源源不断推送碎片化数据流,一段一段传给客户端。
if line.startswith("data: "): # 只处理以 data: 开头的行 (服务器发回来的数据流,只有以 data: 开头的才是真正内容)
data = line[6:] # 去掉前面的 data: ,拿到真正数据
if data == "[DONE]":
break
import json
chunk = json.loads(data) # 把 JSON 字符串转成 Python 字典
delta = chunk["choices"][0]["delta"].get("content", "")
if delta:
print(delta, end="", flush=True)
五、System Prompt:我被这个词吓到了,其实很简单
5.1 System Prompt是什么
System Prompt的本质就是三个字:定规矩。
就像新员工入职,公司给你一个职位说明书:
- 你是谁?(角色)
- 你做什么不做什么?(职责范围)
- 你汇报格式是什么?(输出格式)
System Prompt就是LLM的职位说明书。
5.2 我见过的反面教材
"你是一个AI助手,帮我回答问题。"
这个System Prompt的问题:
- "AI助手"是谁?Siri?ChatGPT?没有边界
- 回答格式?长度?风格?全部未知
- LLM自由发挥,结果完全不可控
5.3 正面教材:三层结构
第一层:角色设定
你是一个资深Java后端架构师,拥有10年以上Spring Boot/微服务开发经验,
专注于性能调优、并发安全、JVM原理。
第二层:行为约束(禁区)
【绝对禁区】
- 不回答与Java/后端无关的问题
- 不给出未经生产验证的"黑科技"
- 不在回复开头加"当然可以"/"很高兴为你服务"
第三层:输出格式
【输出格式】
- 技术问题:问题 → 原因 → 解决方案,分点列出
- 代码示例:必须标注JDK版本,附关键注释
- 结论必须明确,禁止"视情况而定"
python
import os, httpx
API_KEY = os.environ.get("DEEPSEEK_API_KEY", "sk-ed091521af1949809be4a740f7abc237")
url = "https://api.deepseek.com/chat/completions"
# system_prompt = """[把你的System Prompt粘贴在这里]"""
system_prompt = """[
你是一个专业的代码审查工程师,擅长发现Java代码中的:
- 性能问题(N+1查询、内存泄漏、慢查询)
- 安全隐患(SQL注入、硬编码密码、XSS)
- 并发缺陷(线程安全、资源竞争)
- 设计缺陷(违反SOLID原则、过度封装)
审查规则:
1. 严重问题(P0)必须指出:安全漏洞、内存泄漏、数据一致性风险
2. 一般问题(P1)建议指出:性能优化点、代码可读性
3. 格式问题(P2)忽略:不指出变量命名、注释格式、空行问题
4. 每次审查最多指出5个P0/P1问题(聚焦最重要)
输出格式:
## 审查结论
- 整体评级:[A/B/C/D](A=可直接合入,D=必须修改)
- 严重程度分布:P0:X个 | P1:X个 | P2:X个(忽略)
## 详细问题(按严重程度排序)
### [P0] 问题标题
- 位置:文件路径:行号 / 接口名
- 问题:简洁描述
- 原因:为什么这是个问题
- 建议:具体怎么改
## 可选优化建议(非强制)
(列出1-2个P2级建议,不强制要求修改)
]
"""
test_question = """以下代码有无安全问题?
```java
String sql = "SELECT * FROM users WHERE id = " + userId;
stmt.executeQuery(sql);
```"""
for i in range(3):
resp = httpx.post(url, headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}, json={
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": test_question}
],
"max_tokens": 500,
"temperature": 0 # 温度=0,测试一致性
}, timeout=30)
result = resp.json()
print(f"=== 第{i + 1}次回答 ===")
print(result["choices"][0]["message"]["content"])
print()
5.4 怎么验证System Prompt好不好
用 temperature=0 跑3次相同问题,然后检查:
- 一致性:3次回答核心结论是否一致(不能忽左忽右)
- 约束遵守:是否遵守了你设的禁区
- 聚焦程度:是否只回答了你定义的范围
六、Java后端类比总结(快速查阅)
| Agent概念 | Java对应 | 一句话理解 |
|---|---|---|
| Agent | 带大脑的微服务 | 主动决策,不只是接请求 |
| LLM | Drools规则引擎(超强版) | 自然语言写规则,模型自己推理 |
| System Prompt | application.yml | 定义行为边界 |
| Function Calling | @FeignClient | 模型决定调哪个接口 |
| Skill/Tool | Remote Service | 封装好的外部能力 |
| Memory | Redis+MySQL | 短期Redis,长期DB |
| RAG | Elasticsearch+Service | 检索+生成,类似搜索+推荐 |
| Multi-Agent | 微服务集群 | Agent间通信≈RPC调用 |
| Agent Framework | Spring Boot Starter | 脚手架,降低搭建成本 |
七、学习反思:我踩过的坑不希望你再踩
坑1:安装前没扫描自定义目录
只查PATH导致我装了两次Python。正确做法:
装任何软件前,先检查
D:\AAAProgram Files\、C:\Program Files\等常见路径。
坑2:pip命令找不到
解决方案:用 python -m pip 代替。
坑3:PowerShell中文输出乱码
涉及中文时,用Python脚本处理,不用PowerShell管道操作。
坑4:把Agent想得太复杂
一开始我觉得Agent要懂很多AI理论才能学。其实:会用API调用LLM、会写Prompt、会用Java调用HttpClient,就够了。
八、下一步学什么
L1完成,接下来是 L2 进阶:
- 2.1 Prompt工程体系(Zero-shot/Few-shot/CoT)
- 2.2 Function Calling(工具调用)
- 2.3 Agent框架入门(LangChain/Lang4j)
- 2.4 Memory机制(对话记忆)
最后留一个问题给你思考:
如果让你设计一个"技术文档翻译Agent",把英文文档翻译成中文并保持专业术语,你会怎么设计它的System Prompt?三层结构怎么写?
想好了吗?想好了就完成了L1的最后一个考核 ✅