从嵌入式开发转AI Agent的实战记录

从嵌入式开发转AI Agent的实战记录 玩了几年单片机、开发板现在想转型AI Agent开发本文记录作者从Python基础语法到搭建第一个Agent原型的全过程。适合刚学完Python面向对象、想理解Agent核心机制但不知道从何下手的同学一、为啥要学Agent开发目前大模型API已经普及但是单纯的“问答机器人”的价值还是太有限了。为啥现在各个大厂都在砸钱进军Agent市场因为Agent的核心能力相比于大模型来说还是太全面了。Agent的核心能力在于理解用户意图——自主决策——调用工具执行而我不想做调包侠而是想从Agent底层架构开始一步步往上搭梯子所以决定先造轮子再造车。二、学习路线对于已经学会一门编程语言或者刚学会Python的同学来说我们不可能从头学到尾一点点啃下来那样速度太慢了而且工作可不会等你所以这条路线只抓刚需不瞎卷。首先Agent开发的Python知识是很聚焦滴不需要去刷算法题因为对于我这种能力不足的人来说卷算法无疑是死路一条所以我们剑走偏锋曲线救国。阶段内容目的1Python基础语法面向对象能独立写类、继承、方法重写2Pydantic类型注解装饰器理解Agent框架的源码风格3asyncioAgent并发调用多个工具的命脉4第一个Agent项目同步版理解注册-路由-执行机制5接入大模型API让Agent有“脑子”6LangChain/LlamaIndex站在巨人的肩膀上三、Python新概念的通用理解Python 概念一句话解释本项目中的体现类继承子类继承父类的规范必须实现指定方法WeatherHandler继承TaskHandler必须写can_handle和handle多态父类引用指向子类对象调用时自动执行子类版本Agent只认TaskHandler类型自动调用具体子类的实现抽象方法NotImplementedError强制约束子类必须重写否则运行报错忘写handle就抛异常防止静默失败字典.get()安全查表查到返回值查不到给默认值mock_data.get(city, 未知天气)防止 KeyError 崩溃with open上下文管理自动开关文件异常时也保证关闭记事本读写文件不用手动f.close()try-except异常捕获出错时执行兜底逻辑程序不崩文件不存在或损坏时初始化空列表继续运行json.load/dumpPython 对象与 JSON 文本的双向转换记事本数据持久化到本地文件split(maxsplit1)字符串切分限制切分次数把用户输入拆成命令 参数两部分f-string字符串格式化变量直接嵌入f{city}天气{weather}setall()集合用于白名单all() 做批量校验计算器里逐字检查算式是否只含合法字符四、Agent工作流程图五、代码1、先看基类import json from datetime import datetime from abc import ABC, abstractmethod #基类 注意一下这里TaskHandler是继承ABC的一般来说是继承于object的 但是由于object 是普通基类子类可以偷懒不实现 ABC 是抽象基类子类必须实现所有抽象方法否则实例化时就报错。 在这个项目里用 ABC 就是为了 防止有人写 Handler 时 忘记重写 can_handle 和 handle 把错误扼杀在编译/启动阶段。 class TaskHandler(ABC): 所有任务处理器的基类 abstractmethod def can_handle(self, command: str)-bool: 判断是否能执行这个命令 pass 原本的代码应该是这么写的 def can_handle(self, command) raise NotImplementedError(子类必须实现) //提醒子类必须重写否则报错 command: str 这个是新写法告诉函数这个参数是字符串 -bool 告诉函数返回的应该是True或者Faults abstractmethod def handle(self, args:str)-str: 执行具体逻辑返回结果字符串 pass def get_name(self)-str: 返回处理器名称默认实现子类可重写 return self.__class__.__name__2、基于TaskHandler基类我们写以下子类#子类具体处理器 天气查询(模拟) class WeatherHandler(TaskHandler): #新建一个子类继承父类TaskHandler def can_handle(self, command: str) -bool: #父类函数重写,检测用户输入的命令关键词是否是weather,w,天气中的其中一个w是weather的简写可替换成其他的 #weather,w,天气这仨是自定义的可以随便写我这里是因为这个代码是用来查询天气的所以用这三个 return command in (weather,w,天气) def handle(self, city:str) -str: #判定用户是否输入了正确的城市名从而判定输出的数据 #如果用户没有输入 城市名 则return 请告诉我城市比如weather 北京 #如果用户没有输入 正确的城市名字典中的城市 则输出 xx天气未知天气模拟数据有限 #如果输入了正确的城市名则进行天气的输出由于这里没有接入天气API目前只用固定的字典进行应答 if not city: return 请告诉我城市比如weather 北京 #模拟天气数据后面学asyncio时改成调用真实API mock_data { 北京: 晴 25°C, 上海: 小雨 22°C, 广州: 多云 28°C } weather mock_data.get(city, 未知天气模拟数据有限) #注意这里的.get()不是get函数只是恰巧名字一样。这里的 字典.get() 是字典的方法查字典用的 #字典.getkey, default key:必须填 default:非必填 # .get() 就是拿着现成的 keycity去字典里查查到给真的查不到给默认值然后存进 weather 最后 return 拼起来输出 return f{city}天气{weather} class ClacHandler(TaskHandler): 计算器 def can_handle(self, command: str) -bool: return command in (calc,c,计算) def handle(self, expression:str) -str: if not expression: return 请输入算式比如 clac 12*3 try: # 安全计算只允许数字和运算符 allowed set(0123456789-*/.())#创建一个集合 if all(c in allowed for c in expression): #for c in expression把算式里的每个字符都拎出来叫c #c in allowed检查这个字符c在不在白名单里 #all(...)所有字符都必须通过检查有一个不通过就是False result eval(expression) #eval() 是 Python 的字符串执行器把字符串当数学公式算12*3 # 传进去 → 返回 7 # 然后 f-string 拼成 结果7 返回 return f结果{result} return算式包含非法字符 # try 里的代码如果报错比如除以0、括号不匹配不会崩溃而是跳到这里 # e是错误信息比如division by zero # 返回计算错误division by zero 程序继续运行 #except 是关键字捕获 Exception 是所有错误的基类 as e 是把错误信息抓出来存到变量 e 里 except Exception as e: return f计算错误{e} class NoteHandler(TaskHandler): 记事本持久化到本地 #这边定义了一个文件叫notes.json FILE notes.json def can_handle(self, command: str) -bool: return command in (note, n, 记事) def handle(self, content:str) -str: if not content: return 请输入内容比如note 记得交作业 #读取已有记录 try: #打开文件用完自动关不用手动close #r 读模式 #encodingutf-8 指定编码防止中文乱码 #这里是用open打开file然后将打开的file命名为f用with来保证自动关闭 with open(self.FILE,r,encodingutf-8)as f: #json.load(f) 把JSON文件内容变成Python列表/字典 notes json.load(f) #捕获错误 except (FileNotFoundError,json.JSONDecodeError): #出错时就当没有旧纪录从空列表开始 notes [] #添加新纪录 note { time:datetime.now().strftime(%Y-%m-%d %H:%M:%S), content:content } #追加到列表 notes.append(note) #保存 with open(self.FILE,w,encodingutf-8)as f: # json.dump(notes, f) 把Python列表变成JSON字符串写入文件 # ensure_asciiFalse 中文不转义直接写中文 # index 2 格式化缩进让所有JSON文件有换行和空格方便人看 json.dump(notes,f,ensure_asciiFalse,indent2) return f已记录{content} 上面那个版本是用了with方法下面这个版本不用with def handler(self, content: str) - str: if not content: return 请输入内容比如note 记得交作业 #读文件 notes [] try: f open(self.FILE, r, encoding utf-8) //手动打开 notes json.load(f) //读取 f.close() //必须手动关闭 except (FileNotFoundError,json.JSONDecodeError): notes [] ////注意如果上面的json.load(f)报错了f.close()根本执行不到文件就会被一直占着 #造数据 note { time: datetime.now().strftime(%Y-%m-%d %H:%M:%S), content: content } notes.append(note) # 写文件 f open(self.FILE, w, encodingutf-8) # 1. 手动打开 json.dump(notes, f, ensure_asciiFalse, indent2) f.close() # 2. 必须手动关闭 return f已记录{content} 不用with的坏处就是 1. 每次都要记着手动 f.close() 忘了就内存泄漏 2. 如果 json.load(f) 那行报错了程序直接跳到 except f.close() 永远执行不到 3. 就像你开了串口、读了数据、中途出异常串口没关后面再开就报错 class HelpHandler(TaskHandler): 帮助 def can_handle(self, command: str) -bool: return command in (help,h,帮助) def handle(self, args:str) -str: return 可用命令 weather 城市 查天气示例weather 北京 calc 算式 计算示例calc 12*3 note 内容 记事示例note 记得喝水 help 显示帮助 exit 退出3、接下来是Agent开发的核心出装#核心出装Agent路由器 class Agent: 任务路由器——————Agent核心机制 def __init__(self): #注册所有处理器后面学框架时这就是Tool Registry self.handlers [ WeatherHandler(), ClacHandler(), NoteHandler(), HelpHandler(), ] #路由分发 def route(self, user_input:str)-str: 解析用户输入路由到对应处理器 parts user_input.strip().split(maxsplit1) #strip() 去掉用户输入字符串前后的空格和换行 #split(maxsplit 1) 按照空格切割最多切一次分成两段 if not parts: return 请输入命令 command parts[0].lower() #把切割出来的第一部分赋值给command并转成小写 args parts[1] if len(parts) 1 else #把第二部分赋值给args 计算parts长度大于1则args为该parts否则为空 #多态遍历所有处理器找到能处理的 for i in self.handlers: if i.can_handle(command): return i.handle(args) return f未知命令{command}输入 help 查看可用命令4、主函数#主程序 def main(): agent Agent() print(简易任务路由器) print(输入 help 查看命令 exit 退出) while True: try: user_input input(\n ).strip() if user_input.lower() in (exit,quit,退出): print(再见) break result agent.route(user_input) print(result) except KeyboardInterrupt: print(\n再见) break except Exception as e: print(f出错了{e}) if __name__ __main__: main()六、项目亮点和感悟1. 即插即用的扩展性新增功能只需要写一个类继承TaskHandler实现can_handlehandle然后在 ​​​​​​Agent.__init__里注册。主程序完全不需要改动。这就是开闭原则对扩展开放对修改关闭。2. 异常安全设计try-except捕获文件读写异常防止程序崩溃with open自动关闭文件防止资源泄漏.get()安全查字典防止 KeyError3. 安全第一CalcHandler里的eval()是危险函数必须先做白名单字符过滤。任何直接执行用户输入的代码都必须先校验。4. 面向对象不是炫技很多人学 OOP 只会写学生管理系统但在这个项目里继承 多态 抽象方法真正解决了如何统一调度不同功能模块的问题。七、结语目前这是同步版Agent所有操作都是阻塞的。下一步引入 asyncio把handle改成async def让多个工具并发执行接入真实 API把mock_data换成异步 HTTP 请求天气、翻译等引入大模型让Agent能理解自然语言而不是死板的命令词引入向量数据库给 Agent 加长期记忆从 JSON 文件升级到 Chroma/MilvusAgent 开发的核心不是调 API而是架构设计能力如何把理解意图 → 选择工具 → 执行动作这套流程抽象成可扩展的代码结构。当你亲手写完这个任务路由器再看 LangChain 的Tool和AgentExecutor会发现它们做的本质上也是这些事——只是更完善、更工程化。先造轮子再用框架。理解原理后工具只是加分项。