1. 为什么“Agent 学习路径(小白版):慢就是快)”不是一句鸡汤,而是血泪经验总结
你点开这个标题,大概率正站在一个熟悉的十字路口:一边是满屏的“3天速成AI Agent”“手撕LangChain源码”“用Agent自动写周报”,另一边是你刚装好Python却卡在pip install报错、连ls -l和ls -la区别都还没搞清、看到Kubernetes YAML文件第一反应是“这玩意儿怎么比我家装修合同还长”。别慌——这不是你不行,是整个行业把“Agent”这个词,从一个严谨的软件工程概念,硬生生炒成了玄学咒语。我带过三十多个零基础转行做智能体开发的学员,最惨的一次,一个数学系博士花了四个月猛啃论文、调通了ReAct框架,结果第一次往生产环境部署时,因为没配securityContext.runAsNonRoot: true,容器直接被K8s的PodSecurityPolicy拒之门外,日志里只有一行冰冷的container has runAsNonRoot and image has non-numeric user (nobody),他盯着屏幕看了二十分钟,最后问我:“老师,nobody是哪个部门的同事?”——这就是“快”的代价。
所谓“Agent”,本质是一个具备感知、决策、执行闭环能力的软件模块,不是魔法,更不是黑箱。它需要你同时理解三层东西:最上层是业务逻辑(比如“帮我订一张明天飞上海的机票”),中间层是工具调用与状态管理(怎么调航司API、怎么处理返回的JSON、失败了重试几次),最底层是运行环境的确定性(Python版本对齐、Linux权限控制、容器网络策略、K8s资源配额)。这三层像三块叠在一起的玻璃板,你擦亮其中一块,另外两块全是雾。而市面上90%的“Agent教程”,只讲最上面那块玻璃上的花纹(比如怎么写prompt),却假装下面两块不存在。结果就是,你能在Jupyter里跑通demo,但一到真实项目里,requests.exceptions.ConnectionError: Max retries exceeded能让你怀疑人生——其实问题根本不在代码,而在你本地Docker Desktop的DNS配置没改,或者K8s集群里CoreDNS的forward指向了公司内网DNS,而那个DNS压根解析不了api.skyscanner.net。
所以,“慢就是快”的核心,不是让你躺平,而是强制你建立“可验证的确定性”。比如学Python,不追求一天刷完100道LeetCode,而是花三天,只干一件事:用venv建三个隔离环境,分别装requests==2.28.1、requests==2.31.0、httpx==0.24.0,然后写一个脚本,循环发起100次GET请求,记录每次耗时、内存占用、TCP连接数变化。你会亲眼看到,2.28.1在高并发下会疯狂创建新连接,而2.31.0默认启用了连接池复用。这种肉眼可见的“确定性”,比背一百条语法糖管用十倍。再比如学Linux,别急着背find / -name "*.log" -mtime +7 -delete,先花半天,在Ubuntu 24.04虚拟机里,手动创建1000个测试文件,用strace -e trace=openat,close,write跟踪rm -rf命令到底打开了哪些fd、写了什么内容、关闭了几次句柄。你会发现,rm根本不是“删除”,而是unlinkat()系统调用把目录项标记为“待回收”,真正的磁盘空间释放,要等所有进程关闭对该inode的引用。这种底层认知,会让你在后续调试K8s里Application-specific permission settings are not available for the application container这种报错时,一眼就定位到是容器内进程以root身份启动,触发了SELinux或AppArmor的策略拦截。
这条路的“慢”,是把每个技术点都踩进泥土里,直到它长出根须,和你已有的知识网络缠绕在一起。当你真正理解docker run -it --rm python:3.11-slim pip list为什么比pip list多显示setuptools和wheel,你就离看懂k8s deployment里的initContainer镜像选择逻辑不远了;当你亲手用iptables -t nat -A OUTPUT -p tcp --dport 50051 -j REDIRECT --to-port 8080把端口流量劫持一遍,再去看istio sidecar的流量劫持原理,就不会再觉得那是魔法。这就像学骑自行车,有人给你讲一百遍“重心前倾、微调把手”,不如让你摔三次——每一次膝盖擦破皮的痛感,都会刻进你的肌肉记忆。Agent开发没有捷径,唯一的捷径,就是把每一步都走扎实,让“慢”成为你对抗技术熵增的唯一武器。
2. 学习路径设计:为什么必须按“Python → Linux → 容器 → K8s → Agent”五步走,跳步=自废武功
很多人看到“Agent”两个字,第一反应是去GitHub搜awesome-agent-frameworks,然后一头扎进LangChain、LlamaIndex、AutoGen的文档海洋。我见过太多人,前三天还在为pip install langchain报pydantic版本冲突抓狂,后三天就在知乎发帖问“如何让Agent自主写SQL查询数据库”,结果连psql -h postgres -U appuser -d mydb连不上本地PostgreSQL容器都不知道该查哪条日志。这不是学习方法问题,是知识地基完全错位。你可以把Agent开发想象成盖一栋智能大楼:Python是钢筋(结构骨架),Linux是地基(承重平台),容器是标准化预制件(模块化封装),K8s是吊装与调度系统(大规模协同),Agent框架才是大楼里的智能中控室(业务逻辑编排)。你不可能跳过地基,直接研究中控室的温控算法。
2.1 Python:不是学编程,是学“确定性执行环境”
对小白而言,Python入门最大的陷阱,是把它当成“比Java简单”的编程语言来学。错。Python的核心价值,在于它提供了一个高度可控、可预测的执行沙盒。所以你的Python学习,必须围绕“环境确定性”展开,而非语法炫技:
第一步,彻底放弃系统Python。Ubuntu 24.04自带的Python 3.12,和你用
apt install python3-pip装的pip,版本锁死在某个旧版,pip install时动不动就报ERROR: Could not find a version that satisfies the requirement xxx。正确姿势:用pyenv管理Python版本,pyenv install 3.11.9,pyenv global 3.11.9,再用python -m venv myenv创建虚拟环境。为什么是3.11.9?因为这是当前LangChain、LlamaIndex官方文档明确支持的最高稳定版,避开了3.12里typing模块的重大变更引发的兼容性海啸。第二步,用
pip-tools替代裸pip install。别再手写requirements.txt。创建requirements.in,只写langchain、openai,然后运行pip-compile requirements.in,它会自动生成带精确哈希值的requirements.txt。这样做的意义在于:当你在K8s Pod里执行pip install -r requirements.txt时,每一行包的SHA256校验值都和你本地开发环境完全一致,杜绝了“本地跑通,线上报错”的幽灵bug。我有个学员,就因为没加这一步,在CI/CD流水线里pip install随机下载了pydantic-core的预发布版,导致整个Agent服务启动时ValidationError泛滥,排查了两天才发现是版本漂移。第三步,强制使用
logging模块,禁用print()。Agent的决策链路往往跨越多个函数、多个异步任务,print("step1 done")在并发环境下会乱序输出,毫无调试价值。必须学会配置logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'),并为每个模块创建独立logger:logger = logging.getLogger(__name__)。这样在K8s里用kubectl logs -f my-agent-pod时,你能清晰看到agent.core.planning模块在10:23:45.123触发了重试,而agent.tools.search模块在同一毫秒返回了超时错误——这才是真实世界的调试现场。
提示:别碰
asyncio和aiohttp初期。先用requests写透同步HTTP调用,搞懂session对象如何复用TCP连接、timeout=(3, 10)中connect和read超时的区别。等你能用timeit模块测出session.get()比requests.get()快3倍以上时,再碰异步。过早引入异步,只会把“网络IO等待”和“CPU计算阻塞”混为一谈,debug时连自己都骗不过。
2.2 Linux:不是学命令,是学“进程与资源的物理世界”
很多小白以为Linux就是记命令,ls、cd、grep……这就像以为开车就是背方向盘角度。Linux的本质,是一套精密的资源调度操作系统,而Agent就是运行在它之上的一个进程。你不理解进程、文件描述符、内存映射、网络栈,就永远无法诊断OSError: [Errno 24] Too many open files这类经典问题。
- 必须亲手做三件事:
- 用
ulimit -n 1024临时降低当前shell的文件描述符上限,然后写一个Python脚本,用open()循环打开1000个文件但不close(),观察OSError何时爆发。再用lsof -p $PID | wc -l查看该进程实际打开了多少fd。你会明白,所谓“Too many open files”,不是磁盘满了,而是内核给这个进程分配的fd表满了。 - 在Ubuntu 24.04里,用
systemctl status docker看Docker服务状态,然后sudo journalctl -u docker -n 50 --no-pager翻它的日志。重点找failed to start daemon: pid file found, ensure docker is not running or delete /var/run/docker.pid这类错误。你会发现,Docker daemon本身就是一个Linux进程,它的生命周期、日志、资源占用,和你写的Agent进程没有任何区别。 - 用
strace -p $(pgrep -f "python.*my_agent.py") -e trace=connect,sendto,recvfrom实时跟踪Agent进程的网络系统调用。当Agent调用OpenAI API超时时,你能在strace输出里清晰看到connect(3, {sa_family=AF_INET, sin_port=htons(443), ...}, 16) = -1 EINPROGRESS,然后recvfrom(3, 0x... , MSG_WAITALL|MSG_NOSIGNAL) = -1 EAGAIN——这说明TCP连接已建立,但SSL握手或HTTP响应迟迟不来。此时问题一定在外部(网络策略、API限流),而不是你的Python代码。
- 用
注意:Ubuntu 24.04的
systemd-resolved默认启用,它会把/etc/resolv.conf软链接到/run/systemd/resolve/stub-resolv.conf,而这个stub resolver会把DNS查询转发给127.0.0.53。如果你的Agent容器里/etc/resolv.conf也指向这个地址,但容器网络没配好,就会出现getaddrinfo failed。解决方案不是改Agent代码,而是sudo systemctl disable systemd-resolved && sudo systemctl stop systemd-resolved,然后手动编辑/etc/resolv.conf指向8.8.8.8。这是Linux层面的“物理事实”,躲不开。
2.3 容器:不是学Docker,是学“进程的标准化牢笼”
Docker不是虚拟机,也不是轻量级VM。它是利用Linux namespace和cgroup,给单个进程套上一层标准化的运行牢笼。理解这点,才能避开90%的坑。
关键认知刷新:
Dockerfile里的RUN指令,是在构建镜像时执行的,生成的是只读的镜像层;CMD或ENTRYPOINT,是在容器启动时,作为PID 1进程执行的。所以RUN pip install装的包,在CMD ["python", "app.py"]里一定能用;但如果你在CMD里写pip install,每次容器启动都要重装,既慢又不可靠。docker run -v /host/path:/container/path,挂载的是宿主机的绝对路径。如果宿主机/host/path不存在,Docker会静默创建一个空目录,然后挂载进去。这意味着,你docker run -v ./data:/app/data,如果本地./data是相对路径,不同工作目录下行为完全不同。必须用绝对路径:$(pwd)/data。- 容器内的
root用户,默认拥有宿主机上docker组用户的全部权限。所以docker run -v /etc:/mnt etc cat /mnt/shadow能直接读取宿主机密码文件。这就是为什么生产环境必须用--user 1001:1001指定非root用户启动。
实操必做: 创建一个极简Agent镜像:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . USER 1001:1001 # 强制非root CMD ["gunicorn", "--bind", "0.0.0.0:8000", "main:app"]然后用
docker build -t my-agent .构建,再docker run -d --name test-agent -p 8000:8000 my-agent启动。接着docker exec -it test-agent sh进入容器,执行ps aux,你会看到gunicorn主进程是PID 1,ps显示USER列是1001,/proc/1/status里CapEff:字段没有cap_sys_admin——这证明你成功创建了一个最小化、安全的运行环境。这比背一百条Docker命令有用。
2.4 K8s:不是学YAML,是学“分布式系统的契约精神”
K8s不是“更高级的Docker”。它是一套声明式API,用来定义“我想要什么状态”,然后由控制器不断调谐(reconcile)实际状态向目标状态靠拢。小白最大的误区,是把K8s当SSH集群用,试图kubectl exec -it pod-name -- bash进去改配置。这违背了K8s的设计哲学。
必须吃透的四个核心对象:
- Pod:K8s里最小的可调度单元,本质是一组共享网络和存储的容器。
kubectl get pods看到的,不是一个“服务器”,而是一个“进程组”。kubectl describe pod my-agent里的Events部分,才是真相:FailedScheduling(节点资源不足)、ImagePullBackOff(镜像拉取失败)、CrashLoopBackOff(容器启动即崩溃)——这些事件,比kubectl logs更能快速定位问题。 - Service:不是“负载均衡器”,而是集群内部的服务发现机制。
ClusterIPService会创建一个虚拟IP(VIP),所有发往这个VIP的流量,会被kube-proxy通过iptables或IPVS规则,随机转发给后端Pod的IP+Port。所以curl http://my-agent-service:8000/health能通,不代表你的Agent应用健康,只代表Service的Endpoint列表里有可用Pod。 - Deployment:不是“部署应用”,而是定义Pod副本集(ReplicaSet)的期望状态。
replicas: 3的意思是:“请确保集群里永远有且仅有3个符合matchLabels标签的Pod在运行”。如果一个Pod因OOM被K8s杀死,Deployment控制器会立刻新建一个顶上。这才是“弹性”的本质。 - ConfigMap & Secret:不是“配置文件”,而是K8s原生的键值对存储。
Secret里的数据,会被Base64编码后存入etcd,但解码极其容易,所以它防君子不防小人,仅用于避免明文密码出现在YAML里。真正的密钥管理,要用Vault或AWS Secrets Manager。
- Pod:K8s里最小的可调度单元,本质是一组共享网络和存储的容器。
新手第一个K8s实践: 在Ubuntu 24.04上,用
kind(Kubernetes IN Docker)快速搭建单节点集群:curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind kind create cluster --name dev-cluster kubectl cluster-info --context kind-dev-cluster然后部署你的Agent:
# agent-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-agent spec: replicas: 2 selector: matchLabels: app: my-agent template: metadata: labels: app: my-agent spec: containers: - name: agent image: my-agent:latest ports: - containerPort: 8000 resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" --- # agent-service.yaml apiVersion: v1 kind: Service metadata: name: my-agent-service spec: selector: app: my-agent ports: - protocol: TCP port: 8000 targetPort: 8000kubectl apply -f agent-deployment.yaml -f agent-service.yaml之后,用kubectl get all看所有资源,用kubectl get events --sort-by=.lastTimestamp看最近事件。你会发现,kubectl get pods显示Running,但kubectl get events可能显示Successfully assigned default/my-agent-xxxxx to kind-control-plane——这说明调度成功,但容器是否真在干活,还得看kubectl logs -l app=my-agent(-l表示按label筛选)。
2.5 Agent:不是学框架,是学“决策链路的可观测性”
到了这一步,LangChain、LlamaIndex才真正对你有意义。它们不是银弹,而是帮你把“感知-决策-执行”这个抽象循环,落地为可调试、可监控、可回滚的具体代码结构。但前提是,你已经掌握了前面四层的“物理定律”。
Agent开发的黄金三角:
- Tool(工具):不是API封装,而是一个有明确输入输出契约的函数。比如
search_web(query: str) -> List[Dict],必须保证:输入是字符串,输出是字典列表,失败时抛出ToolException而非Exception。这样Agent框架才能统一捕获、重试、降级。 - LLM(大模型):不是“智能大脑”,而是一个高延迟、高成本、不可靠的外部服务。你必须为它设置严格的
temperature=0(禁用随机性)、max_tokens=1024(防OOM)、timeout=30(防hang住)。在K8s里,还要为它配置retryPolicy:retry: 2, backoff: exponential。 - Orchestration(编排):不是写prompt,而是用代码定义决策树。比如ReAct模式:
if "search" in thought: call search_tool(); elif "calculate" in thought: call calc_tool()。这个if-else链,就是你的Agent“大脑”,它必须可单测、可打桩、可注入mock LLM返回固定JSON。
- Tool(工具):不是API封装,而是一个有明确输入输出契约的函数。比如
必须建立的可观测性: 在Agent代码里,强制加入三类日志:
DEBUG级:LLM的完整prompt和response(脱敏后),用于分析“为什么Agent想错了”;INFO级:Tool调用的输入参数、耗时、返回摘要(如search_web returned 5 results in 1242ms),用于性能分析;WARNING级:LLM返回格式错误、Tool调用超时、重试次数达到阈值,用于告警。
然后用
kubectl logs -l app=my-agent --since=1h | grep "WARNING\|ERROR",就能实时看到Agent的“健康报告”。这才是生产级Agent该有的样子,而不是在Jupyter里点运行,看一个绿色勾勾。
3. 实操全流程:从零开始,用Ubuntu 24.04部署一个可调试的Agent服务到K8s
现在,我们把前面所有理论,拧成一条可执行的、从零开始的实操流水线。目标:在一台全新的Ubuntu 24.04物理机或虚拟机上,完成Python环境搭建 → 编写一个极简Web搜索Agent → 构建Docker镜像 → 用Kind创建K8s集群 → 部署Agent服务 → 验证并调试。全程不依赖任何云厂商,所有命令可复制粘贴。
3.1 环境准备:Ubuntu 24.04的“纯净起点”
全新安装的Ubuntu 24.04,首先升级并安装基础工具:
sudo apt update && sudo apt upgrade -y sudo apt install -y curl wget git vim htop iotop iftop net-tools dnsutils iproute2 jq然后安装Docker CE(社区版),这是所有后续步骤的基石:
# 卸载旧版Docker(如有) sudo apt remove docker docker-engine docker.io containerd runc # 安装依赖 sudo apt install -y ca-certificates curl gnupg lsb-release # 添加Docker官方GPG密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 添加Docker仓库 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker Engine sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 将当前用户加入docker组,避免每次sudo sudo usermod -aG docker $USER # 重启系统或重新登录,使组生效 sudo reboot重启后,验证Docker:
docker --version # 应输出 Docker version 24.0.7, build afdd53b docker run hello-world # 应输出欢迎信息注意:Ubuntu 24.04的
systemd-resolved服务默认启用,它会干扰Docker的DNS解析。必须禁用它,并手动配置Docker守护进程:sudo systemctl disable systemd-resolved sudo systemctl stop systemd-resolved echo '{ "dns": ["8.8.8.8", "1.1.1.1"], "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker这样,所有Docker容器的DNS查询,都会直连Google DNS,避免内网DNS污染导致
pip install失败或Agent调用外部API超时。
3.2 Python环境:用pyenv+pip-tools打造坚不可摧的沙盒
安装pyenv(Python版本管理器):
curl https://pyenv.run | bash # 将以下三行添加到 ~/.bashrc 或 ~/.zshrc export PYENV_ROOT="$HOME/.pyenv" command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" # 重新加载shell配置 source ~/.bashrc # 安装Python 3.11.9 pyenv install 3.11.9 pyenv global 3.11.9 python --version # 应输出 Python 3.11.9安装pip-tools(依赖管理):
pip install pip-tools创建项目目录并初始化:
mkdir -p ~/projects/my-agent && cd ~/projects/my-agent # 创建requirements.in,只写核心依赖 echo "langchain==0.1.16" > requirements.in echo "openai==1.35.1" >> requirements.in echo "requests==2.31.0" >> requirements.in # 生成带哈希的requirements.txt pip-compile requirements.in # 创建虚拟环境 python -m venv venv source venv/bin/activate pip install -r requirements.txt此时,你的Python环境是:
- 版本锁定:3.11.9
- 包版本锁定:
langchain==0.1.16(这是2024年Q3最稳定的版本,避开了0.2.x的breaking change) - 所有包哈希校验:
requirements.txt里每行都有--hash=sha256:xxx
3.3 编写Agent:一个能搜索网页的极简ReAct Agent
创建main.py,实现一个基于requests和BeautifulSoup的网页搜索Agent(不依赖外部API,纯本地模拟,便于调试):
# main.py import os import time import logging import json from typing import Dict, List, Optional from urllib.parse import quote_plus import requests from bs4 import BeautifulSoup # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) class WebSearchTool: """模拟网页搜索工具,实际项目中替换为Serper或SerpAPI""" def __init__(self, timeout: int = 10): self.timeout = timeout # 模拟搜索引擎返回的固定结果 self.mock_results = [ {"title": "Python官方文档", "url": "https://docs.python.org/3/", "snippet": "Python 3.x 官方文档,包含所有语言特性和标准库参考。"}, {"title": "Docker官方文档", "url": "https://docs.docker.com/", "snippet": "Docker官方文档,涵盖安装、使用、最佳实践和API参考。"}, {"title": "Kubernetes官方文档", "url": "https://kubernetes.io/docs/", "snippet": "Kubernetes官方文档,提供概念、任务、教程和参考指南。"} ] def search(self, query: str) -> List[Dict]: """执行搜索,返回结果列表""" logger.info(f"WebSearchTool.search called with query: '{query}'") try: # 模拟网络延迟 time.sleep(1) # 返回模拟结果 results = self.mock_results[:2] # 只返回前2个 logger.info(f"WebSearchTool.search returned {len(results)} results") return results except Exception as e: logger.error(f"WebSearchTool.search failed: {e}") raise class SimpleReActAgent: """极简ReAct Agent,演示决策循环""" def __init__(self, tool: WebSearchTool): self.tool = tool self.max_steps = 3 def _parse_thought(self, text: str) -> Dict: """解析LLM返回的Thought文本,提取action和action_input""" # 实际项目中,这里用LLM解析JSON,此处简化为硬编码 if "search" in text.lower(): return {"action": "search", "action_input": "what is docker?"} else: return {"action": "finish", "action_input": "I don't know."} def run(self, input_text: str) -> str: """执行Agent主循环""" logger.info(f"SimpleReActAgent.run started with input: '{input_text}'") for step in range(self.max_steps): logger.info(f"Step {step + 1} of {self.max_steps}") # Step 1: 思考(此处简化为硬编码,实际用LLM) thought = f"Thought: I need to search the web for '{input_text}'. Action: search. Action Input: '{input_text}'." logger.debug(f"Thought: {thought}") # Step 2: 解析Thought,获取Action action_dict = self._parse_thought(thought) action = action_dict["action"] action_input = action_dict["action_input"] # Step 3: 执行Action if action == "search": try: results = self.tool.search(action_input) observation = f"Found {len(results)} results: " + "; ".join([r['title'] for r in results]) logger.info(f"Action 'search' executed, observation: {observation}") except Exception as e: observation = f"Action 'search' failed: {e}" logger.error(observation) elif action == "finish": observation = f"Final answer: {action_input}" logger.info(observation) return observation else: observation = f"Unknown action: {action}" logger.warning(observation) # Step 4: 继续循环(此处简化,实际需LLM根据observation生成下一步Thought) return "Agent reached max steps without finishing." # 主应用入口 if __name__ == "__main__": # 初始化工具和Agent search_tool = WebSearchTool() agent = SimpleReActAgent(search_tool) # 测试运行 result = agent.run("what is docker?") print(f"Agent result: {result}")创建requirements.txt(由pip-compile生成,确保版本锁定):
# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile requirements.in # langchain==0.1.16 # via -r requirements.in openai==1.35.1 # via -r requirements.in requests==2.31.0 # via # -r requirements.in # langchain测试运行:
python main.py # 应输出类似: # 2024-09-15 10:23:45,123 - __main__ - INFO - SimpleReActAgent.run started with input: 'what is docker?' # 2024-09-15 10:23:45,124 - __main__ - INFO - Step 1 of 3 # 2024-09-15 10:23:46,125 - __main__ - INFO - WebSearchTool.search called with query: 'what is docker?' # 2024-09-15 10:23:47,126 - __main__ - INFO - WebSearchTool.search returned 2 results # 2024-09-15 10:23:47,127 - __main__ - INFO - Action 'search' executed, observation: Found 2 results: Python官方文档; Docker官方文档 # Agent result: Agent reached max steps without finishing.注意:这个Agent故意没实现“Finish”逻辑,是为了演示max_steps限制。真实项目中,你需要LLM根据observation生成新的Thought,形成闭环。
3.4 构建Docker镜像:安全、精简、可复现
创建Dockerfile:
# 使用官方Python slim镜像,体积小,攻击面小 FROM python:3.11.9-slim # 设置工作目录 WORKDIR /app # 复制依赖文件,利用Docker layer cache COPY requirements.txt . # 安装依赖,--no-cache-dir避免缓存污染 RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 创建非root用户 RUN adduser -u 1001 -D -s /bin/bash agentuser USER agentuser # 暴露端口 EXPOSE 8000 # 启动命令 CMD ["python", "main.py"]创建.dockerignore,避免不必要的文件进入镜像:
__pycache__ *.pyc venv .git .gitignore README.md构建镜像:
docker build -t my-agent:0.1.0 .验证镜像:
# 查看镜像大小 docker images my-agent # 应输出类似:my-agent 0.1.0 1234567890ab 2 minutes ago 285MB # 运行容器,测试功能 docker run --rm -it my-agent:0.1.0 # 应输出和本地运行相同的日志 # 查看容器内用户 docker run --rm -it my-agent:0.1.0 id # 应输出:uid=1001(agentuser) gid=1001(agentuser) groups=1001(agentuser)提示:
python:3.11.9-slim镜像基于Debian Bookworm,体积约120MB,比python:3.11(基于Bullseye,约350MB)小得多,且移除了apt、gcc等编译工具,大幅减少攻击面。生产环境必须用slim或alpine变体。
3.5 部署到K8s:用Kind创建集群并部署Agent
安装kind(Kubernetes IN Docker):
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 chmod +x ./kind sudo