• 首页
  • 自荐墙
  • 消息
  • 我的主页
    • 团队地图
    • 职位列表
    • 报刊亭
    • 我的
立即登录
设置
隐私政策用户协议营业执照人力资源服务许可证浙B2-20260388浙ICP备2024101243号浙公网安备33011002018106号© 2024-2026 Bonjour! 数字名片. All rights reserved.
特别活动Bonjour 自荐墙全新上线!
热门分区社区用户在这里聚集🤠JevOnce you jev, you never go back !!!💨NowBuildingMake some shit(草稿、半成品、small win/fail 等过程碎片),build in community.🤖Access to Tools分享值得使用和关注的有用工具🧑‍🔧Side Project从灵光乍现,到正式上线💻宝藏网站们交换吧!那些私藏在你收藏夹里的有趣网站们👀☕️Coffee Chat从一杯咖啡开始,连接同频共振的 Builder & Founder🏄🏻‍♂️! Event分享活动的信息、喜悦与感受😎寻求合作Find your partner, build your dream.🙋Ask Me Anything分享路径,开放提问🙋Ask You Anything社区的伙伴会来帮助你的!🤡Builder meme交换 Builder 的「社交硬通货」👾​Hackathon 组队找到和你一起通宵创造的队友⚒️创造、思考,或一切Think Different📺Hello World!printf("Hello, World!");🧑‍🔧Build with AI我已经学会用AI 🙋 来讨论「怎么真正用好AI」吧!不过... 好奇心和持续追问,比技巧更重要
社区找工
消息我
leo

全栈是逼出来的,不是选的。

