[{"data":1,"prerenderedAt":6207},["ShallowReactive",2],{"search-docs":3,"doc-\u002Fai\u002Fagent\u002Fpractice":886},[4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,159,162,165,169,172,175,178,182,186,190,194,198,202,206,210,214,218,222,226,230,234,238,242,246,250,254,258,262,266,269,273,277,281,285,288,291,294,298,301,304,307,310,313,316,319,322,325,329,332,336,340,344,348,352,356,359,362,365,368,371,374,377,380,383,386,389,393,396,399,402,405,408,411,414,417,420,424,428,432,435,438,442,446,450,454,458,462,466,470,474,477,480,483,487,491,494,497,500,504,507,511,515,518,521,524,527,530,533,536,539,542,545,548,551,554,557,560,563,566,569,572,575,579,583,587,591,595,599,603,606,610,614,617,620,623,626,629,633,637,640,643,646,649,652,655,658,661,664,667,670,673,676,679,682,685,688,691,694,697,700,703,706,709,712,716,720,724,728,732,736,740,744,748,752,756,760,764,768,772,775,779,783,787,790,793,796,799,802,805,808,811,814,818,822,825,829,832,835,838,841,844,848,851,854,858,862,865,869,873,876,879,882],{"path":5,"title":6,"description":7},"\u002Fabout\u002Fauthor","作者相关","只想纯粹的做一个程序员...",{"path":9,"title":10,"description":11},"\u002Fabout\u002Fjourney","心路历程","",{"path":13,"title":14,"description":15},"\u002Fai\u002Fagent\u002Fframeworks","Agent 框架","主流 Agent 框架：LangChain、LlamaIndex、AutoGen、CrewAI",{"path":17,"title":18,"description":19},"\u002Fai\u002Fagent\u002Fhooks","Agent Hooks 与自动化","Claude Agent 的 Hooks 生命周期、事件类型、典型自动化场景",{"path":21,"title":22,"description":23},"\u002Fai\u002Fagent\u002Fintroduction","AI Agent 概述","AI Agent 核心概念：感知、规划、执行、记忆",{"path":25,"title":26,"description":27},"\u002Fai\u002Fagent\u002Fpractice","Agent 实战","AI Agent 实战：构建自主任务执行系统",{"path":29,"title":30,"description":31},"\u002Fai\u002Fagent\u002Fsdk","Claude Agent SDK 开发","使用 Claude Agent SDK 构建自定义 AI Agent：架构、API、生命周期",{"path":33,"title":34,"description":35},"\u002Fai\u002Fagent\u002Fsubagents","Subagents 子代理","用 Subagents 分解复杂任务、并发执行、隔离上下文",{"path":37,"title":38,"description":39},"\u002Fai\u002Fagent\u002Ftool-use","工具调用","AI Agent 工具调用：Function Calling、Tool Use 原理与实践",{"path":41,"title":42,"description":43},"\u002Fai\u002Ffundamentals\u002Fdeep-learning","深度学习入门","深度学习基础知识：前向传播、反向传播、损失函数、优化器",{"path":45,"title":46,"description":47},"\u002Fai\u002Ffundamentals\u002Fml-basics","机器学习基础","机器学习核心概念：监督学习、无监督学习、强化学习",{"path":49,"title":50,"description":51},"\u002Fai\u002Ffundamentals\u002Fneural-networks","神经网络原理","神经网络架构：CNN、RNN、注意力机制",{"path":53,"title":54,"description":55},"\u002Fai\u002Fgetting-started","AI 学习路线","AI 技术学习路线图，从基础到实战的完整指南",{"path":57,"title":58,"description":59},"\u002Fai\u002Fllm\u002Ffine-tuning","模型微调","大模型微调技术：LoRA、QLoRA、全量微调、RLHF",{"path":61,"title":62,"description":63},"\u002Fai\u002Fllm\u002Fintroduction","大模型概述","大语言模型发展历程、核心能力与主流模型对比",{"path":65,"title":66,"description":67},"\u002Fai\u002Fllm\u002Flocal-deploy","本地部署","大模型本地部署：Ollama、vLLM、llama.cpp",{"path":69,"title":70,"description":71},"\u002Fai\u002Fllm\u002Ftransformer","Transformer 架构","Transformer 架构详解：自注意力机制、位置编码、多头注意力",{"path":73,"title":74,"description":75},"\u002Fai\u002Fmcp\u002Fclient","MCP Client 开发","MCP Client 开发指南：连接、调用、集成",{"path":77,"title":78,"description":79},"\u002Fai\u002Fmcp\u002Fdebugging","MCP 调试与排错","MCP Server 开发与集成过程中的常见问题、日志分析、诊断工具",{"path":81,"title":82,"description":83},"\u002Fai\u002Fmcp\u002Fintroduction","MCP 概述","Model Context Protocol 协议概述：架构、核心概念、应用场景",{"path":85,"title":86,"description":87},"\u002Fai\u002Fmcp\u002Fserver","MCP Server 开发","MCP Server 开发指南：资源、工具、提示词的实现",{"path":89,"title":90,"description":91},"\u002Fai\u002Fmcp\u002Ftools","MCP Tools 深入","深入理解 MCP Tools：与 Resources\u002FPrompts 的差异、Schema 设计、Annotations 与权限控制",{"path":93,"title":94,"description":95},"\u002Fai\u002Fprompt\u002Fadvanced","高级 Prompt 模式","高级 Prompt 设计模式：Tree-of-Thought、自我反思、多轮对话策略",{"path":97,"title":98,"description":99},"\u002Fai\u002Fprompt\u002Fbasics","Prompt 基础","Prompt Engineering 入门：基本概念、角色设定、输出格式控制",{"path":101,"title":102,"description":103},"\u002Fai\u002Fprompt\u002Ftechniques","提示词技巧","常用提示词技巧：Few-shot、Chain-of-Thought、ReAct",{"path":105,"title":106,"description":107},"\u002Fai\u002Frag\u002Fembedding","文本嵌入","文本嵌入模型：Embedding 原理、模型选择、相似度计算",{"path":109,"title":110,"description":111},"\u002Fai\u002Frag\u002Fintroduction","RAG 概述","检索增强生成（RAG）架构原理、优势与应用场景",{"path":113,"title":114,"description":115},"\u002Fai\u002Frag\u002Fpractice","RAG 实战","RAG 应用实战：文档问答系统、知识库搭建",{"path":117,"title":118,"description":119},"\u002Fai\u002Frag\u002Fvector-database","向量数据库","主流向量数据库对比：Milvus、Pinecone、Chroma、Weaviate",{"path":121,"title":122,"description":123},"\u002Fai\u002Fskills\u002Fbest-practices","Skill 最佳实践","编写高质量 Skill 的设计原则、常见陷阱与优化技巧",{"path":125,"title":126,"description":127},"\u002Fai\u002Fskills\u002Fcreating","创建自定义 Skill","从零编写一个可被 Agent 自动发现和调用的 Skill",{"path":129,"title":130,"description":131},"\u002Fai\u002Fskills\u002Fintroduction","Agent Skills 概述","Claude Agent Skills 概念、工作原理、与 Tools\u002FMCP 的区别",{"path":133,"title":134,"description":135},"\u002Fgolang\u002Fadvanced\u002Fconcurrency","Go - 并发深入","深入理解 Go 并发编程的核心机制。",{"path":137,"title":138,"description":139},"\u002Fgolang\u002Fadvanced\u002Fgc","Go - 垃圾回收","理解 Go 的垃圾回收机制，掌握 GC 调优方法。",{"path":141,"title":142,"description":143},"\u002Fgolang\u002Fadvanced\u002Fgmp","Go - GMP 调度模型","GMP 是 Go 运行时调度器的核心模型，理解它对于编写高性能 Go 程序至关重要。",{"path":145,"title":146,"description":147},"\u002Fgolang\u002Fadvanced\u002Fgo-concurrency","Go - 并发编程","Go 的并发是其核心特性之一，通过 Goroutine 和 Channel 实现。",{"path":149,"title":150,"description":151},"\u002Fgolang\u002Fadvanced\u002Fmemory","Go - 内存模型","理解 Go 的内存分配机制和内存模型。",{"path":153,"title":154,"description":155},"\u002Fgolang\u002Fadvanced\u002Fprofiling","Go - 性能分析","掌握 Go 的性能分析工具：pprof、trace、benchmark。",{"path":157,"title":158,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-basic","Go - 基础语法",{"path":160,"title":161,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-composite","Go - 复合类型",{"path":163,"title":164,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-control","Go - 流程控制",{"path":166,"title":167,"description":168},"\u002Fgolang\u002Fcore\u002Fgo-error","Go - 错误处理","Go 使用显式的错误返回值来处理错误，而不是异常机制。",{"path":170,"title":171,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-function","Go - 函数",{"path":173,"title":174,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-install","Go - 环境搭建",{"path":176,"title":177,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-interface","Go - 接口",{"path":179,"title":180,"description":181},"\u002Fgolang\u002Fcore\u002Fgo-module","Go - 包管理","Go Modules 是 Go 1.11 引入的官方依赖管理方案，Go 1.16 后成为默认模式。",{"path":183,"title":184,"description":185},"\u002Fgolang\u002Fdistributed\u002Fgrpc","Go - gRPC","gRPC 是 Google 开发的高性能 RPC 框架，使用 Protocol Buffers 作为序列化协议。",{"path":187,"title":188,"description":189},"\u002Fgolang\u002Fdistributed\u002Fmicroservice","Go - 微服务","微服务架构的核心组件：服务发现、负载均衡、熔断降级。",{"path":191,"title":192,"description":193},"\u002Fgolang\u002Fdistributed\u002Fmq","Go - 消息队列","使用 Go 操作 Kafka 和 RabbitMQ。",{"path":195,"title":196,"description":197},"\u002Fgolang\u002Fdistributed\u002Fredis","Go - Redis","使用 go-redis 操作 Redis，实现缓存、分布式锁等功能。",{"path":199,"title":200,"description":201},"\u002Fgolang\u002Fengineering\u002Fconfig","Go - 配置管理","使用 viper 进行配置管理，支持多种配置格式和配置中心。",{"path":203,"title":204,"description":205},"\u002Fgolang\u002Fengineering\u002Fdocker","Go - Docker 部署","使用 Docker 容器化部署 Go 应用。",{"path":207,"title":208,"description":209},"\u002Fgolang\u002Fengineering\u002Fkubernetes","Go - Kubernetes 部署","在 Kubernetes 上部署和管理 Go 应用。",{"path":211,"title":212,"description":213},"\u002Fgolang\u002Fengineering\u002Flogging","Go - 日志系统","使用 zap 和 logrus 构建高性能结构化日志系统。",{"path":215,"title":216,"description":217},"\u002Fgolang\u002Fengineering\u002Ftesting","Go - 单元测试","Go 内置了强大的测试框架，掌握测试是编写高质量代码的基础。",{"path":219,"title":220,"description":221},"\u002Fgolang\u002Fstdlib\u002Fbufio","bufio","在 Go 语言中，bufio 包提供了带缓冲的 I\u002FO 操作，能够提高读写性能。以下是一些常用的 bufio 包 API 及其详细说明：",{"path":223,"title":224,"description":225},"\u002Fgolang\u002Fstdlib\u002Fcontainer","container","在Go语言标准库中，container 包提供了几种常用的数据结构实现，这些数据结构对于高效地管理和操作数据非常有用。以下是 container 包中主要的数据结构：",{"path":227,"title":228,"description":229},"\u002Fgolang\u002Fstdlib\u002Fcrypto","crypto","在 Go 语言中，crypto 包提供了一组用于加密和解密的功能。以下是一些常用的 crypto 包及其子包的 API 及其详细说明：",{"path":231,"title":232,"description":233},"\u002Fgolang\u002Fstdlib\u002Fencoding-csv","encoding\u002Fcsv","在 Go 语言中，encoding\u002Fcsv 包提供了对 CSV（逗号分隔值）文件进行读写的功能。以下是一些常用的 encoding\u002Fcsv 包的 API 及其详细说明：",{"path":235,"title":236,"description":237},"\u002Fgolang\u002Fstdlib\u002Fencoding-json","encoding\u002Fjson","在 Go 语言中，encoding\u002Fjson 包提供了对 JSON 数据进行编码和解码的功能。以下是一些常用的 encoding\u002Fjson 包的 API 及其详细说明：",{"path":239,"title":240,"description":241},"\u002Fgolang\u002Fstdlib\u002Fencoding-xml","encoding\u002Fxml","在 Go 语言中，encoding\u002Fxml 包提供了对 XML 数据进行编码和解码的功能。以下是一些常用的 encoding\u002Fxml 包的 API 及其详细说明：",{"path":243,"title":244,"description":245},"\u002Fgolang\u002Fstdlib\u002Fflag","flag","在Go语言中，flag 包是用于处理命令行参数的标准库，它提供了一种简单而直接的方式来解析和使用命令行参数。下面是关于 flag 包的一些基本介绍和常用功能：",{"path":247,"title":248,"description":249},"\u002Fgolang\u002Fstdlib\u002Ffmt","fmt","在 Go 语言的标准库中，fmt 包是非常重要的，它提供了处理格式化输入和输出的基本工具。以下是一些 fmt 包内常用的API：",{"path":251,"title":252,"description":253},"\u002Fgolang\u002Fstdlib\u002Fhttp","net\u002Fhttp","在 Go 语言中，net\u002Fhttp 包提供了用于构建 HTTP 客户端和服务器的强大工具。以下是一些常用的 net\u002Fhttp 包的 API 及其详细说明：",{"path":255,"title":256,"description":257},"\u002Fgolang\u002Fstdlib\u002Fio","io","在 Go 语言中，io 包提供了基本的输入输出功能。以下是一些常用的 io 包的 API 及其详细说明：",{"path":259,"title":260,"description":261},"\u002Fgolang\u002Fstdlib\u002Flog","log","在 Go 语言中，log 包提供了简单的日志记录功能。以下是一些常用的 log 包的 API 及其详细说明：",{"path":263,"title":264,"description":265},"\u002Fgolang\u002Fstdlib\u002Fmath","math","在 Go 语言中，math 包提供了基本的数学函数和常量。以下是一些常用的 math 包的 API 及其详细说明：",{"path":267,"title":268,"description":11},"\u002Fgolang\u002Fstdlib\u002Fnet","net",{"path":270,"title":271,"description":272},"\u002Fgolang\u002Fstdlib\u002Fos","os","在Go语言中，os 包是一个非常重要且常用的标准库，它提供了与操作系统交互的功能，包括文件操作、环境变量管理、进程管理等。下面是一些 os 包中常用的功能和API：",{"path":274,"title":275,"description":276},"\u002Fgolang\u002Fstdlib\u002Fsort","order","在 Go 语言中，sort 包提供了对切片和用户定义的集合进行排序的函数。它实现了常见的排序算法，如快速排序（Quicksort）和堆排序（Heapsort），并且为自定义集合提供了接口，使得用户可以根据特定的需求进行排序。",{"path":278,"title":279,"description":280},"\u002Fgolang\u002Fstdlib\u002Fstrconv","strconv","在 Go 语言中，strconv 包提供了字符串和基本数据类型之间的转换函数，例如将整数转换为字符串、字符串转换为整数，以及其他类型之间的转换。这些功能非常有用，特别是在处理用户输入或从外部数据源读取数据时。",{"path":282,"title":283,"description":284},"\u002Fgolang\u002Fstdlib\u002Ftime","time","在 Go 语言中，time 包提供了处理时间和日期的功能。以下是一些常用的 time 包的 API 及其详细说明：",{"path":286,"title":287,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Ferror","Gin - 错误处理",{"path":289,"title":290,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Ffile","Gin - 文件处理",{"path":292,"title":293,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Fmiddleware","Gin - 中间件",{"path":295,"title":296,"description":297},"\u002Fgolang\u002Fweb\u002Fgin\u002Fquickstart","Gin - 快速开始","Gin 是目前最流行的 Go Web 框架，以高性能和简洁 API 著称。",{"path":299,"title":300,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Frequest","Gin - 请求处理",{"path":302,"title":303,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Fresponse","Gin - 响应处理",{"path":305,"title":306,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Frouter","Gin - 路由",{"path":308,"title":309,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Fvalidation","Gin - 参数校验",{"path":311,"title":312,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fassociation","GORM - 关联关系",{"path":314,"title":315,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fcrud","GORM - CRUD 操作",{"path":317,"title":318,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fmodel","GORM - 模型定义",{"path":320,"title":321,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fperformance","GORM - 日志与性能",{"path":323,"title":324,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fquery","GORM - 高级查询",{"path":326,"title":327,"description":328},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fquickstart","GORM - 快速开始","GORM 是 Go 语言最流行的 ORM 库，功能强大，使用简单。",{"path":330,"title":331,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Ftransaction","GORM - 事务与 Hook",{"path":333,"title":334,"description":335},"\u002Finterview\u002Fbasic","计算机基础面经","本章节汇总了面试中常见的通用技术概念，不局限于特定语言或数据库，是考察技术内功的关键考点。",{"path":337,"title":338,"description":339},"\u002Finterview\u002Fgolang","Golang 面试题","Go 语言面试高频考点，覆盖基础语法、数据结构、并发编程、内存管理、GC、调度器等核心知识。",{"path":341,"title":342,"description":343},"\u002Finterview\u002Fk8s","Kubernetes 面试题","Kubernetes（K8s）面试高频考点，覆盖架构原理、核心资源、网络存储、调度策略、运维监控等核心知识。",{"path":345,"title":346,"description":347},"\u002Finterview\u002Fmysql","MySQL 面试题","MySQL 数据库面试高频考点，覆盖索引、事务、锁、优化、主从复制等核心知识。",{"path":349,"title":350,"description":351},"\u002Finterview\u002Fredis","Redis 面试题","Redis 面试高频考点，覆盖数据结构、持久化、集群、缓存一致性、性能优化等核心知识。",{"path":353,"title":354,"description":355},"\u002Finterview\u002Frocketmq","RocketMQ 面试题","RocketMQ 面试高频考点，覆盖消息模型、可靠性、顺序消息、事务消息、存储与高可用等核心知识。",{"path":357,"title":358,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-arraylist","List - ArrayList 源码解析",{"path":360,"title":361,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-linkedlist","List - LinkedList 源码解析",{"path":363,"title":364,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-stack","List - Satck源码解析",{"path":366,"title":367,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-vectore","List - Vector 源码解析",{"path":369,"title":370,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fmap-hashmap","Map - HashMap 源码解析",{"path":372,"title":373,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fmap-linkedhashmap","Map - LinkedHashMap 源码解析",{"path":375,"title":376,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fmap-treemap","Map - TreeMap 源码解析",{"path":378,"title":379,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fqueue-deque","Queue - Deque 接口解析",{"path":381,"title":382,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fqueue-queue","Queue - Queue 接口解析",{"path":384,"title":385,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fset-hashset","Set - HashSet源码解析",{"path":387,"title":388,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fset-linkedhashset","Set - LinkedHashSet 源码解析",{"path":390,"title":391,"description":392},"\u002Fother\u002Fjava\u002Fcollection\u002Fset-treeset","Set - TreeSet源码解析","TreeSet 是一个 Set 集合接口的实现类，与 HashSet 类似，其底层也是通过维护了一个 TreeMap 对象来封装了一些实现方法，故本篇不再对 TreeSet 的底层原理进行详细说明，仅对常用 API 做简单介绍，如需了解 TreeMap 的底层实现原理，请移步 Map - HashMap 源码解析",{"path":394,"title":395,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fannotation","Java核心 - 注解",{"path":397,"title":398,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fbasic-grammar","Java核心 - 基础语法",{"path":400,"title":401,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fclass-and-object","Java核心 - 面向对象",{"path":403,"title":404,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fcommon-classes","Java核心 - 常用类",{"path":406,"title":407,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fexception","Java核心 - 异常处理",{"path":409,"title":410,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fgenerics","Java核心 - 泛型",{"path":412,"title":413,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fjdk-env-path","Java核心 - 环境搭建",{"path":415,"title":416,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Freflection","Java核心 - 反射",{"path":418,"title":419,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fstring","Java核心 - String 字符串",{"path":421,"title":422,"description":423},"\u002Fother\u002Fjava\u002Fio\u002Fbuffer-stream","Java IO - 缓冲流","缓冲流是对基本流的包装，通过内置缓冲区减少系统调用次数，大幅提升读写效率。",{"path":425,"title":426,"description":427},"\u002Fother\u002Fjava\u002Fio\u002Fbyte-stream","Java IO - 字节流","字节流是 Java IO 中最基本的流类型，以字节（byte）为单位进行数据读写，可以处理任意类型的文件。",{"path":429,"title":430,"description":431},"\u002Fother\u002Fjava\u002Fio\u002Fchar-stream","Java IO - 字符流","字符流以字符为单位进行读写，专门用于处理文本文件。相比字节流，字符流能够正确处理字符编码，避免中文乱码问题。",{"path":433,"title":434,"description":11},"\u002Fother\u002Fjava\u002Fio\u002Ffile","Java IO - File 类",{"path":436,"title":437,"description":11},"\u002Fother\u002Fjava\u002Fio\u002Fio-stream-system","Java IO - IO流概述",{"path":439,"title":440,"description":441},"\u002Fother\u002Fjava\u002Fio\u002Fnio","Java IO - NIO","NIO（New IO）是 JDK 1.4 引入的新 IO 模型，提供了更高效的 IO 操作方式，支持非阻塞 IO 和多路复用。",{"path":443,"title":444,"description":445},"\u002Fother\u002Fjava\u002Fjvm\u002Fclass-loading","类加载机制","类加载机制是 JVM 将 .class 文件加载到内存，并对数据进行校验、转换解析和初始化，最终形成可被 JVM 直接使用的 Java 类型的过程。",{"path":447,"title":448,"description":449},"\u002Fother\u002Fjava\u002Fjvm\u002Fgarbage-collection","垃圾回收","垃圾回收（Garbage Collection，GC）是 JVM 自动管理内存的机制，负责回收不再使用的对象所占用的内存。",{"path":451,"title":452,"description":453},"\u002Fother\u002Fjava\u002Fjvm\u002Fjvm-memory","JVM 内存结构","JVM 在执行 Java 程序时，会把它管理的内存划分为若干个不同的数据区域。这些区域有各自的用途、创建和销毁时间。",{"path":455,"title":456,"description":457},"\u002Fother\u002Fjava\u002Fjvm\u002Fjvm-tuning","JVM 调优","JVM 调优是优化 Java 应用性能的重要手段，主要包括参数配置、性能监控和问题排查。",{"path":459,"title":460,"description":461},"\u002Fother\u002Fjava\u002Fthread\u002Fatomic","原子类","Java 原子类（Atomic Classes）提供了一种无锁的线程安全方式，基于 CAS（Compare-And-Swap）操作实现。",{"path":463,"title":464,"description":465},"\u002Fother\u002Fjava\u002Fthread\u002Fcompletable-future","CompletableFuture","CompletableFuture 是 JDK 8 引入的异步编程工具，实现了 Future 和 CompletionStage 接口，支持函数式编程和链式调用。",{"path":467,"title":468,"description":469},"\u002Fother\u002Fjava\u002Fthread\u002Fconcurrent-collections","并发集合","Java 并发包提供了多种线程安全的集合类，用于替代传统的同步集合（如 Collections.synchronizedList）。",{"path":471,"title":472,"description":473},"\u002Fother\u002Fjava\u002Fthread\u002Fconcurrent-utils","并发工具类","Java 并发包提供了多种实用的并发工具类，用于控制线程之间的协调与同步。",{"path":475,"title":476,"description":11},"\u002Fother\u002Fjava\u002Fthread\u002Fsynchronized-lock","同步机制",{"path":478,"title":479,"description":11},"\u002Fother\u002Fjava\u002Fthread\u002Fthread-basic","线程基础",{"path":481,"title":482,"description":11},"\u002Fother\u002Fjava\u002Fthread\u002Fthread-pool","线程池",{"path":484,"title":485,"description":486},"\u002Fother\u002Fspring-series\u002Fspring\u002Fannotations-beans","Spring - 基于注解管理Bean","从 Java 5 开始，Java 增加了对注解（Annotation）的支持，它是代码中的一种特殊标记，可以在编译、类加载和运行时被读取，执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下，在源代码中嵌入补充信息。",{"path":488,"title":489,"description":490},"\u002Fother\u002Fspring-series\u002Fspring\u002Fimplement-ioc","Spring - 原理手写IoC","Spring 框架的 IOC 是基于 Java 反射机制实现的，在学习手写 IoC 之前，你需要具备一定的 Java 反射相关的知识，参考本站内的 Java 教程。",{"path":492,"title":493,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fintroduction-case","Spring - 入门案例",{"path":495,"title":496,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-aop","Spring - 面向切面AOP",{"path":498,"title":499,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-aot","Spring - AOT提前编译",{"path":501,"title":502,"description":503},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-data-validation","Spring - 数据校验","在开发中，我们经常遇到参数校验的需求，比如用户注册的时候，要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式，我们会把校验的代码和真正的业务处理逻辑耦合在一起，而且如果未来要新增一种校验逻辑也需要在修改多个地方。而spring validation允许通过注解的方式来定义对象校验规则，把校验和业务逻辑分离开，让代码编写更加方便。Spring Validation其实就是对Hibernate Validator进一步的封装，方便在Spring中使用。",{"path":505,"title":506,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-i18n","Spring - 国际化i18n",{"path":508,"title":509,"description":510},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-ioc","Spring - IOC容器","IoC 是 Inversion of Control 的简写，译为“控制反转”，它不是一门技术，而是一种设计思想，是一个重要的面向对象编程法则，能够指导我们如何设计出松耦合、更优良的程序。",{"path":512,"title":513,"description":514},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-junit","Spring - 单元测试JUnit","在之前的测试方法中，几乎都能看到以下的两行代码：",{"path":516,"title":517,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-resources","Spring - 资源操作",{"path":519,"title":520,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-summarize","Spring - Spring概述",{"path":522,"title":523,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-transaction","Spring - 事务",{"path":525,"title":526,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fxml-beans","Spring - 基于XML管理Bean",{"path":528,"title":529,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-config","SpringBoot - 配置详解",{"path":531,"title":532,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-data","SpringBoot - 数据访问",{"path":534,"title":535,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-quickstart","SpringBoot - 快速入门",{"path":537,"title":538,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-web","SpringBoot - Web 开发",{"path":540,"title":541,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-config","SpringCloud - 配置中心",{"path":543,"title":544,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-discovery","SpringCloud - 服务注册与发现",{"path":546,"title":547,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-feign","SpringCloud - 服务调用",{"path":549,"title":550,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-gateway","SpringCloud - 服务网关",{"path":552,"title":553,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-introduction","SpringCloud - 微服务概述",{"path":555,"title":556,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-sentinel","SpringCloud - 服务保护",{"path":558,"title":559,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-databind","SpringMVC - 数据绑定与转换",{"path":561,"title":562,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-exception","SpringMVC - 异常处理",{"path":564,"title":565,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-interceptor","SpringMVC - 拦截器",{"path":567,"title":568,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-introduction","SpringMVC - 简介与环境搭建",{"path":570,"title":571,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-request","SpringMVC - 请求处理",{"path":573,"title":574,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-response","SpringMVC - 响应处理",{"path":576,"title":577,"description":578},"\u002Fproject\u002Frocket-leaf\u002Farchitecture","项目架构","Rocket-Leaf 的目录结构、模块划分、数据流向，以及各层之间的依赖关系。",{"path":580,"title":581,"description":582},"\u002Fproject\u002Frocket-leaf\u002Fbackend-layers","后端分层设计","Rocket-Leaf 的 model \u002F rocketmq \u002F service 三层结构，以及服务之间的依赖关系与设计取舍。",{"path":584,"title":585,"description":586},"\u002Fproject\u002Frocket-leaf\u002Fclient-manager","RocketMQ 客户端管理器","AdminClientManager 的多客户端池、默认连接懒加载、自动重连重试的设计与实现。",{"path":588,"title":589,"description":590},"\u002Fproject\u002Frocket-leaf\u002Fencryption","连接信息加密存储","AES-256-GCM + SHA-256 字段级派生密钥的实现，以及如何在不破坏兼容性的前提下为历史明文数据做透明迁移。",{"path":592,"title":593,"description":594},"\u002Fproject\u002Frocket-leaf\u002Ffrontend","前端结构与类型绑定","React + Vite 目录组织、自动生成的 Wails 绑定、api 薄封装与自定义 hooks 的职责划分。",{"path":596,"title":597,"description":598},"\u002Fproject\u002Frocket-leaf","项目简介","Rocket-Leaf 是一款基于 Wails v3 构建的跨平台 RocketMQ 桌面管理客户端，Go 后端 + React 前端。本文档系列拆解它的架构与关键实现。",{"path":600,"title":601,"description":602},"\u002Fproject\u002Frocket-leaf\u002Fwails-v3","Wails v3 入门","Wails v3 的核心概念、Service 绑定机制，以及 Rocket-Leaf 是如何用它把 Go 后端和 React 前端打通的。",{"path":604,"title":605,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-basic","Docker - 入门基础",{"path":607,"title":608,"description":609},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-compose","Docker - Compose","在部署应用时，常常使用到不止一个容器，那么在部署容器的时候就需要一个一个进行部署，这样的部署过程也相对来说比较繁琐复杂，也容易出问题，那么有没有一种更为简单的方法呢？",{"path":611,"title":612,"description":613},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-container-connection","Docker - 容器互联","在上一个章节中我们学习了 Docker 容器的端口映射，可以将 Docker 容器和本地以及网络中的端口进行连接起来。",{"path":615,"title":616,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-dockerfile","Docker - Dockerfile",{"path":618,"title":619,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-helloworld","Docker - HelloWorld",{"path":621,"title":622,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-install","Docker - 安装",{"path":624,"title":625,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-introduce","Docker - 简介",{"path":627,"title":628,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-object","Docker - 镜像、容器、仓库",{"path":630,"title":631,"description":632},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-warehouse","Docker - 仓库管理","仓库是集中存放资源的地方，代码仓库是存放代码的，那么Docker 中的仓库就是存放 Docker 镜像的。",{"path":634,"title":635,"description":636},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-web-containers","Docker - WEB应用实例","在之前的章节中，仅对普通容器进行了演示，但在实际中常常使用到 Docker 容器中的 WEB 应用程序。",{"path":638,"title":639,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-config","Kubernetes - ConfigMap 与 Secret",{"path":641,"title":642,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-helm","Kubernetes - Helm 包管理",{"path":644,"title":645,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-install","Kubernetes - 集群安装",{"path":647,"title":648,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-introduction","Kubernetes - 简介与架构",{"path":650,"title":651,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-kubectl","Kubernetes - kubectl 命令行工具",{"path":653,"title":654,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-monitoring","Kubernetes - 监控与日志",{"path":656,"title":657,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-network-security","Kubernetes - 网络与安全",{"path":659,"title":660,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-service","Kubernetes - Service 与 Ingress",{"path":662,"title":663,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-storage","Kubernetes - 持久化存储",{"path":665,"title":666,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-workload","Kubernetes - 工作负载资源",{"path":668,"title":669,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-bash","Linux - Bash 基础语法",{"path":671,"title":672,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-file-directory","Linux - 文件与目录操作",{"path":674,"title":675,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-network","Linux - 网络配置",{"path":677,"title":678,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-package","Linux - 软件包管理",{"path":680,"title":681,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-process","Linux - 进程管理",{"path":683,"title":684,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-scripts","Linux - 常用脚本示例",{"path":686,"title":687,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-service","Linux - 服务管理",{"path":689,"title":690,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-user-permission","Linux - 用户与权限管理",{"path":692,"title":693,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-https","Nginx - HTTPS 配置",{"path":695,"title":696,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-install","Nginx - 安装与配置",{"path":698,"title":699,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-loadbalance","Nginx - 负载均衡",{"path":701,"title":702,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-optimization","Nginx - 性能优化",{"path":704,"title":705,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-proxy","Nginx - 反向代理",{"path":707,"title":708,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-static","Nginx - 静态资源服务",{"path":710,"title":711,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-vhost","Nginx - 虚拟主机配置",{"path":713,"title":714,"description":715},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-architecture","MySQL 高可用架构","主从复制、读写分离、分库分表。",{"path":717,"title":718,"description":719},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-index","MySQL 索引","索引是帮助 MySQL 高效获取数据的有序数据结构。",{"path":721,"title":722,"description":723},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-lock","MySQL 锁","锁用于解决并发访问时的数据一致性问题。",{"path":725,"title":726,"description":727},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-optimize","MySQL 性能优化","SQL 优化是后端开发必备技能。",{"path":729,"title":730,"description":731},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-transaction","MySQL 事务","事务是一组不可分割的操作，要么全部成功，要么全部失败。",{"path":733,"title":734,"description":735},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fsql-advanced","SQL 进阶","多表查询、子查询、函数、视图、存储过程。",{"path":737,"title":738,"description":739},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fsql-basic","SQL 基础","SQL（Structured Query Language）是操作关系型数据库的标准语言。",{"path":741,"title":742,"description":743},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-advanced","Redis 进阶功能","事务、发布订阅、Lua 脚本、Pipeline。",{"path":745,"title":746,"description":747},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-basic","Redis 基础","Redis 安装配置与基本命令。",{"path":749,"title":750,"description":751},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-cluster","Redis 高可用","主从复制、哨兵、Cluster 集群。",{"path":753,"title":754,"description":755},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-datatype","Redis 数据类型","Redis 5 种基本数据类型 + 4 种特殊类型。",{"path":757,"title":758,"description":759},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-optimize","Redis 性能优化","内存优化、缓存问题、最佳实践。",{"path":761,"title":762,"description":763},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-persistence","Redis 持久化","Redis 提供 RDB 和 AOF 两种持久化方式。",{"path":765,"title":766,"description":767},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-principle","Redis 底层原理","数据结构、线程模型、网络模型。",{"path":769,"title":770,"description":771},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fbehaiver-patterns\u002Fobserver-pattern","观察者模式","观察者模式属于行为型模式，定义了对象之间的一对多的依赖关系，在这种模式中，当一个对象的状态发生变化时，所有依赖于它的对象都会得到通知，并且执行相关操作。观察者模式又被成为“发布—订阅模式”，即发布者发生改变后，会通知所有订阅者。",{"path":773,"title":774,"description":11},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fcreate-patterns\u002Ffactory-pattern","工厂模式",{"path":776,"title":777,"description":778},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fcreate-patterns\u002Fsingleton-pattern","单例模式","单例模式是最常用的设计模式之一，他可以保证在整个应用中，某个类只存在一个实例化对象，即全局使用到该类的只有一个对象，这种模式在需要限制某些类的实例数量时非常有用，通常全局只需要一个该对象即可，如一些配置文件映射对象、数据库连接对象等。",{"path":780,"title":781,"description":782},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fstructural-patterns\u002Fadapter-pattern","适配器模式","适配器模式是一种结构型模式，可以将一个类的接口转换成客户端所期望的另一种接口，适配器模式可以帮助开发人员在不修改现有代码的情况下，将不兼容的类组合在一起。",{"path":784,"title":785,"description":786},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-basic-operations","Git 创建版本库","在 Git 上创建版本库有两种方式，一种是直接拷贝远程 Git 仓库到本地，另外一种是我们自己创建本地的版本库。",{"path":788,"title":789,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-branch-manage","Git 分支管理",{"path":791,"title":792,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-content-operations","Git 仓库内容操作",{"path":794,"title":795,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-introduce-install","Git 介绍和安装",{"path":797,"title":798,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-remote-manage","Git 远程管理",{"path":800,"title":801,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-workspace-index-repo","Git 工作原理",{"path":803,"title":804,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fhomebrew","HomeBrew 教程",{"path":806,"title":807,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fidea\u002Fshortcuts","快捷键",{"path":809,"title":810,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fmaven\u002Fintroduce-install-config","Maven - 介绍、安装、配置",{"path":812,"title":813,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fbasic-knowledge","2. 基础知识",{"path":815,"title":816,"description":817},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fcomponent-communication","9. 组件通信","在前面的章节内，介绍了 Vue 中最核心的内容——组件的介绍和使用，和 Java 等编程语言相反，组件并不近似于这些变成语言中的类，类可以通过类或者其实例化的对象来相互交互，但 Vue 组件之间的作用域是相互独立的，这就意味着不同组件之间的数据无法相互引用。",{"path":819,"title":820,"description":821},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fcomputed","4. 计算属性","虽然直接在模板中使用表达式方便，但是如果在模板中添加很多逻辑，会让模板变的臃肿且难维护，耦合度较高。有没有一种简单的方式来实现呢？答案是有的。",{"path":823,"title":824,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fcreate-vue-project","1. 环境搭建及安装",{"path":826,"title":827,"description":828},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Flife-cycle","6. 生命周期","生命周期是指组件从创建、挂载、更新到销毁的整个过程中所经历的一系列阶段。在 Vue 中，每个组件都有自己的生命周期，可以通过生命周期钩子函数来监听和处理组件在不同阶段的行为和状态。",{"path":830,"title":831,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fother-api","10. 其他 API",{"path":833,"title":834,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fpinia","8. Pinia",{"path":836,"title":837,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Frouter","7. 路由",{"path":839,"title":840,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Ftemplate-grammar","3. 指令及模板语法",{"path":842,"title":843,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fvue3-new-component","11. Vue3 新组件",{"path":845,"title":846,"description":847},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fwatch","5. 监视","Watch 是 Vue 提供的一个用于监视响应式数据变化并执行相应操作的 API，能够对响应式数据的变化做出一些操作的功能。Vue3 中的 Watch 支持多种用法，包括监视响应式对象、ref 对象、数组、函数等。",{"path":849,"title":850,"description":11},"\u002Ftutorials\u002Fmq\u002Fkafka\u002Fkafka-introduction","Kafka 简介与安装",{"path":852,"title":853,"description":11},"\u002Ftutorials\u002Fmq\u002Fkafka\u002Fkafka-producer-consumer","Kafka 生产者与消费者",{"path":855,"title":856,"description":857},"\u002Ftutorials\u002Fmq\u002Fkafka\u002Fkafka-springboot","Spring Boot 整合 Kafka","Spring Kafka 提供了对 Apache Kafka 的便捷集成。",{"path":859,"title":860,"description":861},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-exchange","RabbitMQ Exchange 详解","Exchange（交换机）是 RabbitMQ 的核心组件，负责接收生产者发送的消息，并根据规则将消息路由到一个或多个队列。",{"path":863,"title":864,"description":11},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-introduction","RabbitMQ 简介与安装",{"path":866,"title":867,"description":868},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-reliability","RabbitMQ 消息可靠性","消息可靠性是消息队列的核心要求，RabbitMQ 提供了多种机制来保证消息不丢失。",{"path":870,"title":871,"description":872},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-springboot","Spring Boot 整合 RabbitMQ","Spring AMQP 提供了对 RabbitMQ 的便捷集成，大大简化了开发工作。",{"path":874,"title":875,"description":11},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-client","RocketMQ 客户端使用",{"path":877,"title":878,"description":11},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-concepts","RocketMQ 核心概念",{"path":880,"title":881,"description":11},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-installation","RocketMQ 安装部署",{"path":883,"title":884,"description":885},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-message-type","RocketMQ 消息类型","RocketMQ 支持多种消息类型，满足不同业务场景需求。",{"id":887,"title":26,"body":888,"description":27,"extension":6202,"meta":6203,"navigation":1089,"path":25,"seo":6204,"stem":6205,"__hash__":6206},"docs\u002Fai\u002Fagent\u002Fpractice.md",{"type":889,"value":890,"toc":6180},"minimark",[891,895,904,938,941,944,948,982,985,1023,1027,1030,1038,1042,1045,1185,1189,1193,1196,2312,2316,2320,2323,2928,2932,2935,3609,3612,3619,3623,3630,3906,3909,3913,3916,4392,4395,4456,4460,4463,5341,5344,5363,5366,5369,5372,5440,5443,5685,5688,5691,5717,5994,5997,6000,6111,6117,6123,6126,6129,6173,6176],[892,893,894],"h2",{"id":894},"项目概述",[896,897,898,899,903],"p",{},"本章将使用 ",[900,901,902],"strong",{},"LangGraph"," 从零构建一个完整的 ReAct Agent。该 Agent 具备以下能力：",[905,906,907,914,920,926,932],"ul",{},[908,909,910,913],"li",{},[900,911,912],{},"网络搜索","：从互联网获取最新信息",[908,915,916,919],{},[900,917,918],{},"数学计算","：执行复杂数学运算",[908,921,922,925],{},[900,923,924],{},"代码执行","：运行 Python 代码并返回结果",[908,927,928,931],{},[900,929,930],{},"对话记忆","：记住历史对话上下文",[908,933,934,937],{},[900,935,936],{},"错误恢复","：工具调用失败时自动重试",[896,939,940],{},"最终我们会得到一个可以部署为 API 服务的 Agent 系统。",[892,942,943],{"id":943},"环境准备",[945,946,947],"h3",{"id":947},"安装依赖",[949,950,954],"pre",{"className":951,"code":952,"language":953,"meta":11,"style":11},"language-bash shiki shiki-themes github-light github-light github-dark","pip install langgraph langchain-openai langchain-community tavily-python\n","bash",[955,956,957],"code",{"__ignoreMap":11},[958,959,962,966,970,973,976,979],"span",{"class":960,"line":961},"line",1,[958,963,965],{"class":964},"snPdu","pip",[958,967,969],{"class":968},"sIIMD"," install",[958,971,972],{"class":968}," langgraph",[958,974,975],{"class":968}," langchain-openai",[958,977,978],{"class":968}," langchain-community",[958,980,981],{"class":968}," tavily-python\n",[945,983,984],{"id":984},"环境变量配置",[949,986,988],{"className":951,"code":987,"language":953,"meta":11,"style":11},"export OPENAI_API_KEY=\"sk-your-api-key\"\nexport TAVILY_API_KEY=\"tvly-your-api-key\"  # 用于网络搜索\n",[955,989,990,1006],{"__ignoreMap":11},[958,991,992,996,1000,1003],{"class":960,"line":961},[958,993,995],{"class":994},"s8jYJ","export",[958,997,999],{"class":998},"sxrX7"," OPENAI_API_KEY",[958,1001,1002],{"class":994},"=",[958,1004,1005],{"class":968},"\"sk-your-api-key\"\n",[958,1007,1009,1011,1014,1016,1019],{"class":960,"line":1008},2,[958,1010,995],{"class":994},[958,1012,1013],{"class":998}," TAVILY_API_KEY",[958,1015,1002],{"class":994},[958,1017,1018],{"class":968},"\"tvly-your-api-key\"",[958,1020,1022],{"class":1021},"sCsY4","  # 用于网络搜索\n",[1024,1025,1026],"note",{},"\n本实战使用 OpenAI 的 GPT-4o 作为基础模型，你也可以替换为 Anthropic Claude 或其他支持工具调用的模型。Tavily 是一个专为 AI Agent 设计的搜索引擎 API，提供免费额度。\n",[945,1028,1029],{"id":1029},"项目结构",[949,1031,1036],{"className":1032,"code":1034,"language":1035},[1033],"language-text","ai-agent-project\u002F\n├── agent\u002F\n│   ├── __init__.py\n│   ├── state.py          # 状态定义\n│   ├── tools.py          # 工具定义\n│   ├── nodes.py          # 图节点\n│   └── graph.py          # 图编排\n├── server.py             # API 服务\n├── test_agent.py         # 测试\n└── requirements.txt\n","text",[955,1037,1034],{"__ignoreMap":11},[892,1039,1041],{"id":1040},"第一步定义状态","第一步：定义状态",[896,1043,1044],{},"Agent 的状态是在整个工作流中流转的数据结构。我们需要定义哪些信息需要在节点之间传递。",[949,1046,1050],{"className":1047,"code":1048,"language":1049,"meta":11,"style":11},"language-python shiki shiki-themes github-light github-light github-dark","# agent\u002Fstate.py\nfrom typing import Annotated, TypedDict\nfrom langgraph.graph.message import add_messages\n\n\nclass AgentState(TypedDict):\n    \"\"\"Agent 状态定义\n\n    Attributes:\n        messages: 对话消息历史，使用 add_messages reducer 自动追加\n        tool_call_count: 当前轮次的工具调用计数，用于防止无限循环\n        error_count: 连续错误计数，用于错误恢复决策\n    \"\"\"\n    messages: Annotated[list, add_messages]\n    tool_call_count: int\n    error_count: int\n","python",[955,1051,1052,1057,1071,1084,1091,1096,1114,1120,1125,1131,1137,1143,1149,1155,1168,1177],{"__ignoreMap":11},[958,1053,1054],{"class":960,"line":961},[958,1055,1056],{"class":1021},"# agent\u002Fstate.py\n",[958,1058,1059,1062,1065,1068],{"class":960,"line":1008},[958,1060,1061],{"class":994},"from",[958,1063,1064],{"class":998}," typing ",[958,1066,1067],{"class":994},"import",[958,1069,1070],{"class":998}," Annotated, TypedDict\n",[958,1072,1074,1076,1079,1081],{"class":960,"line":1073},3,[958,1075,1061],{"class":994},[958,1077,1078],{"class":998}," langgraph.graph.message ",[958,1080,1067],{"class":994},[958,1082,1083],{"class":998}," add_messages\n",[958,1085,1087],{"class":960,"line":1086},4,[958,1088,1090],{"emptyLinePlaceholder":1089},true,"\n",[958,1092,1094],{"class":960,"line":1093},5,[958,1095,1090],{"emptyLinePlaceholder":1089},[958,1097,1099,1102,1105,1108,1111],{"class":960,"line":1098},6,[958,1100,1101],{"class":994},"class",[958,1103,1104],{"class":964}," AgentState",[958,1106,1107],{"class":998},"(",[958,1109,1110],{"class":964},"TypedDict",[958,1112,1113],{"class":998},"):\n",[958,1115,1117],{"class":960,"line":1116},7,[958,1118,1119],{"class":968},"    \"\"\"Agent 状态定义\n",[958,1121,1123],{"class":960,"line":1122},8,[958,1124,1090],{"emptyLinePlaceholder":1089},[958,1126,1128],{"class":960,"line":1127},9,[958,1129,1130],{"class":968},"    Attributes:\n",[958,1132,1134],{"class":960,"line":1133},10,[958,1135,1136],{"class":968},"        messages: 对话消息历史，使用 add_messages reducer 自动追加\n",[958,1138,1140],{"class":960,"line":1139},11,[958,1141,1142],{"class":968},"        tool_call_count: 当前轮次的工具调用计数，用于防止无限循环\n",[958,1144,1146],{"class":960,"line":1145},12,[958,1147,1148],{"class":968},"        error_count: 连续错误计数，用于错误恢复决策\n",[958,1150,1152],{"class":960,"line":1151},13,[958,1153,1154],{"class":968},"    \"\"\"\n",[958,1156,1158,1161,1165],{"class":960,"line":1157},14,[958,1159,1160],{"class":998},"    messages: Annotated[",[958,1162,1164],{"class":1163},"sBjJW","list",[958,1166,1167],{"class":998},", add_messages]\n",[958,1169,1171,1174],{"class":960,"line":1170},15,[958,1172,1173],{"class":998},"    tool_call_count: ",[958,1175,1176],{"class":1163},"int\n",[958,1178,1180,1183],{"class":960,"line":1179},16,[958,1181,1182],{"class":998},"    error_count: ",[958,1184,1176],{"class":1163},[1186,1187,1188],"tip",{},"\n`Annotated[list, add_messages]` 中的 `add_messages` 是 LangGraph 提供的 reducer 函数。它确保新消息被**追加**到列表中而非覆盖，并且能正确处理工具调用消息的匹配。\n",[892,1190,1192],{"id":1191},"第二步定义工具","第二步：定义工具",[896,1194,1195],{},"工具是 Agent 与外部世界交互的接口。我们为 Agent 配备三个实用工具。",[949,1197,1199],{"className":1047,"code":1198,"language":1049,"meta":11,"style":11},"# agent\u002Ftools.py\nimport math\nimport traceback\nfrom langchain_core.tools import tool\nfrom langchain_community.tools.tavily_search import TavilySearchResults\n\n\n# 工具 1：网络搜索\nsearch_tool = TavilySearchResults(\n    max_results=3,\n    search_depth=\"advanced\",\n    include_answer=True,\n    description=\"搜索互联网获取最新信息。当用户询问实时新闻、最新数据、不确定的事实时使用此工具。\"\n)\n\n\n# 工具 2：数学计算\n@tool\ndef calculator(expression: str) -> str:\n    \"\"\"执行数学计算。支持基本四则运算、幂运算、三角函数、对数等。\n\n    Args:\n        expression: 数学表达式，如 \"2**10\"、\"math.sqrt(144)\"、\"math.log(100, 10)\"\n\n    Examples:\n        - \"2 + 3 * 4\" → \"14\"\n        - \"math.pi * 5**2\" → \"78.53981633974483\"\n        - \"math.factorial(10)\" → \"3628800\"\n    \"\"\"\n    # 安全的计算环境：只允许 math 模块的函数\n    allowed_names = {\n        k: v for k, v in math.__dict__.items()\n        if not k.startswith(\"_\")\n    }\n    allowed_names[\"abs\"] = abs\n    allowed_names[\"round\"] = round\n    allowed_names[\"math\"] = math\n\n    try:\n        result = eval(expression, {\"__builtins__\": {}}, allowed_names)\n        return f\"计算结果: {result}\"\n    except ZeroDivisionError:\n        return \"错误: 除数不能为零\"\n    except Exception as e:\n        return f\"计算错误: {type(e).__name__}: {e}\"\n\n\n# 工具 3：Python 代码执行\n@tool\ndef execute_python(code: str) -> str:\n    \"\"\"执行 Python 代码并返回输出结果。适用于数据处理、字符串操作、列表排序等需要编程解决的任务。\n\n    Args:\n        code: 要执行的 Python 代码。代码的 print() 输出和最后一个表达式的值会被返回。\n\n    Note:\n        代码在沙箱环境中执行，不能访问文件系统和网络。执行时间限制为 10 秒。\n    \"\"\"\n    import io\n    import contextlib\n    import signal\n\n    # 超时处理\n    def timeout_handler(signum, frame):\n        raise TimeoutError(\"代码执行超时（10秒限制）\")\n\n    output = io.StringIO()\n    try:\n        signal.signal(signal.SIGALRM, timeout_handler)\n        signal.alarm(10)\n\n        with contextlib.redirect_stdout(output):\n            # 限制可用的内置函数\n            safe_builtins = {\n                \"print\": print, \"len\": len, \"range\": range,\n                \"int\": int, \"float\": float, \"str\": str,\n                \"list\": list, \"dict\": dict, \"set\": set, \"tuple\": tuple,\n                \"sorted\": sorted, \"reversed\": reversed, \"enumerate\": enumerate,\n                \"zip\": zip, \"map\": map, \"filter\": filter,\n                \"sum\": sum, \"min\": min, \"max\": max, \"abs\": abs,\n                \"round\": round, \"isinstance\": isinstance, \"type\": type,\n                \"True\": True, \"False\": False, \"None\": None,\n            }\n            exec(code, {\"__builtins__\": safe_builtins})\n\n        signal.alarm(0)\n        result = output.getvalue()\n        return result if result else \"代码执行成功（无输出）\"\n\n    except TimeoutError as e:\n        return str(e)\n    except Exception:\n        signal.alarm(0)\n        return f\"执行错误:\\n{traceback.format_exc()}\"\n\n\n# 汇总所有工具\nall_tools = [search_tool, calculator, execute_python]\n",[955,1200,1201,1206,1213,1220,1232,1244,1248,1252,1257,1267,1281,1293,1305,1315,1320,1324,1328,1334,1340,1363,1369,1374,1380,1386,1391,1397,1403,1409,1415,1420,1426,1437,1461,1478,1484,1501,1516,1530,1535,1543,1563,1587,1598,1606,1620,1651,1656,1661,1667,1672,1691,1697,1702,1707,1713,1718,1724,1730,1735,1744,1752,1760,1765,1771,1783,1799,1804,1815,1822,1834,1845,1850,1859,1865,1875,1909,1941,1983,2016,2049,2091,2124,2156,2162,2176,2181,2191,2201,2220,2225,2236,2247,2256,2265,2285,2290,2295,2301],{"__ignoreMap":11},[958,1202,1203],{"class":960,"line":961},[958,1204,1205],{"class":1021},"# agent\u002Ftools.py\n",[958,1207,1208,1210],{"class":960,"line":1008},[958,1209,1067],{"class":994},[958,1211,1212],{"class":998}," math\n",[958,1214,1215,1217],{"class":960,"line":1073},[958,1216,1067],{"class":994},[958,1218,1219],{"class":998}," traceback\n",[958,1221,1222,1224,1227,1229],{"class":960,"line":1086},[958,1223,1061],{"class":994},[958,1225,1226],{"class":998}," langchain_core.tools ",[958,1228,1067],{"class":994},[958,1230,1231],{"class":998}," tool\n",[958,1233,1234,1236,1239,1241],{"class":960,"line":1093},[958,1235,1061],{"class":994},[958,1237,1238],{"class":998}," langchain_community.tools.tavily_search ",[958,1240,1067],{"class":994},[958,1242,1243],{"class":998}," TavilySearchResults\n",[958,1245,1246],{"class":960,"line":1098},[958,1247,1090],{"emptyLinePlaceholder":1089},[958,1249,1250],{"class":960,"line":1116},[958,1251,1090],{"emptyLinePlaceholder":1089},[958,1253,1254],{"class":960,"line":1122},[958,1255,1256],{"class":1021},"# 工具 1：网络搜索\n",[958,1258,1259,1262,1264],{"class":960,"line":1127},[958,1260,1261],{"class":998},"search_tool ",[958,1263,1002],{"class":994},[958,1265,1266],{"class":998}," TavilySearchResults(\n",[958,1268,1269,1273,1275,1278],{"class":960,"line":1133},[958,1270,1272],{"class":1271},"sP4rz","    max_results",[958,1274,1002],{"class":994},[958,1276,1277],{"class":1163},"3",[958,1279,1280],{"class":998},",\n",[958,1282,1283,1286,1288,1291],{"class":960,"line":1139},[958,1284,1285],{"class":1271},"    search_depth",[958,1287,1002],{"class":994},[958,1289,1290],{"class":968},"\"advanced\"",[958,1292,1280],{"class":998},[958,1294,1295,1298,1300,1303],{"class":960,"line":1145},[958,1296,1297],{"class":1271},"    include_answer",[958,1299,1002],{"class":994},[958,1301,1302],{"class":1163},"True",[958,1304,1280],{"class":998},[958,1306,1307,1310,1312],{"class":960,"line":1151},[958,1308,1309],{"class":1271},"    description",[958,1311,1002],{"class":994},[958,1313,1314],{"class":968},"\"搜索互联网获取最新信息。当用户询问实时新闻、最新数据、不确定的事实时使用此工具。\"\n",[958,1316,1317],{"class":960,"line":1157},[958,1318,1319],{"class":998},")\n",[958,1321,1322],{"class":960,"line":1170},[958,1323,1090],{"emptyLinePlaceholder":1089},[958,1325,1326],{"class":960,"line":1179},[958,1327,1090],{"emptyLinePlaceholder":1089},[958,1329,1331],{"class":960,"line":1330},17,[958,1332,1333],{"class":1021},"# 工具 2：数学计算\n",[958,1335,1337],{"class":960,"line":1336},18,[958,1338,1339],{"class":964},"@tool\n",[958,1341,1343,1346,1349,1352,1355,1358,1360],{"class":960,"line":1342},19,[958,1344,1345],{"class":994},"def",[958,1347,1348],{"class":964}," calculator",[958,1350,1351],{"class":998},"(expression: ",[958,1353,1354],{"class":1163},"str",[958,1356,1357],{"class":998},") -> ",[958,1359,1354],{"class":1163},[958,1361,1362],{"class":998},":\n",[958,1364,1366],{"class":960,"line":1365},20,[958,1367,1368],{"class":968},"    \"\"\"执行数学计算。支持基本四则运算、幂运算、三角函数、对数等。\n",[958,1370,1372],{"class":960,"line":1371},21,[958,1373,1090],{"emptyLinePlaceholder":1089},[958,1375,1377],{"class":960,"line":1376},22,[958,1378,1379],{"class":968},"    Args:\n",[958,1381,1383],{"class":960,"line":1382},23,[958,1384,1385],{"class":968},"        expression: 数学表达式，如 \"2**10\"、\"math.sqrt(144)\"、\"math.log(100, 10)\"\n",[958,1387,1389],{"class":960,"line":1388},24,[958,1390,1090],{"emptyLinePlaceholder":1089},[958,1392,1394],{"class":960,"line":1393},25,[958,1395,1396],{"class":968},"    Examples:\n",[958,1398,1400],{"class":960,"line":1399},26,[958,1401,1402],{"class":968},"        - \"2 + 3 * 4\" → \"14\"\n",[958,1404,1406],{"class":960,"line":1405},27,[958,1407,1408],{"class":968},"        - \"math.pi * 5**2\" → \"78.53981633974483\"\n",[958,1410,1412],{"class":960,"line":1411},28,[958,1413,1414],{"class":968},"        - \"math.factorial(10)\" → \"3628800\"\n",[958,1416,1418],{"class":960,"line":1417},29,[958,1419,1154],{"class":968},[958,1421,1423],{"class":960,"line":1422},30,[958,1424,1425],{"class":1021},"    # 安全的计算环境：只允许 math 模块的函数\n",[958,1427,1429,1432,1434],{"class":960,"line":1428},31,[958,1430,1431],{"class":998},"    allowed_names ",[958,1433,1002],{"class":994},[958,1435,1436],{"class":998}," {\n",[958,1438,1440,1443,1446,1449,1452,1455,1458],{"class":960,"line":1439},32,[958,1441,1442],{"class":998},"        k: v ",[958,1444,1445],{"class":994},"for",[958,1447,1448],{"class":998}," k, v ",[958,1450,1451],{"class":994},"in",[958,1453,1454],{"class":998}," math.",[958,1456,1457],{"class":1163},"__dict__",[958,1459,1460],{"class":998},".items()\n",[958,1462,1464,1467,1470,1473,1476],{"class":960,"line":1463},33,[958,1465,1466],{"class":994},"        if",[958,1468,1469],{"class":994}," not",[958,1471,1472],{"class":998}," k.startswith(",[958,1474,1475],{"class":968},"\"_\"",[958,1477,1319],{"class":998},[958,1479,1481],{"class":960,"line":1480},34,[958,1482,1483],{"class":998},"    }\n",[958,1485,1487,1490,1493,1496,1498],{"class":960,"line":1486},35,[958,1488,1489],{"class":998},"    allowed_names[",[958,1491,1492],{"class":968},"\"abs\"",[958,1494,1495],{"class":998},"] ",[958,1497,1002],{"class":994},[958,1499,1500],{"class":1163}," abs\n",[958,1502,1504,1506,1509,1511,1513],{"class":960,"line":1503},36,[958,1505,1489],{"class":998},[958,1507,1508],{"class":968},"\"round\"",[958,1510,1495],{"class":998},[958,1512,1002],{"class":994},[958,1514,1515],{"class":1163}," round\n",[958,1517,1519,1521,1524,1526,1528],{"class":960,"line":1518},37,[958,1520,1489],{"class":998},[958,1522,1523],{"class":968},"\"math\"",[958,1525,1495],{"class":998},[958,1527,1002],{"class":994},[958,1529,1212],{"class":998},[958,1531,1533],{"class":960,"line":1532},38,[958,1534,1090],{"emptyLinePlaceholder":1089},[958,1536,1538,1541],{"class":960,"line":1537},39,[958,1539,1540],{"class":994},"    try",[958,1542,1362],{"class":998},[958,1544,1546,1549,1551,1554,1557,1560],{"class":960,"line":1545},40,[958,1547,1548],{"class":998},"        result ",[958,1550,1002],{"class":994},[958,1552,1553],{"class":1163}," eval",[958,1555,1556],{"class":998},"(expression, {",[958,1558,1559],{"class":968},"\"__builtins__\"",[958,1561,1562],{"class":998},": {}}, allowed_names)\n",[958,1564,1566,1569,1572,1575,1578,1581,1584],{"class":960,"line":1565},41,[958,1567,1568],{"class":994},"        return",[958,1570,1571],{"class":994}," f",[958,1573,1574],{"class":968},"\"计算结果: ",[958,1576,1577],{"class":1163},"{",[958,1579,1580],{"class":998},"result",[958,1582,1583],{"class":1163},"}",[958,1585,1586],{"class":968},"\"\n",[958,1588,1590,1593,1596],{"class":960,"line":1589},42,[958,1591,1592],{"class":994},"    except",[958,1594,1595],{"class":1163}," ZeroDivisionError",[958,1597,1362],{"class":998},[958,1599,1601,1603],{"class":960,"line":1600},43,[958,1602,1568],{"class":994},[958,1604,1605],{"class":968}," \"错误: 除数不能为零\"\n",[958,1607,1609,1611,1614,1617],{"class":960,"line":1608},44,[958,1610,1592],{"class":994},[958,1612,1613],{"class":1163}," Exception",[958,1615,1616],{"class":994}," as",[958,1618,1619],{"class":998}," e:\n",[958,1621,1623,1625,1627,1630,1633,1636,1639,1642,1644,1647,1649],{"class":960,"line":1622},45,[958,1624,1568],{"class":994},[958,1626,1571],{"class":994},[958,1628,1629],{"class":968},"\"计算错误: ",[958,1631,1632],{"class":1163},"{type",[958,1634,1635],{"class":998},"(e).",[958,1637,1638],{"class":1163},"__name__}",[958,1640,1641],{"class":968},": ",[958,1643,1577],{"class":1163},[958,1645,1646],{"class":998},"e",[958,1648,1583],{"class":1163},[958,1650,1586],{"class":968},[958,1652,1654],{"class":960,"line":1653},46,[958,1655,1090],{"emptyLinePlaceholder":1089},[958,1657,1659],{"class":960,"line":1658},47,[958,1660,1090],{"emptyLinePlaceholder":1089},[958,1662,1664],{"class":960,"line":1663},48,[958,1665,1666],{"class":1021},"# 工具 3：Python 代码执行\n",[958,1668,1670],{"class":960,"line":1669},49,[958,1671,1339],{"class":964},[958,1673,1675,1677,1680,1683,1685,1687,1689],{"class":960,"line":1674},50,[958,1676,1345],{"class":994},[958,1678,1679],{"class":964}," execute_python",[958,1681,1682],{"class":998},"(code: ",[958,1684,1354],{"class":1163},[958,1686,1357],{"class":998},[958,1688,1354],{"class":1163},[958,1690,1362],{"class":998},[958,1692,1694],{"class":960,"line":1693},51,[958,1695,1696],{"class":968},"    \"\"\"执行 Python 代码并返回输出结果。适用于数据处理、字符串操作、列表排序等需要编程解决的任务。\n",[958,1698,1700],{"class":960,"line":1699},52,[958,1701,1090],{"emptyLinePlaceholder":1089},[958,1703,1705],{"class":960,"line":1704},53,[958,1706,1379],{"class":968},[958,1708,1710],{"class":960,"line":1709},54,[958,1711,1712],{"class":968},"        code: 要执行的 Python 代码。代码的 print() 输出和最后一个表达式的值会被返回。\n",[958,1714,1716],{"class":960,"line":1715},55,[958,1717,1090],{"emptyLinePlaceholder":1089},[958,1719,1721],{"class":960,"line":1720},56,[958,1722,1723],{"class":968},"    Note:\n",[958,1725,1727],{"class":960,"line":1726},57,[958,1728,1729],{"class":968},"        代码在沙箱环境中执行，不能访问文件系统和网络。执行时间限制为 10 秒。\n",[958,1731,1733],{"class":960,"line":1732},58,[958,1734,1154],{"class":968},[958,1736,1738,1741],{"class":960,"line":1737},59,[958,1739,1740],{"class":994},"    import",[958,1742,1743],{"class":998}," io\n",[958,1745,1747,1749],{"class":960,"line":1746},60,[958,1748,1740],{"class":994},[958,1750,1751],{"class":998}," contextlib\n",[958,1753,1755,1757],{"class":960,"line":1754},61,[958,1756,1740],{"class":994},[958,1758,1759],{"class":998}," signal\n",[958,1761,1763],{"class":960,"line":1762},62,[958,1764,1090],{"emptyLinePlaceholder":1089},[958,1766,1768],{"class":960,"line":1767},63,[958,1769,1770],{"class":1021},"    # 超时处理\n",[958,1772,1774,1777,1780],{"class":960,"line":1773},64,[958,1775,1776],{"class":994},"    def",[958,1778,1779],{"class":964}," timeout_handler",[958,1781,1782],{"class":998},"(signum, frame):\n",[958,1784,1786,1789,1792,1794,1797],{"class":960,"line":1785},65,[958,1787,1788],{"class":994},"        raise",[958,1790,1791],{"class":1163}," TimeoutError",[958,1793,1107],{"class":998},[958,1795,1796],{"class":968},"\"代码执行超时（10秒限制）\"",[958,1798,1319],{"class":998},[958,1800,1802],{"class":960,"line":1801},66,[958,1803,1090],{"emptyLinePlaceholder":1089},[958,1805,1807,1810,1812],{"class":960,"line":1806},67,[958,1808,1809],{"class":998},"    output ",[958,1811,1002],{"class":994},[958,1813,1814],{"class":998}," io.StringIO()\n",[958,1816,1818,1820],{"class":960,"line":1817},68,[958,1819,1540],{"class":994},[958,1821,1362],{"class":998},[958,1823,1825,1828,1831],{"class":960,"line":1824},69,[958,1826,1827],{"class":998},"        signal.signal(signal.",[958,1829,1830],{"class":1163},"SIGALRM",[958,1832,1833],{"class":998},", timeout_handler)\n",[958,1835,1837,1840,1843],{"class":960,"line":1836},70,[958,1838,1839],{"class":998},"        signal.alarm(",[958,1841,1842],{"class":1163},"10",[958,1844,1319],{"class":998},[958,1846,1848],{"class":960,"line":1847},71,[958,1849,1090],{"emptyLinePlaceholder":1089},[958,1851,1853,1856],{"class":960,"line":1852},72,[958,1854,1855],{"class":994},"        with",[958,1857,1858],{"class":998}," contextlib.redirect_stdout(output):\n",[958,1860,1862],{"class":960,"line":1861},73,[958,1863,1864],{"class":1021},"            # 限制可用的内置函数\n",[958,1866,1868,1871,1873],{"class":960,"line":1867},74,[958,1869,1870],{"class":998},"            safe_builtins ",[958,1872,1002],{"class":994},[958,1874,1436],{"class":998},[958,1876,1878,1881,1883,1886,1889,1892,1894,1897,1899,1902,1904,1907],{"class":960,"line":1877},75,[958,1879,1880],{"class":968},"                \"print\"",[958,1882,1641],{"class":998},[958,1884,1885],{"class":1163},"print",[958,1887,1888],{"class":998},", ",[958,1890,1891],{"class":968},"\"len\"",[958,1893,1641],{"class":998},[958,1895,1896],{"class":1163},"len",[958,1898,1888],{"class":998},[958,1900,1901],{"class":968},"\"range\"",[958,1903,1641],{"class":998},[958,1905,1906],{"class":1163},"range",[958,1908,1280],{"class":998},[958,1910,1912,1915,1917,1920,1922,1925,1927,1930,1932,1935,1937,1939],{"class":960,"line":1911},76,[958,1913,1914],{"class":968},"                \"int\"",[958,1916,1641],{"class":998},[958,1918,1919],{"class":1163},"int",[958,1921,1888],{"class":998},[958,1923,1924],{"class":968},"\"float\"",[958,1926,1641],{"class":998},[958,1928,1929],{"class":1163},"float",[958,1931,1888],{"class":998},[958,1933,1934],{"class":968},"\"str\"",[958,1936,1641],{"class":998},[958,1938,1354],{"class":1163},[958,1940,1280],{"class":998},[958,1942,1944,1947,1949,1951,1953,1956,1958,1961,1963,1966,1968,1971,1973,1976,1978,1981],{"class":960,"line":1943},77,[958,1945,1946],{"class":968},"                \"list\"",[958,1948,1641],{"class":998},[958,1950,1164],{"class":1163},[958,1952,1888],{"class":998},[958,1954,1955],{"class":968},"\"dict\"",[958,1957,1641],{"class":998},[958,1959,1960],{"class":1163},"dict",[958,1962,1888],{"class":998},[958,1964,1965],{"class":968},"\"set\"",[958,1967,1641],{"class":998},[958,1969,1970],{"class":1163},"set",[958,1972,1888],{"class":998},[958,1974,1975],{"class":968},"\"tuple\"",[958,1977,1641],{"class":998},[958,1979,1980],{"class":1163},"tuple",[958,1982,1280],{"class":998},[958,1984,1986,1989,1991,1994,1996,1999,2001,2004,2006,2009,2011,2014],{"class":960,"line":1985},78,[958,1987,1988],{"class":968},"                \"sorted\"",[958,1990,1641],{"class":998},[958,1992,1993],{"class":1163},"sorted",[958,1995,1888],{"class":998},[958,1997,1998],{"class":968},"\"reversed\"",[958,2000,1641],{"class":998},[958,2002,2003],{"class":1163},"reversed",[958,2005,1888],{"class":998},[958,2007,2008],{"class":968},"\"enumerate\"",[958,2010,1641],{"class":998},[958,2012,2013],{"class":1163},"enumerate",[958,2015,1280],{"class":998},[958,2017,2019,2022,2024,2027,2029,2032,2034,2037,2039,2042,2044,2047],{"class":960,"line":2018},79,[958,2020,2021],{"class":968},"                \"zip\"",[958,2023,1641],{"class":998},[958,2025,2026],{"class":1163},"zip",[958,2028,1888],{"class":998},[958,2030,2031],{"class":968},"\"map\"",[958,2033,1641],{"class":998},[958,2035,2036],{"class":1163},"map",[958,2038,1888],{"class":998},[958,2040,2041],{"class":968},"\"filter\"",[958,2043,1641],{"class":998},[958,2045,2046],{"class":1163},"filter",[958,2048,1280],{"class":998},[958,2050,2052,2055,2057,2060,2062,2065,2067,2070,2072,2075,2077,2080,2082,2084,2086,2089],{"class":960,"line":2051},80,[958,2053,2054],{"class":968},"                \"sum\"",[958,2056,1641],{"class":998},[958,2058,2059],{"class":1163},"sum",[958,2061,1888],{"class":998},[958,2063,2064],{"class":968},"\"min\"",[958,2066,1641],{"class":998},[958,2068,2069],{"class":1163},"min",[958,2071,1888],{"class":998},[958,2073,2074],{"class":968},"\"max\"",[958,2076,1641],{"class":998},[958,2078,2079],{"class":1163},"max",[958,2081,1888],{"class":998},[958,2083,1492],{"class":968},[958,2085,1641],{"class":998},[958,2087,2088],{"class":1163},"abs",[958,2090,1280],{"class":998},[958,2092,2094,2097,2099,2102,2104,2107,2109,2112,2114,2117,2119,2122],{"class":960,"line":2093},81,[958,2095,2096],{"class":968},"                \"round\"",[958,2098,1641],{"class":998},[958,2100,2101],{"class":1163},"round",[958,2103,1888],{"class":998},[958,2105,2106],{"class":968},"\"isinstance\"",[958,2108,1641],{"class":998},[958,2110,2111],{"class":1163},"isinstance",[958,2113,1888],{"class":998},[958,2115,2116],{"class":968},"\"type\"",[958,2118,1641],{"class":998},[958,2120,2121],{"class":1163},"type",[958,2123,1280],{"class":998},[958,2125,2127,2130,2132,2134,2136,2139,2141,2144,2146,2149,2151,2154],{"class":960,"line":2126},82,[958,2128,2129],{"class":968},"                \"True\"",[958,2131,1641],{"class":998},[958,2133,1302],{"class":1163},[958,2135,1888],{"class":998},[958,2137,2138],{"class":968},"\"False\"",[958,2140,1641],{"class":998},[958,2142,2143],{"class":1163},"False",[958,2145,1888],{"class":998},[958,2147,2148],{"class":968},"\"None\"",[958,2150,1641],{"class":998},[958,2152,2153],{"class":1163},"None",[958,2155,1280],{"class":998},[958,2157,2159],{"class":960,"line":2158},83,[958,2160,2161],{"class":998},"            }\n",[958,2163,2165,2168,2171,2173],{"class":960,"line":2164},84,[958,2166,2167],{"class":1163},"            exec",[958,2169,2170],{"class":998},"(code, {",[958,2172,1559],{"class":968},[958,2174,2175],{"class":998},": safe_builtins})\n",[958,2177,2179],{"class":960,"line":2178},85,[958,2180,1090],{"emptyLinePlaceholder":1089},[958,2182,2184,2186,2189],{"class":960,"line":2183},86,[958,2185,1839],{"class":998},[958,2187,2188],{"class":1163},"0",[958,2190,1319],{"class":998},[958,2192,2194,2196,2198],{"class":960,"line":2193},87,[958,2195,1548],{"class":998},[958,2197,1002],{"class":994},[958,2199,2200],{"class":998}," output.getvalue()\n",[958,2202,2204,2206,2209,2212,2214,2217],{"class":960,"line":2203},88,[958,2205,1568],{"class":994},[958,2207,2208],{"class":998}," result ",[958,2210,2211],{"class":994},"if",[958,2213,2208],{"class":998},[958,2215,2216],{"class":994},"else",[958,2218,2219],{"class":968}," \"代码执行成功（无输出）\"\n",[958,2221,2223],{"class":960,"line":2222},89,[958,2224,1090],{"emptyLinePlaceholder":1089},[958,2226,2228,2230,2232,2234],{"class":960,"line":2227},90,[958,2229,1592],{"class":994},[958,2231,1791],{"class":1163},[958,2233,1616],{"class":994},[958,2235,1619],{"class":998},[958,2237,2239,2241,2244],{"class":960,"line":2238},91,[958,2240,1568],{"class":994},[958,2242,2243],{"class":1163}," str",[958,2245,2246],{"class":998},"(e)\n",[958,2248,2250,2252,2254],{"class":960,"line":2249},92,[958,2251,1592],{"class":994},[958,2253,1613],{"class":1163},[958,2255,1362],{"class":998},[958,2257,2259,2261,2263],{"class":960,"line":2258},93,[958,2260,1839],{"class":998},[958,2262,2188],{"class":1163},[958,2264,1319],{"class":998},[958,2266,2268,2270,2272,2275,2278,2281,2283],{"class":960,"line":2267},94,[958,2269,1568],{"class":994},[958,2271,1571],{"class":994},[958,2273,2274],{"class":968},"\"执行错误:",[958,2276,2277],{"class":1163},"\\n{",[958,2279,2280],{"class":998},"traceback.format_exc()",[958,2282,1583],{"class":1163},[958,2284,1586],{"class":968},[958,2286,2288],{"class":960,"line":2287},95,[958,2289,1090],{"emptyLinePlaceholder":1089},[958,2291,2293],{"class":960,"line":2292},96,[958,2294,1090],{"emptyLinePlaceholder":1089},[958,2296,2298],{"class":960,"line":2297},97,[958,2299,2300],{"class":1021},"# 汇总所有工具\n",[958,2302,2304,2307,2309],{"class":960,"line":2303},98,[958,2305,2306],{"class":998},"all_tools ",[958,2308,1002],{"class":994},[958,2310,2311],{"class":998}," [search_tool, calculator, execute_python]\n",[2313,2314,2315],"warning",{},"\n`execute_python` 工具在生产环境中需要更严格的沙箱隔离（如 Docker 容器、gVisor 等）。上面的示例仅做了基本的安全限制，不适合直接用于面向用户的生产系统。\n",[892,2317,2319],{"id":2318},"第三步定义图节点","第三步：定义图节点",[896,2321,2322],{},"节点是 Agent 工作流中的执行单元。我们定义三个核心节点：Agent 推理、工具执行、错误处理。",[949,2324,2326],{"className":1047,"code":2325,"language":1049,"meta":11,"style":11},"# agent\u002Fnodes.py\nfrom langchain_openai import ChatOpenAI\nfrom langgraph.prebuilt import ToolNode\nfrom langchain_core.messages import AIMessage, SystemMessage\nfrom agent.state import AgentState\nfrom agent.tools import all_tools\n\n# 配置 LLM\nllm = ChatOpenAI(\n    model=\"gpt-4o\",\n    temperature=0,  # Agent 场景建议使用低温度，提高确定性\n).bind_tools(all_tools)\n\n# 系统提示词\nSYSTEM_PROMPT = \"\"\"你是一个强大的 AI 助手，能够使用工具来帮助用户完成各种任务。\n\n工作准则：\n1. 仔细理解用户需求，必要时主动澄清\n2. 将复杂任务分解为小步骤，逐步完成\n3. 优先使用工具获取准确信息，避免猜测\n4. 如果工具调用失败，分析原因并尝试其他方案\n5. 在最终回答中综合所有信息，给出清晰、有条理的回复\n\n注意事项：\n- 数学计算请使用 calculator 工具，不要心算\n- 需要多步处理的数据任务请使用 execute_python 工具\n- 关于实时信息（新闻、价格、天气等）请使用搜索工具\n\"\"\"\n\n\ndef agent_node(state: AgentState) -> dict:\n    \"\"\"Agent 推理节点：调用 LLM 进行思考和决策\"\"\"\n    messages = state[\"messages\"]\n\n    # 确保系统提示词在消息列表开头\n    if not messages or not isinstance(messages[0], SystemMessage):\n        messages = [SystemMessage(content=SYSTEM_PROMPT)] + messages\n\n    response = llm.invoke(messages)\n\n    # 更新工具调用计数\n    tool_call_count = state.get(\"tool_call_count\", 0)\n    if response.tool_calls:\n        tool_call_count += len(response.tool_calls)\n\n    return {\n        \"messages\": [response],\n        \"tool_call_count\": tool_call_count,\n        \"error_count\": 0  # 成功调用时重置错误计数\n    }\n\n\n# 工具执行节点（使用 LangGraph 内置的 ToolNode）\ntool_node = ToolNode(all_tools)\n\n\ndef error_handler_node(state: AgentState) -> dict:\n    \"\"\"错误处理节点：当工具调用失败时提供恢复策略\"\"\"\n    error_count = state.get(\"error_count\", 0) + 1\n\n    if error_count >= 3:\n        # 连续失败 3 次，放弃工具调用，直接回复\n        error_message = AIMessage(\n            content=\"抱歉，工具调用多次失败。让我尝试直接根据已有信息回答您的问题。\"\n        )\n    else:\n        error_message = AIMessage(\n            content=f\"工具调用出现问题（第 {error_count} 次），正在重试...\"\n        )\n\n    return {\n        \"messages\": [error_message],\n        \"error_count\": error_count\n    }\n",[955,2327,2328,2333,2345,2357,2369,2381,2393,2397,2402,2412,2424,2439,2444,2448,2453,2464,2468,2473,2478,2483,2488,2493,2498,2502,2507,2512,2517,2522,2527,2531,2535,2549,2554,2570,2574,2579,2605,2631,2635,2645,2649,2654,2673,2680,2694,2698,2705,2713,2721,2733,2737,2741,2745,2750,2760,2764,2768,2781,2786,2810,2814,2829,2834,2844,2854,2859,2866,2874,2896,2900,2904,2910,2917,2924],{"__ignoreMap":11},[958,2329,2330],{"class":960,"line":961},[958,2331,2332],{"class":1021},"# agent\u002Fnodes.py\n",[958,2334,2335,2337,2340,2342],{"class":960,"line":1008},[958,2336,1061],{"class":994},[958,2338,2339],{"class":998}," langchain_openai ",[958,2341,1067],{"class":994},[958,2343,2344],{"class":998}," ChatOpenAI\n",[958,2346,2347,2349,2352,2354],{"class":960,"line":1073},[958,2348,1061],{"class":994},[958,2350,2351],{"class":998}," langgraph.prebuilt ",[958,2353,1067],{"class":994},[958,2355,2356],{"class":998}," ToolNode\n",[958,2358,2359,2361,2364,2366],{"class":960,"line":1086},[958,2360,1061],{"class":994},[958,2362,2363],{"class":998}," langchain_core.messages ",[958,2365,1067],{"class":994},[958,2367,2368],{"class":998}," AIMessage, SystemMessage\n",[958,2370,2371,2373,2376,2378],{"class":960,"line":1093},[958,2372,1061],{"class":994},[958,2374,2375],{"class":998}," agent.state ",[958,2377,1067],{"class":994},[958,2379,2380],{"class":998}," AgentState\n",[958,2382,2383,2385,2388,2390],{"class":960,"line":1098},[958,2384,1061],{"class":994},[958,2386,2387],{"class":998}," agent.tools ",[958,2389,1067],{"class":994},[958,2391,2392],{"class":998}," all_tools\n",[958,2394,2395],{"class":960,"line":1116},[958,2396,1090],{"emptyLinePlaceholder":1089},[958,2398,2399],{"class":960,"line":1122},[958,2400,2401],{"class":1021},"# 配置 LLM\n",[958,2403,2404,2407,2409],{"class":960,"line":1127},[958,2405,2406],{"class":998},"llm ",[958,2408,1002],{"class":994},[958,2410,2411],{"class":998}," ChatOpenAI(\n",[958,2413,2414,2417,2419,2422],{"class":960,"line":1133},[958,2415,2416],{"class":1271},"    model",[958,2418,1002],{"class":994},[958,2420,2421],{"class":968},"\"gpt-4o\"",[958,2423,1280],{"class":998},[958,2425,2426,2429,2431,2433,2436],{"class":960,"line":1139},[958,2427,2428],{"class":1271},"    temperature",[958,2430,1002],{"class":994},[958,2432,2188],{"class":1163},[958,2434,2435],{"class":998},",  ",[958,2437,2438],{"class":1021},"# Agent 场景建议使用低温度，提高确定性\n",[958,2440,2441],{"class":960,"line":1145},[958,2442,2443],{"class":998},").bind_tools(all_tools)\n",[958,2445,2446],{"class":960,"line":1151},[958,2447,1090],{"emptyLinePlaceholder":1089},[958,2449,2450],{"class":960,"line":1157},[958,2451,2452],{"class":1021},"# 系统提示词\n",[958,2454,2455,2458,2461],{"class":960,"line":1170},[958,2456,2457],{"class":1163},"SYSTEM_PROMPT",[958,2459,2460],{"class":994}," =",[958,2462,2463],{"class":968}," \"\"\"你是一个强大的 AI 助手，能够使用工具来帮助用户完成各种任务。\n",[958,2465,2466],{"class":960,"line":1179},[958,2467,1090],{"emptyLinePlaceholder":1089},[958,2469,2470],{"class":960,"line":1330},[958,2471,2472],{"class":968},"工作准则：\n",[958,2474,2475],{"class":960,"line":1336},[958,2476,2477],{"class":968},"1. 仔细理解用户需求，必要时主动澄清\n",[958,2479,2480],{"class":960,"line":1342},[958,2481,2482],{"class":968},"2. 将复杂任务分解为小步骤，逐步完成\n",[958,2484,2485],{"class":960,"line":1365},[958,2486,2487],{"class":968},"3. 优先使用工具获取准确信息，避免猜测\n",[958,2489,2490],{"class":960,"line":1371},[958,2491,2492],{"class":968},"4. 如果工具调用失败，分析原因并尝试其他方案\n",[958,2494,2495],{"class":960,"line":1376},[958,2496,2497],{"class":968},"5. 在最终回答中综合所有信息，给出清晰、有条理的回复\n",[958,2499,2500],{"class":960,"line":1382},[958,2501,1090],{"emptyLinePlaceholder":1089},[958,2503,2504],{"class":960,"line":1388},[958,2505,2506],{"class":968},"注意事项：\n",[958,2508,2509],{"class":960,"line":1393},[958,2510,2511],{"class":968},"- 数学计算请使用 calculator 工具，不要心算\n",[958,2513,2514],{"class":960,"line":1399},[958,2515,2516],{"class":968},"- 需要多步处理的数据任务请使用 execute_python 工具\n",[958,2518,2519],{"class":960,"line":1405},[958,2520,2521],{"class":968},"- 关于实时信息（新闻、价格、天气等）请使用搜索工具\n",[958,2523,2524],{"class":960,"line":1411},[958,2525,2526],{"class":968},"\"\"\"\n",[958,2528,2529],{"class":960,"line":1417},[958,2530,1090],{"emptyLinePlaceholder":1089},[958,2532,2533],{"class":960,"line":1422},[958,2534,1090],{"emptyLinePlaceholder":1089},[958,2536,2537,2539,2542,2545,2547],{"class":960,"line":1428},[958,2538,1345],{"class":994},[958,2540,2541],{"class":964}," agent_node",[958,2543,2544],{"class":998},"(state: AgentState) -> ",[958,2546,1960],{"class":1163},[958,2548,1362],{"class":998},[958,2550,2551],{"class":960,"line":1439},[958,2552,2553],{"class":968},"    \"\"\"Agent 推理节点：调用 LLM 进行思考和决策\"\"\"\n",[958,2555,2556,2559,2561,2564,2567],{"class":960,"line":1463},[958,2557,2558],{"class":998},"    messages ",[958,2560,1002],{"class":994},[958,2562,2563],{"class":998}," state[",[958,2565,2566],{"class":968},"\"messages\"",[958,2568,2569],{"class":998},"]\n",[958,2571,2572],{"class":960,"line":1480},[958,2573,1090],{"emptyLinePlaceholder":1089},[958,2575,2576],{"class":960,"line":1486},[958,2577,2578],{"class":1021},"    # 确保系统提示词在消息列表开头\n",[958,2580,2581,2584,2586,2589,2592,2594,2597,2600,2602],{"class":960,"line":1503},[958,2582,2583],{"class":994},"    if",[958,2585,1469],{"class":994},[958,2587,2588],{"class":998}," messages ",[958,2590,2591],{"class":994},"or",[958,2593,1469],{"class":994},[958,2595,2596],{"class":1163}," isinstance",[958,2598,2599],{"class":998},"(messages[",[958,2601,2188],{"class":1163},[958,2603,2604],{"class":998},"], SystemMessage):\n",[958,2606,2607,2610,2612,2615,2618,2620,2622,2625,2628],{"class":960,"line":1518},[958,2608,2609],{"class":998},"        messages ",[958,2611,1002],{"class":994},[958,2613,2614],{"class":998}," [SystemMessage(",[958,2616,2617],{"class":1271},"content",[958,2619,1002],{"class":994},[958,2621,2457],{"class":1163},[958,2623,2624],{"class":998},")] ",[958,2626,2627],{"class":994},"+",[958,2629,2630],{"class":998}," messages\n",[958,2632,2633],{"class":960,"line":1532},[958,2634,1090],{"emptyLinePlaceholder":1089},[958,2636,2637,2640,2642],{"class":960,"line":1537},[958,2638,2639],{"class":998},"    response ",[958,2641,1002],{"class":994},[958,2643,2644],{"class":998}," llm.invoke(messages)\n",[958,2646,2647],{"class":960,"line":1545},[958,2648,1090],{"emptyLinePlaceholder":1089},[958,2650,2651],{"class":960,"line":1565},[958,2652,2653],{"class":1021},"    # 更新工具调用计数\n",[958,2655,2656,2659,2661,2664,2667,2669,2671],{"class":960,"line":1589},[958,2657,2658],{"class":998},"    tool_call_count ",[958,2660,1002],{"class":994},[958,2662,2663],{"class":998}," state.get(",[958,2665,2666],{"class":968},"\"tool_call_count\"",[958,2668,1888],{"class":998},[958,2670,2188],{"class":1163},[958,2672,1319],{"class":998},[958,2674,2675,2677],{"class":960,"line":1600},[958,2676,2583],{"class":994},[958,2678,2679],{"class":998}," response.tool_calls:\n",[958,2681,2682,2685,2688,2691],{"class":960,"line":1608},[958,2683,2684],{"class":998},"        tool_call_count ",[958,2686,2687],{"class":994},"+=",[958,2689,2690],{"class":1163}," len",[958,2692,2693],{"class":998},"(response.tool_calls)\n",[958,2695,2696],{"class":960,"line":1622},[958,2697,1090],{"emptyLinePlaceholder":1089},[958,2699,2700,2703],{"class":960,"line":1653},[958,2701,2702],{"class":994},"    return",[958,2704,1436],{"class":998},[958,2706,2707,2710],{"class":960,"line":1658},[958,2708,2709],{"class":968},"        \"messages\"",[958,2711,2712],{"class":998},": [response],\n",[958,2714,2715,2718],{"class":960,"line":1663},[958,2716,2717],{"class":968},"        \"tool_call_count\"",[958,2719,2720],{"class":998},": tool_call_count,\n",[958,2722,2723,2726,2728,2730],{"class":960,"line":1669},[958,2724,2725],{"class":968},"        \"error_count\"",[958,2727,1641],{"class":998},[958,2729,2188],{"class":1163},[958,2731,2732],{"class":1021},"  # 成功调用时重置错误计数\n",[958,2734,2735],{"class":960,"line":1674},[958,2736,1483],{"class":998},[958,2738,2739],{"class":960,"line":1693},[958,2740,1090],{"emptyLinePlaceholder":1089},[958,2742,2743],{"class":960,"line":1699},[958,2744,1090],{"emptyLinePlaceholder":1089},[958,2746,2747],{"class":960,"line":1704},[958,2748,2749],{"class":1021},"# 工具执行节点（使用 LangGraph 内置的 ToolNode）\n",[958,2751,2752,2755,2757],{"class":960,"line":1709},[958,2753,2754],{"class":998},"tool_node ",[958,2756,1002],{"class":994},[958,2758,2759],{"class":998}," ToolNode(all_tools)\n",[958,2761,2762],{"class":960,"line":1715},[958,2763,1090],{"emptyLinePlaceholder":1089},[958,2765,2766],{"class":960,"line":1720},[958,2767,1090],{"emptyLinePlaceholder":1089},[958,2769,2770,2772,2775,2777,2779],{"class":960,"line":1726},[958,2771,1345],{"class":994},[958,2773,2774],{"class":964}," error_handler_node",[958,2776,2544],{"class":998},[958,2778,1960],{"class":1163},[958,2780,1362],{"class":998},[958,2782,2783],{"class":960,"line":1732},[958,2784,2785],{"class":968},"    \"\"\"错误处理节点：当工具调用失败时提供恢复策略\"\"\"\n",[958,2787,2788,2791,2793,2795,2798,2800,2802,2805,2807],{"class":960,"line":1737},[958,2789,2790],{"class":998},"    error_count ",[958,2792,1002],{"class":994},[958,2794,2663],{"class":998},[958,2796,2797],{"class":968},"\"error_count\"",[958,2799,1888],{"class":998},[958,2801,2188],{"class":1163},[958,2803,2804],{"class":998},") ",[958,2806,2627],{"class":994},[958,2808,2809],{"class":1163}," 1\n",[958,2811,2812],{"class":960,"line":1746},[958,2813,1090],{"emptyLinePlaceholder":1089},[958,2815,2816,2818,2821,2824,2827],{"class":960,"line":1754},[958,2817,2583],{"class":994},[958,2819,2820],{"class":998}," error_count ",[958,2822,2823],{"class":994},">=",[958,2825,2826],{"class":1163}," 3",[958,2828,1362],{"class":998},[958,2830,2831],{"class":960,"line":1762},[958,2832,2833],{"class":1021},"        # 连续失败 3 次，放弃工具调用，直接回复\n",[958,2835,2836,2839,2841],{"class":960,"line":1767},[958,2837,2838],{"class":998},"        error_message ",[958,2840,1002],{"class":994},[958,2842,2843],{"class":998}," AIMessage(\n",[958,2845,2846,2849,2851],{"class":960,"line":1773},[958,2847,2848],{"class":1271},"            content",[958,2850,1002],{"class":994},[958,2852,2853],{"class":968},"\"抱歉，工具调用多次失败。让我尝试直接根据已有信息回答您的问题。\"\n",[958,2855,2856],{"class":960,"line":1785},[958,2857,2858],{"class":998},"        )\n",[958,2860,2861,2864],{"class":960,"line":1801},[958,2862,2863],{"class":994},"    else",[958,2865,1362],{"class":998},[958,2867,2868,2870,2872],{"class":960,"line":1806},[958,2869,2838],{"class":998},[958,2871,1002],{"class":994},[958,2873,2843],{"class":998},[958,2875,2876,2878,2880,2883,2886,2888,2891,2893],{"class":960,"line":1817},[958,2877,2848],{"class":1271},[958,2879,1002],{"class":994},[958,2881,2882],{"class":994},"f",[958,2884,2885],{"class":968},"\"工具调用出现问题（第 ",[958,2887,1577],{"class":1163},[958,2889,2890],{"class":998},"error_count",[958,2892,1583],{"class":1163},[958,2894,2895],{"class":968}," 次），正在重试...\"\n",[958,2897,2898],{"class":960,"line":1824},[958,2899,2858],{"class":998},[958,2901,2902],{"class":960,"line":1836},[958,2903,1090],{"emptyLinePlaceholder":1089},[958,2905,2906,2908],{"class":960,"line":1847},[958,2907,2702],{"class":994},[958,2909,1436],{"class":998},[958,2911,2912,2914],{"class":960,"line":1852},[958,2913,2709],{"class":968},[958,2915,2916],{"class":998},": [error_message],\n",[958,2918,2919,2921],{"class":960,"line":1861},[958,2920,2725],{"class":968},[958,2922,2923],{"class":998},": error_count\n",[958,2925,2926],{"class":960,"line":1867},[958,2927,1483],{"class":998},[892,2929,2931],{"id":2930},"第四步编排工作流图","第四步：编排工作流图",[896,2933,2934],{},"这是核心步骤——将所有节点通过边连接起来，形成完整的 Agent 工作流。",[949,2936,2938],{"className":1047,"code":2937,"language":1049,"meta":11,"style":11},"# agent\u002Fgraph.py\nfrom langgraph.graph import StateGraph, START, END\nfrom langgraph.checkpoint.memory import MemorySaver\nfrom agent.state import AgentState\nfrom agent.nodes import agent_node, tool_node, error_handler_node\n\n# 最大工具调用次数限制\nMAX_TOOL_CALLS = 15\n\n\ndef should_continue(state: AgentState) -> str:\n    \"\"\"条件边：决定 Agent 的下一步动作\n\n    返回值对应图中的边：\n    - \"tools\": 前往工具执行节点\n    - \"end\": 结束对话\n    - \"error\": 前往错误处理节点\n    \"\"\"\n    messages = state[\"messages\"]\n    last_message = messages[-1]\n    tool_call_count = state.get(\"tool_call_count\", 0)\n\n    # 检查是否超过工具调用上限\n    if tool_call_count >= MAX_TOOL_CALLS:\n        return \"end\"\n\n    # 检查最后一条消息是否包含工具调用\n    if hasattr(last_message, \"tool_calls\") and last_message.tool_calls:\n        return \"tools\"\n\n    return \"end\"\n\n\ndef after_tool_execution(state: AgentState) -> str:\n    \"\"\"工具执行后的路由：检查执行结果\"\"\"\n    messages = state[\"messages\"]\n    last_message = messages[-1]\n\n    # 检查工具执行是否出错\n    if hasattr(last_message, \"content\") and \"error\" in str(last_message.content).lower():\n        error_count = state.get(\"error_count\", 0)\n        if error_count >= 3:\n            return \"end\"\n        return \"error\"\n\n    return \"agent\"\n\n\ndef build_agent_graph():\n    \"\"\"构建 Agent 工作流图\"\"\"\n    # 创建图\n    graph = StateGraph(AgentState)\n\n    # 添加节点\n    graph.add_node(\"agent\", agent_node)\n    graph.add_node(\"tools\", tool_node)\n    graph.add_node(\"error_handler\", error_handler_node)\n\n    # 添加边\n    graph.add_edge(START, \"agent\")\n\n    # Agent 节点的条件路由\n    graph.add_conditional_edges(\n        \"agent\",\n        should_continue,\n        {\n            \"tools\": \"tools\",\n            \"end\": END,\n            \"error\": \"error_handler\"\n        }\n    )\n\n    # 工具节点执行后回到 Agent\n    graph.add_edge(\"tools\", \"agent\")\n\n    # 错误处理后回到 Agent 重试\n    graph.add_edge(\"error_handler\", \"agent\")\n\n    # 使用内存检查点实现对话记忆\n    memory = MemorySaver()\n    return graph.compile(checkpointer=memory)\n\n\n# 创建全局 Agent 实例\nagent = build_agent_graph()\n",[955,2939,2940,2945,2965,2977,2987,2999,3003,3008,3018,3022,3026,3039,3044,3048,3053,3058,3063,3068,3072,3084,3102,3118,3122,3127,3141,3148,3152,3157,3178,3185,3189,3195,3199,3203,3216,3221,3233,3247,3251,3256,3282,3299,3311,3318,3325,3329,3336,3340,3344,3354,3359,3364,3374,3378,3383,3394,3404,3414,3418,3423,3436,3440,3445,3450,3457,3462,3467,3478,3490,3500,3505,3510,3514,3519,3531,3535,3540,3552,3556,3561,3571,3586,3590,3594,3599],{"__ignoreMap":11},[958,2941,2942],{"class":960,"line":961},[958,2943,2944],{"class":1021},"# agent\u002Fgraph.py\n",[958,2946,2947,2949,2952,2954,2957,2960,2962],{"class":960,"line":1008},[958,2948,1061],{"class":994},[958,2950,2951],{"class":998}," langgraph.graph ",[958,2953,1067],{"class":994},[958,2955,2956],{"class":998}," StateGraph, ",[958,2958,2959],{"class":1163},"START",[958,2961,1888],{"class":998},[958,2963,2964],{"class":1163},"END\n",[958,2966,2967,2969,2972,2974],{"class":960,"line":1073},[958,2968,1061],{"class":994},[958,2970,2971],{"class":998}," langgraph.checkpoint.memory ",[958,2973,1067],{"class":994},[958,2975,2976],{"class":998}," MemorySaver\n",[958,2978,2979,2981,2983,2985],{"class":960,"line":1086},[958,2980,1061],{"class":994},[958,2982,2375],{"class":998},[958,2984,1067],{"class":994},[958,2986,2380],{"class":998},[958,2988,2989,2991,2994,2996],{"class":960,"line":1093},[958,2990,1061],{"class":994},[958,2992,2993],{"class":998}," agent.nodes ",[958,2995,1067],{"class":994},[958,2997,2998],{"class":998}," agent_node, tool_node, error_handler_node\n",[958,3000,3001],{"class":960,"line":1098},[958,3002,1090],{"emptyLinePlaceholder":1089},[958,3004,3005],{"class":960,"line":1116},[958,3006,3007],{"class":1021},"# 最大工具调用次数限制\n",[958,3009,3010,3013,3015],{"class":960,"line":1122},[958,3011,3012],{"class":1163},"MAX_TOOL_CALLS",[958,3014,2460],{"class":994},[958,3016,3017],{"class":1163}," 15\n",[958,3019,3020],{"class":960,"line":1127},[958,3021,1090],{"emptyLinePlaceholder":1089},[958,3023,3024],{"class":960,"line":1133},[958,3025,1090],{"emptyLinePlaceholder":1089},[958,3027,3028,3030,3033,3035,3037],{"class":960,"line":1139},[958,3029,1345],{"class":994},[958,3031,3032],{"class":964}," should_continue",[958,3034,2544],{"class":998},[958,3036,1354],{"class":1163},[958,3038,1362],{"class":998},[958,3040,3041],{"class":960,"line":1145},[958,3042,3043],{"class":968},"    \"\"\"条件边：决定 Agent 的下一步动作\n",[958,3045,3046],{"class":960,"line":1151},[958,3047,1090],{"emptyLinePlaceholder":1089},[958,3049,3050],{"class":960,"line":1157},[958,3051,3052],{"class":968},"    返回值对应图中的边：\n",[958,3054,3055],{"class":960,"line":1170},[958,3056,3057],{"class":968},"    - \"tools\": 前往工具执行节点\n",[958,3059,3060],{"class":960,"line":1179},[958,3061,3062],{"class":968},"    - \"end\": 结束对话\n",[958,3064,3065],{"class":960,"line":1330},[958,3066,3067],{"class":968},"    - \"error\": 前往错误处理节点\n",[958,3069,3070],{"class":960,"line":1336},[958,3071,1154],{"class":968},[958,3073,3074,3076,3078,3080,3082],{"class":960,"line":1342},[958,3075,2558],{"class":998},[958,3077,1002],{"class":994},[958,3079,2563],{"class":998},[958,3081,2566],{"class":968},[958,3083,2569],{"class":998},[958,3085,3086,3089,3091,3094,3097,3100],{"class":960,"line":1365},[958,3087,3088],{"class":998},"    last_message ",[958,3090,1002],{"class":994},[958,3092,3093],{"class":998}," messages[",[958,3095,3096],{"class":994},"-",[958,3098,3099],{"class":1163},"1",[958,3101,2569],{"class":998},[958,3103,3104,3106,3108,3110,3112,3114,3116],{"class":960,"line":1371},[958,3105,2658],{"class":998},[958,3107,1002],{"class":994},[958,3109,2663],{"class":998},[958,3111,2666],{"class":968},[958,3113,1888],{"class":998},[958,3115,2188],{"class":1163},[958,3117,1319],{"class":998},[958,3119,3120],{"class":960,"line":1376},[958,3121,1090],{"emptyLinePlaceholder":1089},[958,3123,3124],{"class":960,"line":1382},[958,3125,3126],{"class":1021},"    # 检查是否超过工具调用上限\n",[958,3128,3129,3131,3134,3136,3139],{"class":960,"line":1388},[958,3130,2583],{"class":994},[958,3132,3133],{"class":998}," tool_call_count ",[958,3135,2823],{"class":994},[958,3137,3138],{"class":1163}," MAX_TOOL_CALLS",[958,3140,1362],{"class":998},[958,3142,3143,3145],{"class":960,"line":1393},[958,3144,1568],{"class":994},[958,3146,3147],{"class":968}," \"end\"\n",[958,3149,3150],{"class":960,"line":1399},[958,3151,1090],{"emptyLinePlaceholder":1089},[958,3153,3154],{"class":960,"line":1405},[958,3155,3156],{"class":1021},"    # 检查最后一条消息是否包含工具调用\n",[958,3158,3159,3161,3164,3167,3170,3172,3175],{"class":960,"line":1411},[958,3160,2583],{"class":994},[958,3162,3163],{"class":1163}," hasattr",[958,3165,3166],{"class":998},"(last_message, ",[958,3168,3169],{"class":968},"\"tool_calls\"",[958,3171,2804],{"class":998},[958,3173,3174],{"class":994},"and",[958,3176,3177],{"class":998}," last_message.tool_calls:\n",[958,3179,3180,3182],{"class":960,"line":1417},[958,3181,1568],{"class":994},[958,3183,3184],{"class":968}," \"tools\"\n",[958,3186,3187],{"class":960,"line":1422},[958,3188,1090],{"emptyLinePlaceholder":1089},[958,3190,3191,3193],{"class":960,"line":1428},[958,3192,2702],{"class":994},[958,3194,3147],{"class":968},[958,3196,3197],{"class":960,"line":1439},[958,3198,1090],{"emptyLinePlaceholder":1089},[958,3200,3201],{"class":960,"line":1463},[958,3202,1090],{"emptyLinePlaceholder":1089},[958,3204,3205,3207,3210,3212,3214],{"class":960,"line":1480},[958,3206,1345],{"class":994},[958,3208,3209],{"class":964}," after_tool_execution",[958,3211,2544],{"class":998},[958,3213,1354],{"class":1163},[958,3215,1362],{"class":998},[958,3217,3218],{"class":960,"line":1486},[958,3219,3220],{"class":968},"    \"\"\"工具执行后的路由：检查执行结果\"\"\"\n",[958,3222,3223,3225,3227,3229,3231],{"class":960,"line":1503},[958,3224,2558],{"class":998},[958,3226,1002],{"class":994},[958,3228,2563],{"class":998},[958,3230,2566],{"class":968},[958,3232,2569],{"class":998},[958,3234,3235,3237,3239,3241,3243,3245],{"class":960,"line":1518},[958,3236,3088],{"class":998},[958,3238,1002],{"class":994},[958,3240,3093],{"class":998},[958,3242,3096],{"class":994},[958,3244,3099],{"class":1163},[958,3246,2569],{"class":998},[958,3248,3249],{"class":960,"line":1532},[958,3250,1090],{"emptyLinePlaceholder":1089},[958,3252,3253],{"class":960,"line":1537},[958,3254,3255],{"class":1021},"    # 检查工具执行是否出错\n",[958,3257,3258,3260,3262,3264,3267,3269,3271,3274,3277,3279],{"class":960,"line":1545},[958,3259,2583],{"class":994},[958,3261,3163],{"class":1163},[958,3263,3166],{"class":998},[958,3265,3266],{"class":968},"\"content\"",[958,3268,2804],{"class":998},[958,3270,3174],{"class":994},[958,3272,3273],{"class":968}," \"error\"",[958,3275,3276],{"class":994}," in",[958,3278,2243],{"class":1163},[958,3280,3281],{"class":998},"(last_message.content).lower():\n",[958,3283,3284,3287,3289,3291,3293,3295,3297],{"class":960,"line":1565},[958,3285,3286],{"class":998},"        error_count ",[958,3288,1002],{"class":994},[958,3290,2663],{"class":998},[958,3292,2797],{"class":968},[958,3294,1888],{"class":998},[958,3296,2188],{"class":1163},[958,3298,1319],{"class":998},[958,3300,3301,3303,3305,3307,3309],{"class":960,"line":1589},[958,3302,1466],{"class":994},[958,3304,2820],{"class":998},[958,3306,2823],{"class":994},[958,3308,2826],{"class":1163},[958,3310,1362],{"class":998},[958,3312,3313,3316],{"class":960,"line":1600},[958,3314,3315],{"class":994},"            return",[958,3317,3147],{"class":968},[958,3319,3320,3322],{"class":960,"line":1608},[958,3321,1568],{"class":994},[958,3323,3324],{"class":968}," \"error\"\n",[958,3326,3327],{"class":960,"line":1622},[958,3328,1090],{"emptyLinePlaceholder":1089},[958,3330,3331,3333],{"class":960,"line":1653},[958,3332,2702],{"class":994},[958,3334,3335],{"class":968}," \"agent\"\n",[958,3337,3338],{"class":960,"line":1658},[958,3339,1090],{"emptyLinePlaceholder":1089},[958,3341,3342],{"class":960,"line":1663},[958,3343,1090],{"emptyLinePlaceholder":1089},[958,3345,3346,3348,3351],{"class":960,"line":1669},[958,3347,1345],{"class":994},[958,3349,3350],{"class":964}," build_agent_graph",[958,3352,3353],{"class":998},"():\n",[958,3355,3356],{"class":960,"line":1674},[958,3357,3358],{"class":968},"    \"\"\"构建 Agent 工作流图\"\"\"\n",[958,3360,3361],{"class":960,"line":1693},[958,3362,3363],{"class":1021},"    # 创建图\n",[958,3365,3366,3369,3371],{"class":960,"line":1699},[958,3367,3368],{"class":998},"    graph ",[958,3370,1002],{"class":994},[958,3372,3373],{"class":998}," StateGraph(AgentState)\n",[958,3375,3376],{"class":960,"line":1704},[958,3377,1090],{"emptyLinePlaceholder":1089},[958,3379,3380],{"class":960,"line":1709},[958,3381,3382],{"class":1021},"    # 添加节点\n",[958,3384,3385,3388,3391],{"class":960,"line":1715},[958,3386,3387],{"class":998},"    graph.add_node(",[958,3389,3390],{"class":968},"\"agent\"",[958,3392,3393],{"class":998},", agent_node)\n",[958,3395,3396,3398,3401],{"class":960,"line":1720},[958,3397,3387],{"class":998},[958,3399,3400],{"class":968},"\"tools\"",[958,3402,3403],{"class":998},", tool_node)\n",[958,3405,3406,3408,3411],{"class":960,"line":1726},[958,3407,3387],{"class":998},[958,3409,3410],{"class":968},"\"error_handler\"",[958,3412,3413],{"class":998},", error_handler_node)\n",[958,3415,3416],{"class":960,"line":1732},[958,3417,1090],{"emptyLinePlaceholder":1089},[958,3419,3420],{"class":960,"line":1737},[958,3421,3422],{"class":1021},"    # 添加边\n",[958,3424,3425,3428,3430,3432,3434],{"class":960,"line":1746},[958,3426,3427],{"class":998},"    graph.add_edge(",[958,3429,2959],{"class":1163},[958,3431,1888],{"class":998},[958,3433,3390],{"class":968},[958,3435,1319],{"class":998},[958,3437,3438],{"class":960,"line":1754},[958,3439,1090],{"emptyLinePlaceholder":1089},[958,3441,3442],{"class":960,"line":1762},[958,3443,3444],{"class":1021},"    # Agent 节点的条件路由\n",[958,3446,3447],{"class":960,"line":1767},[958,3448,3449],{"class":998},"    graph.add_conditional_edges(\n",[958,3451,3452,3455],{"class":960,"line":1773},[958,3453,3454],{"class":968},"        \"agent\"",[958,3456,1280],{"class":998},[958,3458,3459],{"class":960,"line":1785},[958,3460,3461],{"class":998},"        should_continue,\n",[958,3463,3464],{"class":960,"line":1801},[958,3465,3466],{"class":998},"        {\n",[958,3468,3469,3472,3474,3476],{"class":960,"line":1806},[958,3470,3471],{"class":968},"            \"tools\"",[958,3473,1641],{"class":998},[958,3475,3400],{"class":968},[958,3477,1280],{"class":998},[958,3479,3480,3483,3485,3488],{"class":960,"line":1817},[958,3481,3482],{"class":968},"            \"end\"",[958,3484,1641],{"class":998},[958,3486,3487],{"class":1163},"END",[958,3489,1280],{"class":998},[958,3491,3492,3495,3497],{"class":960,"line":1824},[958,3493,3494],{"class":968},"            \"error\"",[958,3496,1641],{"class":998},[958,3498,3499],{"class":968},"\"error_handler\"\n",[958,3501,3502],{"class":960,"line":1836},[958,3503,3504],{"class":998},"        }\n",[958,3506,3507],{"class":960,"line":1847},[958,3508,3509],{"class":998},"    )\n",[958,3511,3512],{"class":960,"line":1852},[958,3513,1090],{"emptyLinePlaceholder":1089},[958,3515,3516],{"class":960,"line":1861},[958,3517,3518],{"class":1021},"    # 工具节点执行后回到 Agent\n",[958,3520,3521,3523,3525,3527,3529],{"class":960,"line":1867},[958,3522,3427],{"class":998},[958,3524,3400],{"class":968},[958,3526,1888],{"class":998},[958,3528,3390],{"class":968},[958,3530,1319],{"class":998},[958,3532,3533],{"class":960,"line":1877},[958,3534,1090],{"emptyLinePlaceholder":1089},[958,3536,3537],{"class":960,"line":1911},[958,3538,3539],{"class":1021},"    # 错误处理后回到 Agent 重试\n",[958,3541,3542,3544,3546,3548,3550],{"class":960,"line":1943},[958,3543,3427],{"class":998},[958,3545,3410],{"class":968},[958,3547,1888],{"class":998},[958,3549,3390],{"class":968},[958,3551,1319],{"class":998},[958,3553,3554],{"class":960,"line":1985},[958,3555,1090],{"emptyLinePlaceholder":1089},[958,3557,3558],{"class":960,"line":2018},[958,3559,3560],{"class":1021},"    # 使用内存检查点实现对话记忆\n",[958,3562,3563,3566,3568],{"class":960,"line":2051},[958,3564,3565],{"class":998},"    memory ",[958,3567,1002],{"class":994},[958,3569,3570],{"class":998}," MemorySaver()\n",[958,3572,3573,3575,3578,3581,3583],{"class":960,"line":2093},[958,3574,2702],{"class":994},[958,3576,3577],{"class":998}," graph.compile(",[958,3579,3580],{"class":1271},"checkpointer",[958,3582,1002],{"class":994},[958,3584,3585],{"class":998},"memory)\n",[958,3587,3588],{"class":960,"line":2126},[958,3589,1090],{"emptyLinePlaceholder":1089},[958,3591,3592],{"class":960,"line":2158},[958,3593,1090],{"emptyLinePlaceholder":1089},[958,3595,3596],{"class":960,"line":2164},[958,3597,3598],{"class":1021},"# 创建全局 Agent 实例\n",[958,3600,3601,3604,3606],{"class":960,"line":2178},[958,3602,3603],{"class":998},"agent ",[958,3605,1002],{"class":994},[958,3607,3608],{"class":998}," build_agent_graph()\n",[896,3610,3611],{},"工作流图的完整结构：",[949,3613,3617],{"className":3614,"code":3615,"language":1035,"meta":3616},[1033],"flowchart TD\n    START[\"START\"] --> Agent[\"agent\"]\n    Agent --> Cond{\"需要工具？\"}\n    Cond -->|是| Tools[\"tools\"]\n    Tools --> Agent\n    Cond -->|否| END[\"END\"]\n    Err[\"error_handler\"] --> Agent\n","mermaid",[955,3618,3615],{"__ignoreMap":11},[892,3620,3622],{"id":3621},"第五步对话记忆管理","第五步：对话记忆管理",[896,3624,3625,3626,3629],{},"LangGraph 的 Checkpointer 机制让我们轻松实现对话记忆。每个对话通过 ",[955,3627,3628],{},"thread_id"," 隔离。",[949,3631,3633],{"className":1047,"code":3632,"language":1049,"meta":11,"style":11},"# 使用示例\nfrom agent.graph import agent\n\ndef chat(user_input: str, thread_id: str = \"default\"):\n    \"\"\"与 Agent 对话\n\n    Args:\n        user_input: 用户输入\n        thread_id: 对话线程 ID，同一 ID 共享对话历史\n    \"\"\"\n    config = {\"configurable\": {\"thread_id\": thread_id}}\n\n    result = agent.invoke(\n        {\n            \"messages\": [(\"user\", user_input)],\n            \"tool_call_count\": 0,\n            \"error_count\": 0\n        },\n        config=config\n    )\n\n    # 提取最终回复\n    return result[\"messages\"][-1].content\n\n\n# 同一 thread_id 下的多轮对话共享记忆\nprint(chat(\"我叫小明，我是一名 Python 开发者\", thread_id=\"user_001\"))\nprint(chat(\"我刚才说我叫什么名字？\", thread_id=\"user_001\"))  # Agent 能记住\nprint(chat(\"帮我搜索一下 Python 3.13 的新特性\", thread_id=\"user_001\"))\n",[955,3634,3635,3640,3652,3656,3680,3685,3689,3693,3698,3703,3707,3729,3733,3743,3747,3761,3772,3782,3787,3797,3801,3805,3810,3829,3833,3837,3842,3864,3887],{"__ignoreMap":11},[958,3636,3637],{"class":960,"line":961},[958,3638,3639],{"class":1021},"# 使用示例\n",[958,3641,3642,3644,3647,3649],{"class":960,"line":1008},[958,3643,1061],{"class":994},[958,3645,3646],{"class":998}," agent.graph ",[958,3648,1067],{"class":994},[958,3650,3651],{"class":998}," agent\n",[958,3653,3654],{"class":960,"line":1073},[958,3655,1090],{"emptyLinePlaceholder":1089},[958,3657,3658,3660,3663,3666,3668,3671,3673,3675,3678],{"class":960,"line":1086},[958,3659,1345],{"class":994},[958,3661,3662],{"class":964}," chat",[958,3664,3665],{"class":998},"(user_input: ",[958,3667,1354],{"class":1163},[958,3669,3670],{"class":998},", thread_id: ",[958,3672,1354],{"class":1163},[958,3674,2460],{"class":994},[958,3676,3677],{"class":968}," \"default\"",[958,3679,1113],{"class":998},[958,3681,3682],{"class":960,"line":1093},[958,3683,3684],{"class":968},"    \"\"\"与 Agent 对话\n",[958,3686,3687],{"class":960,"line":1098},[958,3688,1090],{"emptyLinePlaceholder":1089},[958,3690,3691],{"class":960,"line":1116},[958,3692,1379],{"class":968},[958,3694,3695],{"class":960,"line":1122},[958,3696,3697],{"class":968},"        user_input: 用户输入\n",[958,3699,3700],{"class":960,"line":1127},[958,3701,3702],{"class":968},"        thread_id: 对话线程 ID，同一 ID 共享对话历史\n",[958,3704,3705],{"class":960,"line":1133},[958,3706,1154],{"class":968},[958,3708,3709,3712,3714,3717,3720,3723,3726],{"class":960,"line":1139},[958,3710,3711],{"class":998},"    config ",[958,3713,1002],{"class":994},[958,3715,3716],{"class":998}," {",[958,3718,3719],{"class":968},"\"configurable\"",[958,3721,3722],{"class":998},": {",[958,3724,3725],{"class":968},"\"thread_id\"",[958,3727,3728],{"class":998},": thread_id}}\n",[958,3730,3731],{"class":960,"line":1145},[958,3732,1090],{"emptyLinePlaceholder":1089},[958,3734,3735,3738,3740],{"class":960,"line":1151},[958,3736,3737],{"class":998},"    result ",[958,3739,1002],{"class":994},[958,3741,3742],{"class":998}," agent.invoke(\n",[958,3744,3745],{"class":960,"line":1157},[958,3746,3466],{"class":998},[958,3748,3749,3752,3755,3758],{"class":960,"line":1170},[958,3750,3751],{"class":968},"            \"messages\"",[958,3753,3754],{"class":998},": [(",[958,3756,3757],{"class":968},"\"user\"",[958,3759,3760],{"class":998},", user_input)],\n",[958,3762,3763,3766,3768,3770],{"class":960,"line":1179},[958,3764,3765],{"class":968},"            \"tool_call_count\"",[958,3767,1641],{"class":998},[958,3769,2188],{"class":1163},[958,3771,1280],{"class":998},[958,3773,3774,3777,3779],{"class":960,"line":1330},[958,3775,3776],{"class":968},"            \"error_count\"",[958,3778,1641],{"class":998},[958,3780,3781],{"class":1163},"0\n",[958,3783,3784],{"class":960,"line":1336},[958,3785,3786],{"class":998},"        },\n",[958,3788,3789,3792,3794],{"class":960,"line":1342},[958,3790,3791],{"class":1271},"        config",[958,3793,1002],{"class":994},[958,3795,3796],{"class":998},"config\n",[958,3798,3799],{"class":960,"line":1365},[958,3800,3509],{"class":998},[958,3802,3803],{"class":960,"line":1371},[958,3804,1090],{"emptyLinePlaceholder":1089},[958,3806,3807],{"class":960,"line":1376},[958,3808,3809],{"class":1021},"    # 提取最终回复\n",[958,3811,3812,3814,3817,3819,3822,3824,3826],{"class":960,"line":1382},[958,3813,2702],{"class":994},[958,3815,3816],{"class":998}," result[",[958,3818,2566],{"class":968},[958,3820,3821],{"class":998},"][",[958,3823,3096],{"class":994},[958,3825,3099],{"class":1163},[958,3827,3828],{"class":998},"].content\n",[958,3830,3831],{"class":960,"line":1388},[958,3832,1090],{"emptyLinePlaceholder":1089},[958,3834,3835],{"class":960,"line":1393},[958,3836,1090],{"emptyLinePlaceholder":1089},[958,3838,3839],{"class":960,"line":1399},[958,3840,3841],{"class":1021},"# 同一 thread_id 下的多轮对话共享记忆\n",[958,3843,3844,3846,3849,3852,3854,3856,3858,3861],{"class":960,"line":1405},[958,3845,1885],{"class":1163},[958,3847,3848],{"class":998},"(chat(",[958,3850,3851],{"class":968},"\"我叫小明，我是一名 Python 开发者\"",[958,3853,1888],{"class":998},[958,3855,3628],{"class":1271},[958,3857,1002],{"class":994},[958,3859,3860],{"class":968},"\"user_001\"",[958,3862,3863],{"class":998},"))\n",[958,3865,3866,3868,3870,3873,3875,3877,3879,3881,3884],{"class":960,"line":1411},[958,3867,1885],{"class":1163},[958,3869,3848],{"class":998},[958,3871,3872],{"class":968},"\"我刚才说我叫什么名字？\"",[958,3874,1888],{"class":998},[958,3876,3628],{"class":1271},[958,3878,1002],{"class":994},[958,3880,3860],{"class":968},[958,3882,3883],{"class":998},"))  ",[958,3885,3886],{"class":1021},"# Agent 能记住\n",[958,3888,3889,3891,3893,3896,3898,3900,3902,3904],{"class":960,"line":1417},[958,3890,1885],{"class":1163},[958,3892,3848],{"class":998},[958,3894,3895],{"class":968},"\"帮我搜索一下 Python 3.13 的新特性\"",[958,3897,1888],{"class":998},[958,3899,3628],{"class":1271},[958,3901,1002],{"class":994},[958,3903,3860],{"class":968},[958,3905,3863],{"class":998},[1024,3907,3908],{},"\n`MemorySaver` 将状态存储在内存中，进程重启后会丢失。生产环境中应使用持久化存储，如 `SqliteSaver` 或 `PostgresSaver`。LangGraph 提供了对应的 checkpointer 实现。\n",[892,3910,3912],{"id":3911},"第六步部署为-api-服务","第六步：部署为 API 服务",[896,3914,3915],{},"使用 FastAPI 将 Agent 部署为 HTTP 服务：",[949,3917,3919],{"className":1047,"code":3918,"language":1049,"meta":11,"style":11},"# server.py\nfrom fastapi import FastAPI, HTTPException\nfrom pydantic import BaseModel\nfrom agent.graph import agent\nimport uvicorn\n\napp = FastAPI(title=\"AI Agent API\")\n\n\nclass ChatRequest(BaseModel):\n    message: str\n    thread_id: str = \"default\"\n\n\nclass ChatResponse(BaseModel):\n    reply: str\n    tool_calls_made: int\n\n\n@app.post(\"\u002Fchat\", response_model=ChatResponse)\nasync def chat_endpoint(request: ChatRequest):\n    try:\n        config = {\"configurable\": {\"thread_id\": request.thread_id}}\n\n        result = agent.invoke(\n            {\n                \"messages\": [(\"user\", request.message)],\n                \"tool_call_count\": 0,\n                \"error_count\": 0\n            },\n            config=config\n        )\n\n        return ChatResponse(\n            reply=result[\"messages\"][-1].content,\n            tool_calls_made=result.get(\"tool_call_count\", 0)\n        )\n\n    except Exception as e:\n        raise HTTPException(status_code=500, detail=str(e))\n\n\n@app.get(\"\u002Fhealth\")\nasync def health_check():\n    return {\"status\": \"healthy\"}\n\n\nif __name__ == \"__main__\":\n    uvicorn.run(app, host=\"0.0.0.0\", port=8000)\n",[955,3920,3921,3926,3938,3950,3960,3967,3971,3991,3995,3999,4013,4021,4033,4037,4041,4054,4061,4068,4072,4076,4096,4110,4116,4134,4138,4146,4151,4163,4174,4183,4188,4197,4201,4205,4212,4233,4251,4255,4259,4269,4296,4300,4304,4316,4327,4344,4348,4352,4367],{"__ignoreMap":11},[958,3922,3923],{"class":960,"line":961},[958,3924,3925],{"class":1021},"# server.py\n",[958,3927,3928,3930,3933,3935],{"class":960,"line":1008},[958,3929,1061],{"class":994},[958,3931,3932],{"class":998}," fastapi ",[958,3934,1067],{"class":994},[958,3936,3937],{"class":998}," FastAPI, HTTPException\n",[958,3939,3940,3942,3945,3947],{"class":960,"line":1073},[958,3941,1061],{"class":994},[958,3943,3944],{"class":998}," pydantic ",[958,3946,1067],{"class":994},[958,3948,3949],{"class":998}," BaseModel\n",[958,3951,3952,3954,3956,3958],{"class":960,"line":1086},[958,3953,1061],{"class":994},[958,3955,3646],{"class":998},[958,3957,1067],{"class":994},[958,3959,3651],{"class":998},[958,3961,3962,3964],{"class":960,"line":1093},[958,3963,1067],{"class":994},[958,3965,3966],{"class":998}," uvicorn\n",[958,3968,3969],{"class":960,"line":1098},[958,3970,1090],{"emptyLinePlaceholder":1089},[958,3972,3973,3976,3978,3981,3984,3986,3989],{"class":960,"line":1116},[958,3974,3975],{"class":998},"app ",[958,3977,1002],{"class":994},[958,3979,3980],{"class":998}," FastAPI(",[958,3982,3983],{"class":1271},"title",[958,3985,1002],{"class":994},[958,3987,3988],{"class":968},"\"AI Agent API\"",[958,3990,1319],{"class":998},[958,3992,3993],{"class":960,"line":1122},[958,3994,1090],{"emptyLinePlaceholder":1089},[958,3996,3997],{"class":960,"line":1127},[958,3998,1090],{"emptyLinePlaceholder":1089},[958,4000,4001,4003,4006,4008,4011],{"class":960,"line":1133},[958,4002,1101],{"class":994},[958,4004,4005],{"class":964}," ChatRequest",[958,4007,1107],{"class":998},[958,4009,4010],{"class":964},"BaseModel",[958,4012,1113],{"class":998},[958,4014,4015,4018],{"class":960,"line":1139},[958,4016,4017],{"class":998},"    message: ",[958,4019,4020],{"class":1163},"str\n",[958,4022,4023,4026,4028,4030],{"class":960,"line":1145},[958,4024,4025],{"class":998},"    thread_id: ",[958,4027,1354],{"class":1163},[958,4029,2460],{"class":994},[958,4031,4032],{"class":968}," \"default\"\n",[958,4034,4035],{"class":960,"line":1151},[958,4036,1090],{"emptyLinePlaceholder":1089},[958,4038,4039],{"class":960,"line":1157},[958,4040,1090],{"emptyLinePlaceholder":1089},[958,4042,4043,4045,4048,4050,4052],{"class":960,"line":1170},[958,4044,1101],{"class":994},[958,4046,4047],{"class":964}," ChatResponse",[958,4049,1107],{"class":998},[958,4051,4010],{"class":964},[958,4053,1113],{"class":998},[958,4055,4056,4059],{"class":960,"line":1179},[958,4057,4058],{"class":998},"    reply: ",[958,4060,4020],{"class":1163},[958,4062,4063,4066],{"class":960,"line":1330},[958,4064,4065],{"class":998},"    tool_calls_made: ",[958,4067,1176],{"class":1163},[958,4069,4070],{"class":960,"line":1336},[958,4071,1090],{"emptyLinePlaceholder":1089},[958,4073,4074],{"class":960,"line":1342},[958,4075,1090],{"emptyLinePlaceholder":1089},[958,4077,4078,4081,4083,4086,4088,4091,4093],{"class":960,"line":1365},[958,4079,4080],{"class":964},"@app.post",[958,4082,1107],{"class":998},[958,4084,4085],{"class":968},"\"\u002Fchat\"",[958,4087,1888],{"class":998},[958,4089,4090],{"class":1271},"response_model",[958,4092,1002],{"class":994},[958,4094,4095],{"class":998},"ChatResponse)\n",[958,4097,4098,4101,4104,4107],{"class":960,"line":1371},[958,4099,4100],{"class":994},"async",[958,4102,4103],{"class":994}," def",[958,4105,4106],{"class":964}," chat_endpoint",[958,4108,4109],{"class":998},"(request: ChatRequest):\n",[958,4111,4112,4114],{"class":960,"line":1376},[958,4113,1540],{"class":994},[958,4115,1362],{"class":998},[958,4117,4118,4121,4123,4125,4127,4129,4131],{"class":960,"line":1382},[958,4119,4120],{"class":998},"        config ",[958,4122,1002],{"class":994},[958,4124,3716],{"class":998},[958,4126,3719],{"class":968},[958,4128,3722],{"class":998},[958,4130,3725],{"class":968},[958,4132,4133],{"class":998},": request.thread_id}}\n",[958,4135,4136],{"class":960,"line":1388},[958,4137,1090],{"emptyLinePlaceholder":1089},[958,4139,4140,4142,4144],{"class":960,"line":1393},[958,4141,1548],{"class":998},[958,4143,1002],{"class":994},[958,4145,3742],{"class":998},[958,4147,4148],{"class":960,"line":1399},[958,4149,4150],{"class":998},"            {\n",[958,4152,4153,4156,4158,4160],{"class":960,"line":1405},[958,4154,4155],{"class":968},"                \"messages\"",[958,4157,3754],{"class":998},[958,4159,3757],{"class":968},[958,4161,4162],{"class":998},", request.message)],\n",[958,4164,4165,4168,4170,4172],{"class":960,"line":1411},[958,4166,4167],{"class":968},"                \"tool_call_count\"",[958,4169,1641],{"class":998},[958,4171,2188],{"class":1163},[958,4173,1280],{"class":998},[958,4175,4176,4179,4181],{"class":960,"line":1417},[958,4177,4178],{"class":968},"                \"error_count\"",[958,4180,1641],{"class":998},[958,4182,3781],{"class":1163},[958,4184,4185],{"class":960,"line":1422},[958,4186,4187],{"class":998},"            },\n",[958,4189,4190,4193,4195],{"class":960,"line":1428},[958,4191,4192],{"class":1271},"            config",[958,4194,1002],{"class":994},[958,4196,3796],{"class":998},[958,4198,4199],{"class":960,"line":1439},[958,4200,2858],{"class":998},[958,4202,4203],{"class":960,"line":1463},[958,4204,1090],{"emptyLinePlaceholder":1089},[958,4206,4207,4209],{"class":960,"line":1480},[958,4208,1568],{"class":994},[958,4210,4211],{"class":998}," ChatResponse(\n",[958,4213,4214,4217,4219,4222,4224,4226,4228,4230],{"class":960,"line":1486},[958,4215,4216],{"class":1271},"            reply",[958,4218,1002],{"class":994},[958,4220,4221],{"class":998},"result[",[958,4223,2566],{"class":968},[958,4225,3821],{"class":998},[958,4227,3096],{"class":994},[958,4229,3099],{"class":1163},[958,4231,4232],{"class":998},"].content,\n",[958,4234,4235,4238,4240,4243,4245,4247,4249],{"class":960,"line":1503},[958,4236,4237],{"class":1271},"            tool_calls_made",[958,4239,1002],{"class":994},[958,4241,4242],{"class":998},"result.get(",[958,4244,2666],{"class":968},[958,4246,1888],{"class":998},[958,4248,2188],{"class":1163},[958,4250,1319],{"class":998},[958,4252,4253],{"class":960,"line":1518},[958,4254,2858],{"class":998},[958,4256,4257],{"class":960,"line":1532},[958,4258,1090],{"emptyLinePlaceholder":1089},[958,4260,4261,4263,4265,4267],{"class":960,"line":1537},[958,4262,1592],{"class":994},[958,4264,1613],{"class":1163},[958,4266,1616],{"class":994},[958,4268,1619],{"class":998},[958,4270,4271,4273,4276,4279,4281,4284,4286,4289,4291,4293],{"class":960,"line":1545},[958,4272,1788],{"class":994},[958,4274,4275],{"class":998}," HTTPException(",[958,4277,4278],{"class":1271},"status_code",[958,4280,1002],{"class":994},[958,4282,4283],{"class":1163},"500",[958,4285,1888],{"class":998},[958,4287,4288],{"class":1271},"detail",[958,4290,1002],{"class":994},[958,4292,1354],{"class":1163},[958,4294,4295],{"class":998},"(e))\n",[958,4297,4298],{"class":960,"line":1565},[958,4299,1090],{"emptyLinePlaceholder":1089},[958,4301,4302],{"class":960,"line":1589},[958,4303,1090],{"emptyLinePlaceholder":1089},[958,4305,4306,4309,4311,4314],{"class":960,"line":1600},[958,4307,4308],{"class":964},"@app.get",[958,4310,1107],{"class":998},[958,4312,4313],{"class":968},"\"\u002Fhealth\"",[958,4315,1319],{"class":998},[958,4317,4318,4320,4322,4325],{"class":960,"line":1608},[958,4319,4100],{"class":994},[958,4321,4103],{"class":994},[958,4323,4324],{"class":964}," health_check",[958,4326,3353],{"class":998},[958,4328,4329,4331,4333,4336,4338,4341],{"class":960,"line":1622},[958,4330,2702],{"class":994},[958,4332,3716],{"class":998},[958,4334,4335],{"class":968},"\"status\"",[958,4337,1641],{"class":998},[958,4339,4340],{"class":968},"\"healthy\"",[958,4342,4343],{"class":998},"}\n",[958,4345,4346],{"class":960,"line":1653},[958,4347,1090],{"emptyLinePlaceholder":1089},[958,4349,4350],{"class":960,"line":1658},[958,4351,1090],{"emptyLinePlaceholder":1089},[958,4353,4354,4356,4359,4362,4365],{"class":960,"line":1663},[958,4355,2211],{"class":994},[958,4357,4358],{"class":1163}," __name__",[958,4360,4361],{"class":994}," ==",[958,4363,4364],{"class":968}," \"__main__\"",[958,4366,1362],{"class":998},[958,4368,4369,4372,4375,4377,4380,4382,4385,4387,4390],{"class":960,"line":1669},[958,4370,4371],{"class":998},"    uvicorn.run(app, ",[958,4373,4374],{"class":1271},"host",[958,4376,1002],{"class":994},[958,4378,4379],{"class":968},"\"0.0.0.0\"",[958,4381,1888],{"class":998},[958,4383,4384],{"class":1271},"port",[958,4386,1002],{"class":994},[958,4388,4389],{"class":1163},"8000",[958,4391,1319],{"class":998},[896,4393,4394],{},"使用方式：",[949,4396,4398],{"className":951,"code":4397,"language":953,"meta":11,"style":11},"# 启动服务\npython server.py\n\n# 调用 API\ncurl -X POST http:\u002F\u002Flocalhost:8000\u002Fchat \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"message\": \"帮我计算 2 的 100 次方\", \"thread_id\": \"test_001\"}'\n",[955,4399,4400,4405,4412,4416,4421,4438,4448],{"__ignoreMap":11},[958,4401,4402],{"class":960,"line":961},[958,4403,4404],{"class":1021},"# 启动服务\n",[958,4406,4407,4409],{"class":960,"line":1008},[958,4408,1049],{"class":964},[958,4410,4411],{"class":968}," server.py\n",[958,4413,4414],{"class":960,"line":1073},[958,4415,1090],{"emptyLinePlaceholder":1089},[958,4417,4418],{"class":960,"line":1086},[958,4419,4420],{"class":1021},"# 调用 API\n",[958,4422,4423,4426,4429,4432,4435],{"class":960,"line":1093},[958,4424,4425],{"class":964},"curl",[958,4427,4428],{"class":1163}," -X",[958,4430,4431],{"class":968}," POST",[958,4433,4434],{"class":968}," http:\u002F\u002Flocalhost:8000\u002Fchat",[958,4436,4437],{"class":1163}," \\\n",[958,4439,4440,4443,4446],{"class":960,"line":1098},[958,4441,4442],{"class":1163},"  -H",[958,4444,4445],{"class":968}," \"Content-Type: application\u002Fjson\"",[958,4447,4437],{"class":1163},[958,4449,4450,4453],{"class":960,"line":1116},[958,4451,4452],{"class":1163},"  -d",[958,4454,4455],{"class":968}," '{\"message\": \"帮我计算 2 的 100 次方\", \"thread_id\": \"test_001\"}'\n",[892,4457,4459],{"id":4458},"第七步测试策略","第七步：测试策略",[896,4461,4462],{},"Agent 系统的测试需要覆盖多个层次：",[949,4464,4466],{"className":1047,"code":4465,"language":1049,"meta":11,"style":11},"# test_agent.py\nimport pytest\nfrom agent.tools import calculator, execute_python\nfrom agent.graph import agent\n\n\nclass TestTools:\n    \"\"\"工具单元测试\"\"\"\n\n    def test_calculator_basic(self):\n        result = calculator.invoke({\"expression\": \"2 + 3\"})\n        assert \"5\" in result\n\n    def test_calculator_complex(self):\n        result = calculator.invoke({\"expression\": \"math.sqrt(144)\"})\n        assert \"12\" in result\n\n    def test_calculator_division_by_zero(self):\n        result = calculator.invoke({\"expression\": \"1 \u002F 0\"})\n        assert \"错误\" in result or \"零\" in result\n\n    def test_execute_python_basic(self):\n        result = execute_python.invoke({\"code\": \"print(sum(range(10)))\"})\n        assert \"45\" in result\n\n    def test_execute_python_timeout(self):\n        result = execute_python.invoke({\"code\": \"while True: pass\"})\n        assert \"超时\" in result\n\n\nclass TestAgentIntegration:\n    \"\"\"Agent 集成测试\"\"\"\n\n    def test_simple_question(self):\n        \"\"\"测试简单问答（不需要工具）\"\"\"\n        result = agent.invoke(\n            {\n                \"messages\": [(\"user\", \"你好，介绍一下你自己\")],\n                \"tool_call_count\": 0,\n                \"error_count\": 0\n            },\n            config={\"configurable\": {\"thread_id\": \"test_simple\"}}\n        )\n        assert len(result[\"messages\"]) >= 2\n        assert result[\"messages\"][-1].content  # 确保有回复\n\n    def test_calculator_usage(self):\n        \"\"\"测试 Agent 是否能正确使用计算器工具\"\"\"\n        result = agent.invoke(\n            {\n                \"messages\": [(\"user\", \"请计算 123456 * 789\")],\n                \"tool_call_count\": 0,\n                \"error_count\": 0\n            },\n            config={\"configurable\": {\"thread_id\": \"test_calc\"}}\n        )\n        reply = result[\"messages\"][-1].content\n        assert \"97406784\" in reply\n\n    def test_multi_turn_memory(self):\n        \"\"\"测试多轮对话记忆\"\"\"\n        thread_id = \"test_memory\"\n        config = {\"configurable\": {\"thread_id\": thread_id}}\n\n        # 第一轮\n        agent.invoke(\n            {\n                \"messages\": [(\"user\", \"记住这个数字：42\")],\n                \"tool_call_count\": 0,\n                \"error_count\": 0\n            },\n            config=config\n        )\n\n        # 第二轮\n        result = agent.invoke(\n            {\n                \"messages\": [(\"user\", \"我刚才让你记住的数字是多少？\")],\n                \"tool_call_count\": 0,\n                \"error_count\": 0\n            },\n            config=config\n        )\n        assert \"42\" in result[\"messages\"][-1].content\n\n    def test_tool_call_limit(self):\n        \"\"\"测试工具调用上限保护\"\"\"\n        result = agent.invoke(\n            {\n                \"messages\": [(\"user\", \"连续搜索10个不同的话题\")],\n                \"tool_call_count\": 14,  # 接近上限\n                \"error_count\": 0\n            },\n            config={\"configurable\": {\"thread_id\": \"test_limit\"}}\n        )\n        # Agent 应该在达到上限后停止工具调用\n        assert result[\"tool_call_count\"] \u003C= 15\n",[955,4467,4468,4473,4480,4491,4501,4505,4509,4518,4523,4527,4537,4557,4570,4574,4583,4600,4611,4615,4624,4641,4661,4665,4674,4693,4704,4708,4717,4734,4745,4749,4753,4762,4767,4771,4780,4785,4793,4797,4813,4823,4831,4835,4857,4861,4880,4900,4904,4913,4918,4926,4930,4945,4955,4963,4967,4988,4992,5011,5023,5027,5036,5041,5051,5067,5071,5076,5081,5085,5100,5110,5118,5122,5130,5134,5138,5143,5151,5155,5170,5180,5188,5192,5200,5204,5225,5229,5238,5243,5251,5255,5270,5284,5292,5296,5317,5321,5326],{"__ignoreMap":11},[958,4469,4470],{"class":960,"line":961},[958,4471,4472],{"class":1021},"# test_agent.py\n",[958,4474,4475,4477],{"class":960,"line":1008},[958,4476,1067],{"class":994},[958,4478,4479],{"class":998}," pytest\n",[958,4481,4482,4484,4486,4488],{"class":960,"line":1073},[958,4483,1061],{"class":994},[958,4485,2387],{"class":998},[958,4487,1067],{"class":994},[958,4489,4490],{"class":998}," calculator, execute_python\n",[958,4492,4493,4495,4497,4499],{"class":960,"line":1086},[958,4494,1061],{"class":994},[958,4496,3646],{"class":998},[958,4498,1067],{"class":994},[958,4500,3651],{"class":998},[958,4502,4503],{"class":960,"line":1093},[958,4504,1090],{"emptyLinePlaceholder":1089},[958,4506,4507],{"class":960,"line":1098},[958,4508,1090],{"emptyLinePlaceholder":1089},[958,4510,4511,4513,4516],{"class":960,"line":1116},[958,4512,1101],{"class":994},[958,4514,4515],{"class":964}," TestTools",[958,4517,1362],{"class":998},[958,4519,4520],{"class":960,"line":1122},[958,4521,4522],{"class":968},"    \"\"\"工具单元测试\"\"\"\n",[958,4524,4525],{"class":960,"line":1127},[958,4526,1090],{"emptyLinePlaceholder":1089},[958,4528,4529,4531,4534],{"class":960,"line":1133},[958,4530,1776],{"class":994},[958,4532,4533],{"class":964}," test_calculator_basic",[958,4535,4536],{"class":998},"(self):\n",[958,4538,4539,4541,4543,4546,4549,4551,4554],{"class":960,"line":1139},[958,4540,1548],{"class":998},[958,4542,1002],{"class":994},[958,4544,4545],{"class":998}," calculator.invoke({",[958,4547,4548],{"class":968},"\"expression\"",[958,4550,1641],{"class":998},[958,4552,4553],{"class":968},"\"2 + 3\"",[958,4555,4556],{"class":998},"})\n",[958,4558,4559,4562,4565,4567],{"class":960,"line":1145},[958,4560,4561],{"class":994},"        assert",[958,4563,4564],{"class":968}," \"5\"",[958,4566,3276],{"class":994},[958,4568,4569],{"class":998}," result\n",[958,4571,4572],{"class":960,"line":1151},[958,4573,1090],{"emptyLinePlaceholder":1089},[958,4575,4576,4578,4581],{"class":960,"line":1157},[958,4577,1776],{"class":994},[958,4579,4580],{"class":964}," test_calculator_complex",[958,4582,4536],{"class":998},[958,4584,4585,4587,4589,4591,4593,4595,4598],{"class":960,"line":1170},[958,4586,1548],{"class":998},[958,4588,1002],{"class":994},[958,4590,4545],{"class":998},[958,4592,4548],{"class":968},[958,4594,1641],{"class":998},[958,4596,4597],{"class":968},"\"math.sqrt(144)\"",[958,4599,4556],{"class":998},[958,4601,4602,4604,4607,4609],{"class":960,"line":1179},[958,4603,4561],{"class":994},[958,4605,4606],{"class":968}," \"12\"",[958,4608,3276],{"class":994},[958,4610,4569],{"class":998},[958,4612,4613],{"class":960,"line":1330},[958,4614,1090],{"emptyLinePlaceholder":1089},[958,4616,4617,4619,4622],{"class":960,"line":1336},[958,4618,1776],{"class":994},[958,4620,4621],{"class":964}," test_calculator_division_by_zero",[958,4623,4536],{"class":998},[958,4625,4626,4628,4630,4632,4634,4636,4639],{"class":960,"line":1342},[958,4627,1548],{"class":998},[958,4629,1002],{"class":994},[958,4631,4545],{"class":998},[958,4633,4548],{"class":968},[958,4635,1641],{"class":998},[958,4637,4638],{"class":968},"\"1 \u002F 0\"",[958,4640,4556],{"class":998},[958,4642,4643,4645,4648,4650,4652,4654,4657,4659],{"class":960,"line":1365},[958,4644,4561],{"class":994},[958,4646,4647],{"class":968}," \"错误\"",[958,4649,3276],{"class":994},[958,4651,2208],{"class":998},[958,4653,2591],{"class":994},[958,4655,4656],{"class":968}," \"零\"",[958,4658,3276],{"class":994},[958,4660,4569],{"class":998},[958,4662,4663],{"class":960,"line":1371},[958,4664,1090],{"emptyLinePlaceholder":1089},[958,4666,4667,4669,4672],{"class":960,"line":1376},[958,4668,1776],{"class":994},[958,4670,4671],{"class":964}," test_execute_python_basic",[958,4673,4536],{"class":998},[958,4675,4676,4678,4680,4683,4686,4688,4691],{"class":960,"line":1382},[958,4677,1548],{"class":998},[958,4679,1002],{"class":994},[958,4681,4682],{"class":998}," execute_python.invoke({",[958,4684,4685],{"class":968},"\"code\"",[958,4687,1641],{"class":998},[958,4689,4690],{"class":968},"\"print(sum(range(10)))\"",[958,4692,4556],{"class":998},[958,4694,4695,4697,4700,4702],{"class":960,"line":1388},[958,4696,4561],{"class":994},[958,4698,4699],{"class":968}," \"45\"",[958,4701,3276],{"class":994},[958,4703,4569],{"class":998},[958,4705,4706],{"class":960,"line":1393},[958,4707,1090],{"emptyLinePlaceholder":1089},[958,4709,4710,4712,4715],{"class":960,"line":1399},[958,4711,1776],{"class":994},[958,4713,4714],{"class":964}," test_execute_python_timeout",[958,4716,4536],{"class":998},[958,4718,4719,4721,4723,4725,4727,4729,4732],{"class":960,"line":1405},[958,4720,1548],{"class":998},[958,4722,1002],{"class":994},[958,4724,4682],{"class":998},[958,4726,4685],{"class":968},[958,4728,1641],{"class":998},[958,4730,4731],{"class":968},"\"while True: pass\"",[958,4733,4556],{"class":998},[958,4735,4736,4738,4741,4743],{"class":960,"line":1411},[958,4737,4561],{"class":994},[958,4739,4740],{"class":968}," \"超时\"",[958,4742,3276],{"class":994},[958,4744,4569],{"class":998},[958,4746,4747],{"class":960,"line":1417},[958,4748,1090],{"emptyLinePlaceholder":1089},[958,4750,4751],{"class":960,"line":1422},[958,4752,1090],{"emptyLinePlaceholder":1089},[958,4754,4755,4757,4760],{"class":960,"line":1428},[958,4756,1101],{"class":994},[958,4758,4759],{"class":964}," TestAgentIntegration",[958,4761,1362],{"class":998},[958,4763,4764],{"class":960,"line":1439},[958,4765,4766],{"class":968},"    \"\"\"Agent 集成测试\"\"\"\n",[958,4768,4769],{"class":960,"line":1463},[958,4770,1090],{"emptyLinePlaceholder":1089},[958,4772,4773,4775,4778],{"class":960,"line":1480},[958,4774,1776],{"class":994},[958,4776,4777],{"class":964}," test_simple_question",[958,4779,4536],{"class":998},[958,4781,4782],{"class":960,"line":1486},[958,4783,4784],{"class":968},"        \"\"\"测试简单问答（不需要工具）\"\"\"\n",[958,4786,4787,4789,4791],{"class":960,"line":1503},[958,4788,1548],{"class":998},[958,4790,1002],{"class":994},[958,4792,3742],{"class":998},[958,4794,4795],{"class":960,"line":1518},[958,4796,4150],{"class":998},[958,4798,4799,4801,4803,4805,4807,4810],{"class":960,"line":1532},[958,4800,4155],{"class":968},[958,4802,3754],{"class":998},[958,4804,3757],{"class":968},[958,4806,1888],{"class":998},[958,4808,4809],{"class":968},"\"你好，介绍一下你自己\"",[958,4811,4812],{"class":998},")],\n",[958,4814,4815,4817,4819,4821],{"class":960,"line":1537},[958,4816,4167],{"class":968},[958,4818,1641],{"class":998},[958,4820,2188],{"class":1163},[958,4822,1280],{"class":998},[958,4824,4825,4827,4829],{"class":960,"line":1545},[958,4826,4178],{"class":968},[958,4828,1641],{"class":998},[958,4830,3781],{"class":1163},[958,4832,4833],{"class":960,"line":1565},[958,4834,4187],{"class":998},[958,4836,4837,4839,4841,4843,4845,4847,4849,4851,4854],{"class":960,"line":1589},[958,4838,4192],{"class":1271},[958,4840,1002],{"class":994},[958,4842,1577],{"class":998},[958,4844,3719],{"class":968},[958,4846,3722],{"class":998},[958,4848,3725],{"class":968},[958,4850,1641],{"class":998},[958,4852,4853],{"class":968},"\"test_simple\"",[958,4855,4856],{"class":998},"}}\n",[958,4858,4859],{"class":960,"line":1600},[958,4860,2858],{"class":998},[958,4862,4863,4865,4867,4870,4872,4875,4877],{"class":960,"line":1608},[958,4864,4561],{"class":994},[958,4866,2690],{"class":1163},[958,4868,4869],{"class":998},"(result[",[958,4871,2566],{"class":968},[958,4873,4874],{"class":998},"]) ",[958,4876,2823],{"class":994},[958,4878,4879],{"class":1163}," 2\n",[958,4881,4882,4884,4886,4888,4890,4892,4894,4897],{"class":960,"line":1622},[958,4883,4561],{"class":994},[958,4885,3816],{"class":998},[958,4887,2566],{"class":968},[958,4889,3821],{"class":998},[958,4891,3096],{"class":994},[958,4893,3099],{"class":1163},[958,4895,4896],{"class":998},"].content  ",[958,4898,4899],{"class":1021},"# 确保有回复\n",[958,4901,4902],{"class":960,"line":1653},[958,4903,1090],{"emptyLinePlaceholder":1089},[958,4905,4906,4908,4911],{"class":960,"line":1658},[958,4907,1776],{"class":994},[958,4909,4910],{"class":964}," test_calculator_usage",[958,4912,4536],{"class":998},[958,4914,4915],{"class":960,"line":1663},[958,4916,4917],{"class":968},"        \"\"\"测试 Agent 是否能正确使用计算器工具\"\"\"\n",[958,4919,4920,4922,4924],{"class":960,"line":1669},[958,4921,1548],{"class":998},[958,4923,1002],{"class":994},[958,4925,3742],{"class":998},[958,4927,4928],{"class":960,"line":1674},[958,4929,4150],{"class":998},[958,4931,4932,4934,4936,4938,4940,4943],{"class":960,"line":1693},[958,4933,4155],{"class":968},[958,4935,3754],{"class":998},[958,4937,3757],{"class":968},[958,4939,1888],{"class":998},[958,4941,4942],{"class":968},"\"请计算 123456 * 789\"",[958,4944,4812],{"class":998},[958,4946,4947,4949,4951,4953],{"class":960,"line":1699},[958,4948,4167],{"class":968},[958,4950,1641],{"class":998},[958,4952,2188],{"class":1163},[958,4954,1280],{"class":998},[958,4956,4957,4959,4961],{"class":960,"line":1704},[958,4958,4178],{"class":968},[958,4960,1641],{"class":998},[958,4962,3781],{"class":1163},[958,4964,4965],{"class":960,"line":1709},[958,4966,4187],{"class":998},[958,4968,4969,4971,4973,4975,4977,4979,4981,4983,4986],{"class":960,"line":1715},[958,4970,4192],{"class":1271},[958,4972,1002],{"class":994},[958,4974,1577],{"class":998},[958,4976,3719],{"class":968},[958,4978,3722],{"class":998},[958,4980,3725],{"class":968},[958,4982,1641],{"class":998},[958,4984,4985],{"class":968},"\"test_calc\"",[958,4987,4856],{"class":998},[958,4989,4990],{"class":960,"line":1720},[958,4991,2858],{"class":998},[958,4993,4994,4997,4999,5001,5003,5005,5007,5009],{"class":960,"line":1726},[958,4995,4996],{"class":998},"        reply ",[958,4998,1002],{"class":994},[958,5000,3816],{"class":998},[958,5002,2566],{"class":968},[958,5004,3821],{"class":998},[958,5006,3096],{"class":994},[958,5008,3099],{"class":1163},[958,5010,3828],{"class":998},[958,5012,5013,5015,5018,5020],{"class":960,"line":1732},[958,5014,4561],{"class":994},[958,5016,5017],{"class":968}," \"97406784\"",[958,5019,3276],{"class":994},[958,5021,5022],{"class":998}," reply\n",[958,5024,5025],{"class":960,"line":1737},[958,5026,1090],{"emptyLinePlaceholder":1089},[958,5028,5029,5031,5034],{"class":960,"line":1746},[958,5030,1776],{"class":994},[958,5032,5033],{"class":964}," test_multi_turn_memory",[958,5035,4536],{"class":998},[958,5037,5038],{"class":960,"line":1754},[958,5039,5040],{"class":968},"        \"\"\"测试多轮对话记忆\"\"\"\n",[958,5042,5043,5046,5048],{"class":960,"line":1762},[958,5044,5045],{"class":998},"        thread_id ",[958,5047,1002],{"class":994},[958,5049,5050],{"class":968}," \"test_memory\"\n",[958,5052,5053,5055,5057,5059,5061,5063,5065],{"class":960,"line":1767},[958,5054,4120],{"class":998},[958,5056,1002],{"class":994},[958,5058,3716],{"class":998},[958,5060,3719],{"class":968},[958,5062,3722],{"class":998},[958,5064,3725],{"class":968},[958,5066,3728],{"class":998},[958,5068,5069],{"class":960,"line":1773},[958,5070,1090],{"emptyLinePlaceholder":1089},[958,5072,5073],{"class":960,"line":1785},[958,5074,5075],{"class":1021},"        # 第一轮\n",[958,5077,5078],{"class":960,"line":1801},[958,5079,5080],{"class":998},"        agent.invoke(\n",[958,5082,5083],{"class":960,"line":1806},[958,5084,4150],{"class":998},[958,5086,5087,5089,5091,5093,5095,5098],{"class":960,"line":1817},[958,5088,4155],{"class":968},[958,5090,3754],{"class":998},[958,5092,3757],{"class":968},[958,5094,1888],{"class":998},[958,5096,5097],{"class":968},"\"记住这个数字：42\"",[958,5099,4812],{"class":998},[958,5101,5102,5104,5106,5108],{"class":960,"line":1824},[958,5103,4167],{"class":968},[958,5105,1641],{"class":998},[958,5107,2188],{"class":1163},[958,5109,1280],{"class":998},[958,5111,5112,5114,5116],{"class":960,"line":1836},[958,5113,4178],{"class":968},[958,5115,1641],{"class":998},[958,5117,3781],{"class":1163},[958,5119,5120],{"class":960,"line":1847},[958,5121,4187],{"class":998},[958,5123,5124,5126,5128],{"class":960,"line":1852},[958,5125,4192],{"class":1271},[958,5127,1002],{"class":994},[958,5129,3796],{"class":998},[958,5131,5132],{"class":960,"line":1861},[958,5133,2858],{"class":998},[958,5135,5136],{"class":960,"line":1867},[958,5137,1090],{"emptyLinePlaceholder":1089},[958,5139,5140],{"class":960,"line":1877},[958,5141,5142],{"class":1021},"        # 第二轮\n",[958,5144,5145,5147,5149],{"class":960,"line":1911},[958,5146,1548],{"class":998},[958,5148,1002],{"class":994},[958,5150,3742],{"class":998},[958,5152,5153],{"class":960,"line":1943},[958,5154,4150],{"class":998},[958,5156,5157,5159,5161,5163,5165,5168],{"class":960,"line":1985},[958,5158,4155],{"class":968},[958,5160,3754],{"class":998},[958,5162,3757],{"class":968},[958,5164,1888],{"class":998},[958,5166,5167],{"class":968},"\"我刚才让你记住的数字是多少？\"",[958,5169,4812],{"class":998},[958,5171,5172,5174,5176,5178],{"class":960,"line":2018},[958,5173,4167],{"class":968},[958,5175,1641],{"class":998},[958,5177,2188],{"class":1163},[958,5179,1280],{"class":998},[958,5181,5182,5184,5186],{"class":960,"line":2051},[958,5183,4178],{"class":968},[958,5185,1641],{"class":998},[958,5187,3781],{"class":1163},[958,5189,5190],{"class":960,"line":2093},[958,5191,4187],{"class":998},[958,5193,5194,5196,5198],{"class":960,"line":2126},[958,5195,4192],{"class":1271},[958,5197,1002],{"class":994},[958,5199,3796],{"class":998},[958,5201,5202],{"class":960,"line":2158},[958,5203,2858],{"class":998},[958,5205,5206,5208,5211,5213,5215,5217,5219,5221,5223],{"class":960,"line":2164},[958,5207,4561],{"class":994},[958,5209,5210],{"class":968}," \"42\"",[958,5212,3276],{"class":994},[958,5214,3816],{"class":998},[958,5216,2566],{"class":968},[958,5218,3821],{"class":998},[958,5220,3096],{"class":994},[958,5222,3099],{"class":1163},[958,5224,3828],{"class":998},[958,5226,5227],{"class":960,"line":2178},[958,5228,1090],{"emptyLinePlaceholder":1089},[958,5230,5231,5233,5236],{"class":960,"line":2183},[958,5232,1776],{"class":994},[958,5234,5235],{"class":964}," test_tool_call_limit",[958,5237,4536],{"class":998},[958,5239,5240],{"class":960,"line":2193},[958,5241,5242],{"class":968},"        \"\"\"测试工具调用上限保护\"\"\"\n",[958,5244,5245,5247,5249],{"class":960,"line":2203},[958,5246,1548],{"class":998},[958,5248,1002],{"class":994},[958,5250,3742],{"class":998},[958,5252,5253],{"class":960,"line":2222},[958,5254,4150],{"class":998},[958,5256,5257,5259,5261,5263,5265,5268],{"class":960,"line":2227},[958,5258,4155],{"class":968},[958,5260,3754],{"class":998},[958,5262,3757],{"class":968},[958,5264,1888],{"class":998},[958,5266,5267],{"class":968},"\"连续搜索10个不同的话题\"",[958,5269,4812],{"class":998},[958,5271,5272,5274,5276,5279,5281],{"class":960,"line":2238},[958,5273,4167],{"class":968},[958,5275,1641],{"class":998},[958,5277,5278],{"class":1163},"14",[958,5280,2435],{"class":998},[958,5282,5283],{"class":1021},"# 接近上限\n",[958,5285,5286,5288,5290],{"class":960,"line":2249},[958,5287,4178],{"class":968},[958,5289,1641],{"class":998},[958,5291,3781],{"class":1163},[958,5293,5294],{"class":960,"line":2258},[958,5295,4187],{"class":998},[958,5297,5298,5300,5302,5304,5306,5308,5310,5312,5315],{"class":960,"line":2267},[958,5299,4192],{"class":1271},[958,5301,1002],{"class":994},[958,5303,1577],{"class":998},[958,5305,3719],{"class":968},[958,5307,3722],{"class":998},[958,5309,3725],{"class":968},[958,5311,1641],{"class":998},[958,5313,5314],{"class":968},"\"test_limit\"",[958,5316,4856],{"class":998},[958,5318,5319],{"class":960,"line":2287},[958,5320,2858],{"class":998},[958,5322,5323],{"class":960,"line":2292},[958,5324,5325],{"class":1021},"        # Agent 应该在达到上限后停止工具调用\n",[958,5327,5328,5330,5332,5334,5336,5339],{"class":960,"line":2297},[958,5329,4561],{"class":994},[958,5331,3816],{"class":998},[958,5333,2666],{"class":968},[958,5335,1495],{"class":998},[958,5337,5338],{"class":994},"\u003C=",[958,5340,3017],{"class":1163},[896,5342,5343],{},"运行测试：",[949,5345,5347],{"className":951,"code":5346,"language":953,"meta":11,"style":11},"pytest test_agent.py -v --tb=short\n",[955,5348,5349],{"__ignoreMap":11},[958,5350,5351,5354,5357,5360],{"class":960,"line":961},[958,5352,5353],{"class":964},"pytest",[958,5355,5356],{"class":968}," test_agent.py",[958,5358,5359],{"class":1163}," -v",[958,5361,5362],{"class":1163}," --tb=short\n",[892,5364,5365],{"id":5365},"部署注意事项",[896,5367,5368],{},"将 Agent 部署到生产环境时需要关注以下几个方面：",[945,5370,5371],{"id":5371},"性能优化",[5373,5374,5375,5388],"table",{},[5376,5377,5378],"thead",{},[5379,5380,5381,5385],"tr",{},[5382,5383,5384],"th",{},"优化手段",[5382,5386,5387],{},"说明",[5389,5390,5391,5406,5416,5430],"tbody",{},[5379,5392,5393,5399],{},[5394,5395,5396],"td",{},[900,5397,5398],{},"流式输出",[5394,5400,5401,5402,5405],{},"使用 ",[955,5403,5404],{},"agent.astream()"," 实现逐 token 返回，降低用户感知延迟",[5379,5407,5408,5413],{},[5394,5409,5410],{},[900,5411,5412],{},"工具结果缓存",[5394,5414,5415],{},"对搜索结果等工具输出做短期缓存，减少重复调用",[5379,5417,5418,5423],{},[5394,5419,5420],{},[900,5421,5422],{},"异步执行",[5394,5424,5425,5426,5429],{},"使用异步版本 ",[955,5427,5428],{},"agent.ainvoke()"," 提高并发处理能力",[5379,5431,5432,5437],{},[5394,5433,5434],{},[900,5435,5436],{},"模型选择",[5394,5438,5439],{},"简单任务使用小模型（如 GPT-4o-mini），复杂任务使用大模型",[945,5441,5442],{"id":5442},"可观测性",[949,5444,5446],{"className":1047,"code":5445,"language":1049,"meta":11,"style":11},"# 添加日志和追踪\nimport logging\n\nlogger = logging.getLogger(\"agent\")\n\ndef agent_node_with_logging(state: AgentState) -> dict:\n    \"\"\"带日志的 Agent 节点\"\"\"\n    logger.info(f\"Agent 调用 | 消息数: {len(state['messages'])} | \"\n                f\"工具调用次数: {state.get('tool_call_count', 0)}\")\n\n    result = agent_node(state)\n\n    last_msg = result[\"messages\"][-1] if result[\"messages\"] else None\n    if last_msg and hasattr(last_msg, \"tool_calls\") and last_msg.tool_calls:\n        tool_names = [tc[\"name\"] for tc in last_msg.tool_calls]\n        logger.info(f\"Agent 决定调用工具: {tool_names}\")\n\n    return result\n",[955,5447,5448,5453,5460,5464,5478,5482,5495,5500,5527,5557,5561,5570,5574,5606,5629,5654,5675,5679],{"__ignoreMap":11},[958,5449,5450],{"class":960,"line":961},[958,5451,5452],{"class":1021},"# 添加日志和追踪\n",[958,5454,5455,5457],{"class":960,"line":1008},[958,5456,1067],{"class":994},[958,5458,5459],{"class":998}," logging\n",[958,5461,5462],{"class":960,"line":1073},[958,5463,1090],{"emptyLinePlaceholder":1089},[958,5465,5466,5469,5471,5474,5476],{"class":960,"line":1086},[958,5467,5468],{"class":998},"logger ",[958,5470,1002],{"class":994},[958,5472,5473],{"class":998}," logging.getLogger(",[958,5475,3390],{"class":968},[958,5477,1319],{"class":998},[958,5479,5480],{"class":960,"line":1093},[958,5481,1090],{"emptyLinePlaceholder":1089},[958,5483,5484,5486,5489,5491,5493],{"class":960,"line":1098},[958,5485,1345],{"class":994},[958,5487,5488],{"class":964}," agent_node_with_logging",[958,5490,2544],{"class":998},[958,5492,1960],{"class":1163},[958,5494,1362],{"class":998},[958,5496,5497],{"class":960,"line":1116},[958,5498,5499],{"class":968},"    \"\"\"带日志的 Agent 节点\"\"\"\n",[958,5501,5502,5505,5507,5510,5513,5516,5519,5522,5524],{"class":960,"line":1122},[958,5503,5504],{"class":998},"    logger.info(",[958,5506,2882],{"class":994},[958,5508,5509],{"class":968},"\"Agent 调用 | 消息数: ",[958,5511,5512],{"class":1163},"{len",[958,5514,5515],{"class":998},"(state[",[958,5517,5518],{"class":968},"'messages'",[958,5520,5521],{"class":998},"])",[958,5523,1583],{"class":1163},[958,5525,5526],{"class":968}," | \"\n",[958,5528,5529,5532,5535,5537,5540,5543,5545,5547,5550,5552,5555],{"class":960,"line":1127},[958,5530,5531],{"class":994},"                f",[958,5533,5534],{"class":968},"\"工具调用次数: ",[958,5536,1577],{"class":1163},[958,5538,5539],{"class":998},"state.get(",[958,5541,5542],{"class":968},"'tool_call_count'",[958,5544,1888],{"class":998},[958,5546,2188],{"class":1163},[958,5548,5549],{"class":998},")",[958,5551,1583],{"class":1163},[958,5553,5554],{"class":968},"\"",[958,5556,1319],{"class":998},[958,5558,5559],{"class":960,"line":1133},[958,5560,1090],{"emptyLinePlaceholder":1089},[958,5562,5563,5565,5567],{"class":960,"line":1139},[958,5564,3737],{"class":998},[958,5566,1002],{"class":994},[958,5568,5569],{"class":998}," agent_node(state)\n",[958,5571,5572],{"class":960,"line":1145},[958,5573,1090],{"emptyLinePlaceholder":1089},[958,5575,5576,5579,5581,5583,5585,5587,5589,5591,5593,5595,5597,5599,5601,5603],{"class":960,"line":1151},[958,5577,5578],{"class":998},"    last_msg ",[958,5580,1002],{"class":994},[958,5582,3816],{"class":998},[958,5584,2566],{"class":968},[958,5586,3821],{"class":998},[958,5588,3096],{"class":994},[958,5590,3099],{"class":1163},[958,5592,1495],{"class":998},[958,5594,2211],{"class":994},[958,5596,3816],{"class":998},[958,5598,2566],{"class":968},[958,5600,1495],{"class":998},[958,5602,2216],{"class":994},[958,5604,5605],{"class":1163}," None\n",[958,5607,5608,5610,5613,5615,5617,5620,5622,5624,5626],{"class":960,"line":1157},[958,5609,2583],{"class":994},[958,5611,5612],{"class":998}," last_msg ",[958,5614,3174],{"class":994},[958,5616,3163],{"class":1163},[958,5618,5619],{"class":998},"(last_msg, ",[958,5621,3169],{"class":968},[958,5623,2804],{"class":998},[958,5625,3174],{"class":994},[958,5627,5628],{"class":998}," last_msg.tool_calls:\n",[958,5630,5631,5634,5636,5639,5642,5644,5646,5649,5651],{"class":960,"line":1170},[958,5632,5633],{"class":998},"        tool_names ",[958,5635,1002],{"class":994},[958,5637,5638],{"class":998}," [tc[",[958,5640,5641],{"class":968},"\"name\"",[958,5643,1495],{"class":998},[958,5645,1445],{"class":994},[958,5647,5648],{"class":998}," tc ",[958,5650,1451],{"class":994},[958,5652,5653],{"class":998}," last_msg.tool_calls]\n",[958,5655,5656,5659,5661,5664,5666,5669,5671,5673],{"class":960,"line":1179},[958,5657,5658],{"class":998},"        logger.info(",[958,5660,2882],{"class":994},[958,5662,5663],{"class":968},"\"Agent 决定调用工具: ",[958,5665,1577],{"class":1163},[958,5667,5668],{"class":998},"tool_names",[958,5670,1583],{"class":1163},[958,5672,5554],{"class":968},[958,5674,1319],{"class":998},[958,5676,5677],{"class":960,"line":1330},[958,5678,1090],{"emptyLinePlaceholder":1089},[958,5680,5681,5683],{"class":960,"line":1336},[958,5682,2702],{"class":994},[958,5684,4569],{"class":998},[1186,5686,5687],{},"\nLangSmith（LangChain 团队的追踪平台）可以可视化 Agent 的每一步执行过程，包括 LLM 输入输出、工具调用参数和结果、Token 用量等。强烈建议在开发阶段集成 LangSmith 进行调试。\n",[945,5689,5690],{"id":5690},"安全防护",[905,5692,5693,5699,5705,5711],{},[908,5694,5695,5698],{},[900,5696,5697],{},"输入过滤","：对用户输入进行检查，防止 Prompt 注入攻击",[908,5700,5701,5704],{},[900,5702,5703],{},"权限控制","：不同用户可使用的工具集应有差异",[908,5706,5707,5710],{},[900,5708,5709],{},"输出审核","：对 Agent 的最终输出进行安全审查",[908,5712,5713,5716],{},[900,5714,5715],{},"速率限制","：限制单用户的 API 调用频率和 Token 消耗",[949,5718,5720],{"className":1047,"code":5719,"language":1049,"meta":11,"style":11},"# 简单的安全中间件示例\nfrom fastapi import Request\nfrom fastapi.responses import JSONResponse\nimport time\n\n# 速率限制\nrate_limits: dict[str, list[float]] = {}\n\n@app.middleware(\"http\")\nasync def rate_limit_middleware(request: Request, call_next):\n    client_ip = request.client.host\n    now = time.time()\n\n    if client_ip not in rate_limits:\n        rate_limits[client_ip] = []\n\n    # 清理过期记录（1 分钟窗口）\n    rate_limits[client_ip] = [t for t in rate_limits[client_ip] if now - t \u003C 60]\n\n    # 每分钟最多 20 次请求\n    if len(rate_limits[client_ip]) >= 20:\n        return JSONResponse(\n            status_code=429,\n            content={\"error\": \"请求过于频繁，请稍后再试\"}\n        )\n\n    rate_limits[client_ip].append(now)\n    return await call_next(request)\n",[955,5721,5722,5727,5738,5750,5757,5761,5766,5786,5790,5802,5814,5824,5834,5838,5853,5863,5867,5872,5909,5913,5918,5934,5941,5953,5971,5975,5979,5984],{"__ignoreMap":11},[958,5723,5724],{"class":960,"line":961},[958,5725,5726],{"class":1021},"# 简单的安全中间件示例\n",[958,5728,5729,5731,5733,5735],{"class":960,"line":1008},[958,5730,1061],{"class":994},[958,5732,3932],{"class":998},[958,5734,1067],{"class":994},[958,5736,5737],{"class":998}," Request\n",[958,5739,5740,5742,5745,5747],{"class":960,"line":1073},[958,5741,1061],{"class":994},[958,5743,5744],{"class":998}," fastapi.responses ",[958,5746,1067],{"class":994},[958,5748,5749],{"class":998}," JSONResponse\n",[958,5751,5752,5754],{"class":960,"line":1086},[958,5753,1067],{"class":994},[958,5755,5756],{"class":998}," time\n",[958,5758,5759],{"class":960,"line":1093},[958,5760,1090],{"emptyLinePlaceholder":1089},[958,5762,5763],{"class":960,"line":1098},[958,5764,5765],{"class":1021},"# 速率限制\n",[958,5767,5768,5771,5773,5776,5778,5781,5783],{"class":960,"line":1116},[958,5769,5770],{"class":998},"rate_limits: dict[",[958,5772,1354],{"class":1163},[958,5774,5775],{"class":998},", list[",[958,5777,1929],{"class":1163},[958,5779,5780],{"class":998},"]] ",[958,5782,1002],{"class":994},[958,5784,5785],{"class":998}," {}\n",[958,5787,5788],{"class":960,"line":1122},[958,5789,1090],{"emptyLinePlaceholder":1089},[958,5791,5792,5795,5797,5800],{"class":960,"line":1127},[958,5793,5794],{"class":964},"@app.middleware",[958,5796,1107],{"class":998},[958,5798,5799],{"class":968},"\"http\"",[958,5801,1319],{"class":998},[958,5803,5804,5806,5808,5811],{"class":960,"line":1133},[958,5805,4100],{"class":994},[958,5807,4103],{"class":994},[958,5809,5810],{"class":964}," rate_limit_middleware",[958,5812,5813],{"class":998},"(request: Request, call_next):\n",[958,5815,5816,5819,5821],{"class":960,"line":1139},[958,5817,5818],{"class":998},"    client_ip ",[958,5820,1002],{"class":994},[958,5822,5823],{"class":998}," request.client.host\n",[958,5825,5826,5829,5831],{"class":960,"line":1145},[958,5827,5828],{"class":998},"    now ",[958,5830,1002],{"class":994},[958,5832,5833],{"class":998}," time.time()\n",[958,5835,5836],{"class":960,"line":1151},[958,5837,1090],{"emptyLinePlaceholder":1089},[958,5839,5840,5842,5845,5848,5850],{"class":960,"line":1157},[958,5841,2583],{"class":994},[958,5843,5844],{"class":998}," client_ip ",[958,5846,5847],{"class":994},"not",[958,5849,3276],{"class":994},[958,5851,5852],{"class":998}," rate_limits:\n",[958,5854,5855,5858,5860],{"class":960,"line":1170},[958,5856,5857],{"class":998},"        rate_limits[client_ip] ",[958,5859,1002],{"class":994},[958,5861,5862],{"class":998}," []\n",[958,5864,5865],{"class":960,"line":1179},[958,5866,1090],{"emptyLinePlaceholder":1089},[958,5868,5869],{"class":960,"line":1330},[958,5870,5871],{"class":1021},"    # 清理过期记录（1 分钟窗口）\n",[958,5873,5874,5877,5879,5882,5884,5887,5889,5892,5894,5897,5899,5901,5904,5907],{"class":960,"line":1336},[958,5875,5876],{"class":998},"    rate_limits[client_ip] ",[958,5878,1002],{"class":994},[958,5880,5881],{"class":998}," [t ",[958,5883,1445],{"class":994},[958,5885,5886],{"class":998}," t ",[958,5888,1451],{"class":994},[958,5890,5891],{"class":998}," rate_limits[client_ip] ",[958,5893,2211],{"class":994},[958,5895,5896],{"class":998}," now ",[958,5898,3096],{"class":994},[958,5900,5886],{"class":998},[958,5902,5903],{"class":994},"\u003C",[958,5905,5906],{"class":1163}," 60",[958,5908,2569],{"class":998},[958,5910,5911],{"class":960,"line":1342},[958,5912,1090],{"emptyLinePlaceholder":1089},[958,5914,5915],{"class":960,"line":1365},[958,5916,5917],{"class":1021},"    # 每分钟最多 20 次请求\n",[958,5919,5920,5922,5924,5927,5929,5932],{"class":960,"line":1371},[958,5921,2583],{"class":994},[958,5923,2690],{"class":1163},[958,5925,5926],{"class":998},"(rate_limits[client_ip]) ",[958,5928,2823],{"class":994},[958,5930,5931],{"class":1163}," 20",[958,5933,1362],{"class":998},[958,5935,5936,5938],{"class":960,"line":1376},[958,5937,1568],{"class":994},[958,5939,5940],{"class":998}," JSONResponse(\n",[958,5942,5943,5946,5948,5951],{"class":960,"line":1382},[958,5944,5945],{"class":1271},"            status_code",[958,5947,1002],{"class":994},[958,5949,5950],{"class":1163},"429",[958,5952,1280],{"class":998},[958,5954,5955,5957,5959,5961,5964,5966,5969],{"class":960,"line":1388},[958,5956,2848],{"class":1271},[958,5958,1002],{"class":994},[958,5960,1577],{"class":998},[958,5962,5963],{"class":968},"\"error\"",[958,5965,1641],{"class":998},[958,5967,5968],{"class":968},"\"请求过于频繁，请稍后再试\"",[958,5970,4343],{"class":998},[958,5972,5973],{"class":960,"line":1393},[958,5974,2858],{"class":998},[958,5976,5977],{"class":960,"line":1399},[958,5978,1090],{"emptyLinePlaceholder":1089},[958,5980,5981],{"class":960,"line":1405},[958,5982,5983],{"class":998},"    rate_limits[client_ip].append(now)\n",[958,5985,5986,5988,5991],{"class":960,"line":1411},[958,5987,2702],{"class":994},[958,5989,5990],{"class":994}," await",[958,5992,5993],{"class":998}," call_next(request)\n",[892,5995,5996],{"id":5996},"完整代码汇总",[896,5998,5999],{},"将上述所有模块组合在一起，完整的项目代码仓库结构如下：",[949,6001,6003],{"className":951,"code":6002,"language":953,"meta":11,"style":11},"ai-agent-project\u002F\n├── agent\u002F\n│   ├── __init__.py       # from agent.graph import agent\n│   ├── state.py          # AgentState 定义\n│   ├── tools.py          # 工具定义（search, calculator, execute_python）\n│   ├── nodes.py          # 节点（agent_node, tool_node, error_handler_node）\n│   └── graph.py          # 图编排（build_agent_graph）\n├── server.py             # FastAPI 服务\n├── test_agent.py         # pytest 测试\n└── requirements.txt      # 依赖清单\n",[955,6004,6005,6010,6018,6032,6044,6056,6068,6081,6091,6100],{"__ignoreMap":11},[958,6006,6007],{"class":960,"line":961},[958,6008,6009],{"class":964},"ai-agent-project\u002F\n",[958,6011,6012,6015],{"class":960,"line":1008},[958,6013,6014],{"class":964},"├──",[958,6016,6017],{"class":968}," agent\u002F\n",[958,6019,6020,6023,6026,6029],{"class":960,"line":1073},[958,6021,6022],{"class":964},"│",[958,6024,6025],{"class":968},"   ├──",[958,6027,6028],{"class":968}," __init__.py",[958,6030,6031],{"class":1021},"       # from agent.graph import agent\n",[958,6033,6034,6036,6038,6041],{"class":960,"line":1086},[958,6035,6022],{"class":964},[958,6037,6025],{"class":968},[958,6039,6040],{"class":968}," state.py",[958,6042,6043],{"class":1021},"          # AgentState 定义\n",[958,6045,6046,6048,6050,6053],{"class":960,"line":1093},[958,6047,6022],{"class":964},[958,6049,6025],{"class":968},[958,6051,6052],{"class":968}," tools.py",[958,6054,6055],{"class":1021},"          # 工具定义（search, calculator, execute_python）\n",[958,6057,6058,6060,6062,6065],{"class":960,"line":1098},[958,6059,6022],{"class":964},[958,6061,6025],{"class":968},[958,6063,6064],{"class":968}," nodes.py",[958,6066,6067],{"class":1021},"          # 节点（agent_node, tool_node, error_handler_node）\n",[958,6069,6070,6072,6075,6078],{"class":960,"line":1116},[958,6071,6022],{"class":964},[958,6073,6074],{"class":968},"   └──",[958,6076,6077],{"class":968}," graph.py",[958,6079,6080],{"class":1021},"          # 图编排（build_agent_graph）\n",[958,6082,6083,6085,6088],{"class":960,"line":1122},[958,6084,6014],{"class":964},[958,6086,6087],{"class":968}," server.py",[958,6089,6090],{"class":1021},"             # FastAPI 服务\n",[958,6092,6093,6095,6097],{"class":960,"line":1127},[958,6094,6014],{"class":964},[958,6096,5356],{"class":968},[958,6098,6099],{"class":1021},"         # pytest 测试\n",[958,6101,6102,6105,6108],{"class":960,"line":1133},[958,6103,6104],{"class":964},"└──",[958,6106,6107],{"class":968}," requirements.txt",[958,6109,6110],{"class":1021},"      # 依赖清单\n",[896,6112,6113,6116],{},[955,6114,6115],{},"requirements.txt"," 内容：",[949,6118,6121],{"className":6119,"code":6120,"language":1035},[1033],"langgraph>=0.2.0\nlangchain-openai>=0.2.0\nlangchain-community>=0.3.0\ntavily-python>=0.5.0\nfastapi>=0.115.0\nuvicorn>=0.32.0\npytest>=8.0.0\n",[955,6122,6120],{"__ignoreMap":11},[892,6124,6125],{"id":6125},"小结",[896,6127,6128],{},"本章我们完成了一个具备实际能力的 AI Agent 系统，涵盖了从设计到部署的完整流程：",[6130,6131,6132,6138,6144,6150,6156,6161,6167],"ol",{},[908,6133,6134,6137],{},[900,6135,6136],{},"状态设计","：定义清晰的状态结构，支撑工作流数据流转",[908,6139,6140,6143],{},[900,6141,6142],{},"工具开发","：实现安全、可靠的外部工具，注意超时和异常处理",[908,6145,6146,6149],{},[900,6147,6148],{},"图编排","：使用 LangGraph 的有向图精确控制 Agent 执行流程",[908,6151,6152,6155],{},[900,6153,6154],{},"记忆管理","：通过 Checkpointer 实现多轮对话记忆",[908,6157,6158,6160],{},[900,6159,936],{},"：设置工具调用上限和错误重试机制",[908,6162,6163,6166],{},[900,6164,6165],{},"API 部署","：使用 FastAPI 发布为可调用的服务",[908,6168,6169,6172],{},[900,6170,6171],{},"测试策略","：从工具单元测试到 Agent 集成测试的分层测试方案",[896,6174,6175],{},"这个项目可以作为你构建更复杂 Agent 系统的基础。下一步可以探索的方向包括：多 Agent 协作、RAG 集成、人工审批流程（Human-in-the-Loop）、以及接入更多专业工具。",[6177,6178,6179],"style",{},"html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":11,"searchDepth":1008,"depth":1008,"links":6181},[6182,6183,6188,6189,6190,6191,6192,6193,6194,6195,6200,6201],{"id":894,"depth":1008,"text":894},{"id":943,"depth":1008,"text":943,"children":6184},[6185,6186,6187],{"id":947,"depth":1073,"text":947},{"id":984,"depth":1073,"text":984},{"id":1029,"depth":1073,"text":1029},{"id":1040,"depth":1008,"text":1041},{"id":1191,"depth":1008,"text":1192},{"id":2318,"depth":1008,"text":2319},{"id":2930,"depth":1008,"text":2931},{"id":3621,"depth":1008,"text":3622},{"id":3911,"depth":1008,"text":3912},{"id":4458,"depth":1008,"text":4459},{"id":5365,"depth":1008,"text":5365,"children":6196},[6197,6198,6199],{"id":5371,"depth":1073,"text":5371},{"id":5442,"depth":1073,"text":5442},{"id":5690,"depth":1073,"text":5690},{"id":5996,"depth":1008,"text":5996},{"id":6125,"depth":1008,"text":6125},"md",{},{"title":26,"description":27},"ai\u002Fagent\u002Fpractice","qhzhAt2g3OGUBpmXU3wR3AlAQHpP28NnkoUk-ynU1qE",1775474636015]