这两年一直致力于公司的Agent开发。总结了一些思考和解决方案,今天给大家分享下,也是给自己的一个复盘。 做 Agent 最有意思的是,通常是 Demo 刚跑通的那一下:模型理解了需求,自己选了工具,还给出了看起来不错的结果。 真正让人头疼的是,是第二个阶段。 用户换了一种问法,状态丢了;工具返回一个含糊的错误,Agent 开始乱试;你改了提示词,一个场景好了,另一个场景坏了;上线第二天,账单和延迟一起超预期。 所以,Agent Demo 和 Agent 系统之间,隔着一层工程问题。这就是通常所说的工程化解决方案 这层问题不是换框架能解决的。框架解决的是调用模型、注册工具、组织消息这些胶水层;真正难的是下面五件事: 状态怎么传? 工具怎么约束? 错误怎么恢复? 效果怎么回归? 延迟和成本怎么控? LangChain、LangGraph、Spring AI、AgentScope 或自研循环,这些都是我们去做一个agent的基本框架,工程化的问题,他们依然存在。 可能这里有人嘴犟了,推出市面上一些比较好的开源框架 比如字节的deerflow,这个我们公司也在用,的确把一些基本上工程问题化解掉了,但就像你做springboot、cloud开发一样。你要知道怎么解决实际业务问题,实际的业务开发才是硬道理。 今天这篇文章带你从难点展开。每一节都先讲麻烦在哪里,再给可落地的解法。不管你是否搞过agent开发,都能明白。(多说一句,不用在乎什么语言,不管是Python、亦或是Spring ai生态,其实都是一致的,我们需要掌握的是解决思路与方案) 一、上下文:难在状态会丢 首当其冲肯定是上下文,上下文的难点:不是写 Prompt; 假设我们要做一个“汽车门店销量助手”。 用户先问:“帮我看看上海上周的销量。” 下一句又问:“那杭州呢?” 人一听就懂:用户要查“杭州上周的销量”,继承“上周”和“销量”,只把“上海”换成“杭州”。 但如果系统只把最后一句“那杭州呢?”传给模型,模型就不知道该查销量,更不知道时间范围是上周。 很多人的第一反应是把更多资料塞给模型。这样做短期有效,长期会引入三个问题: 状态丢失:用户说“换成上周”“再看另一个对象”,关键信息在历史对话里,不在当前输入里。 状态冲突:系统提示说只能查最近 30 天,历史里还有用户之前查过的去年数据。 重点稀释:工具返回、检索片段、执行日志全部塞进上下文,模型反而找不到关键约束。 Agent 的上下文不是一个聊天记录列表,而是一个随任务推进不断变化的运行时状态。 正确做法类似 Java 里的领域对象:维护一个明确的查询状态。 class SalesQueryState { private String city = "上海"; private LocalDate startDate = LocalDate.of(2026, 9, 14); private LocalDate endDate = LocalDate.of(2026, 9, 20); private String metric = "sales"; } 用户说“那杭州呢”,只更新一个字段: state.setCity("杭州"); 不要把整个对象重建,也不要让模型随意覆盖所有字段。 一个实用的上下文结构是: State 当前任务状态:城市、时间、指标 Evidence 本次回答需要的证据:销量数据、报表片段 History 最近几轮对话,用来理解“那杭州呢” 一句话:程序维护状态,模型解析意图。 落成流程就是四步: 模型读取最近对话 + 当前状态,输出结构化意图 程序校验城市、日期、指标是否合法 只合并允许变化的字段 用新状态去查数据 对应 Java 大概是: QueryIntent intent = llm.parseIntent(history, state, "那杭州呢?"); if (!cityRepository.exists(intent.city())) { throw new InvalidCityException(intent.city()); } state.apply(intent); // 只改 city,保留时间和指标 二、工具:难在把概率系统接到确定性接口上 工具调用看起来只是让模型调用函数,麻烦在失败场景。 比如模型调用: querySales("魔都", lastWeek); 系统可能遇到: 魔都不是标准城市名 日期算错 查询结果为空 接口超时 用户没有权限 如果工具只返回字符串: String querySales(String city, DateRange range); 模型分不清空结果、参数错、超时和权限问题,只能靠猜。 比如可以像设计 Java 接口一样设计工具契约: record ToolResult( Status status, Map<String, Object> data, String message, boolean retryable, String nextAction ) {} enum Status { SUCCESS, EMPTY, INVALID_ARGUMENT, PERMISSION_DENIED, TIMEOUT } 城市名错误时返回: new ToolResult( INVALID_ARGUMENT, Map.of(), "city 不是标准城市名", true, "调用 resolveCity,把魔都解析成上海" ); 这样程序知道能不能重试,模型知道下一步该做什么。 工具层还要做三件事: 参数校验:城市、日期、范围在工具层强制检查 幂等:下单、发消息、删除数据必须带幂等键 权限分级:只读、可逆写、不可逆写分开处理 一句话:不要指望 Prompt 保证安全,接口必须能挡住错误。 写工具时可以直接套这个模板: ToolResult querySales(String city, DateRange range, User user) { if (!permissionService.canReadSales(user, city)) { return ToolResult.denied("无权限查看该城市销量"); } String normalizedCity = cityService.normalize(city); if (normalizedCity == null) { return ToolResult.invalidArgument( "城市名不标准", "请先调用 resolveCity" ); } if (!dateService.isValidSalesRange(range)) { return ToolResult.invalidArgument("日期超出可查范围", "请改成最近90天"); } List<SalesRow> rows = salesRepository.findByCityAndRange(normalizedCity, range); if (rows.isEmpty()) { return ToolResult.empty("查询成功但没有数据", "不要编造趋势"); } return ToolResult.success(rows); } 重点是顺序不能乱: 先权限,再参数,再查询,最后处理空结果和异常。 三、执行:难在 Agent 越跑越偏 没有边界的 Agent 很容易这样跑: 第 1 步:查上海销量 第 2 步:查杭州销量 第 3 步:发现杭州门店数据异常 第 4 步:跑去查库存 第 5 步:又查了一遍上海 第 6 步:宣布任务完成 用户只是想比较上海和杭州上周销量,Agent 却发散到库存,还重复查询。 解决方案不是让模型“更认真”,而是给它一个有限任务图: ClarifyIntent 明确城市、时间、指标 ResolveCity 处理城市别名 FetchSales 查询销量 Analyze 对比结果 GenerateResult 生成回答 ConfirmResult 验收 类似 Java 状态机: if (state == RESOLVE_CITY && cityValid) { state = FETCH_SALES; } 再加上预算: 最多 2 次销量查询 最多 1 次城市解析 最多 1 次重试 总耗时不超过 10 秒 最后由程序验收: boolean acceptable = result.contains("上海") && result.contains("杭州") && result.timeRangeIs(lastWeek) && result.referencesToolData(); 不要相信“模型说完成”。 一句话:模型负责决策建议,程序负责边界和验收。 可以直接落成三个对象。 第一,节点定义: enum Node { CLARIFY_INTENT, RESOLVE_CITY, FETCH_SALES, ANALYZE, GENERATE_RESULT, CONFIRM_RESULT } 第二,预算控制: class TaskBudget { private int llmCalls; private int toolCalls; private int retries; boolean allowToolCall() { return toolCalls < 5; } boolean allowRetry() { return retries < 2; } } 第三,验收器: class SalesResultValidator { boolean validate(SalesQueryState state, AgentResult result) { return result.cities().equals(state.cities()) && result.range().equals(state.range()) && result.metric().equals(state.metric()) && result.allNumbersHaveSource(); } } 如果验收失败: 第一次:把失败原因交给模型修复 第二次:降低任务范围 第三次:转人工或返回失败原因 四、评估:难在不知道错在哪一步 Agent 最后回答: 杭州销量比上海高 18%。 这个结论可能错在四层: 意图层:用户问门店销量,Agent 查了城市总量 工具层:只查了杭州,没查上海 数据层:把上周日期算错 生成层:数据是 8%,写成 18% 只看最终答案,无法定位问题。 所以要分层评估: [ { "id": "followup-city", "history": ["帮我

9天前

登录并发表评论

评论 · 0