资讯动态

用Agent工作流设计资讯日报生成器:模型、工具与规则编排实战

发布时间:2026/9/2 9:53:14 来源:尧图企业网站定制
做资讯日报生成器的时候很多人第一反应是这不就是“爬虫 调用大模型接口写摘要”吗如果真有这么简单Agent 架构师这个岗位也不会有存在的必要了。实际上一个能稳定运行的资讯日报生成器恰恰是理解“模型参与的复杂工作流”最好的练手项目。它没有电商客服那么重的业务规则也没有复杂系统那么大的并发压力但它完整覆盖了 Agent 工作流设计中最核心的三个问题模型如何调用工具、模型如何做决策、多个模型如何协同编排。这篇实战课会从架构设计开始讲起而不是直接给代码。因为日报生成器的代码本身并不难写难的是你想清楚哪些步骤用模型哪些步骤用规则模型跑偏了怎么兜底多源信息怎么去重和融合。这些问题想明白了代码是水到渠成的事。读完这篇文章你可以获得四样东西一个可运行的资讯日报生成器工程骨架一套“模型 工具 编排”的 Agent 工作流设计方法让模型在长流程任务中保持稳定输出的工程化手段从单体脚本走向 Agent 系统的重构思路。1. 这篇文章真正要解决的问题1.1 看起来简单做起来容易翻车的项目资讯日报生成器表面需求就是三句话定时去几个新闻源抓内容让大模型读完生成摘要把摘要整理成日报发出去。但真正落到工程上问题会一个接一个冒出来有的新闻源反爬抓回来的是登录页有的新闻源返回的是乱码或空正文模型把摘要写得像广告文案多个源重复入库同一个新闻被生成三遍某次模型调用超时整个流程卡住模型把 A 新闻的分类标到 B 新闻上日报格式一会儿用的 Markdown一会儿用的纯文本。这些问题的本质是什么是一个简单的“爬虫 模型调用”脚本无法应对真实业务中的信息不确定性。模型天生有随机性外部数据源天生不稳定这两者叠加在一起靠传统编程的“if-else”是兜不住的。1.2 这篇文章的定位这不是一篇教你调用某一个模型 API 的教程。市面上此类教程已经很多了写来写去都是“如何调用接口、如何写 Prompt”。这篇实战课的核心是把资讯日报生成器当成一个Agent 工作流系统来设计。你会用到模型的能力但不会盲目依赖模型你会设计工作流但不会让工作流变成死板的流水线。具体来说你会学会如何把“读取 RSS”“抓取正文”“生成摘要”“分类打标签”“排版日报”拆成独立工具如何让模型在关键节点做质量判断而不是从头生成到尾如何用“规则 模型 校验”的混合架构提升稳定性如何把错误处理嵌入到工作流中而不是靠事后补救。1.3 适合谁读正在做 AI Agent 开发但只知道调用模型接口、不清楚如何设计复杂工作的开发者用过 Coze、Dify、n8n 等低代码工作流平台想理解底层原理的人有 Python 后端经验希望往 Agent 架构方向进阶的工程师准备系统架构师考试或面试想拿一个真实案例说明“模型应用系统”如何设计的人。2. 核心概念模型参与的复杂工作流到底复杂在哪2.1 先给 Agent 工作流下一个精确的定义很多人把 Agent 简单理解为“能自己思考的 AI”。这个理解没有错但不够工程化。在系统设计视角下一个 Agent 工作流应该定义为模型节点 工具节点 路由规则 状态管理 异常处理按一定拓扑结构组合而成的自动化任务系统。注意模型只是其中的一个节点不是全部。对比一下就清楚了。传统脚本的流程是采集数据 - 写死逻辑处理 - 输出结果Agent 工作流的流程是采集数据 - 规则清洗 - 模型理解 - 模型判断 - 工具执行 - 校验 - 模型优化 - 输出结果关键差异在于模型在做判断和生成工具在做确定性的执行规则在做边界控制。2.2 模型参与工作流的三种模式在很多 Agent 项目里模型有三种参与方式术语上可以这么分模式英文特点适用场景调用模式Callable输入输出都是结构化数据模型只做转换摘要、翻译、改写、信息抽取决策模式Decision模型从多个选项中选择一个或决定下一步动作分类、路由、内容质量判断、是否需要补充信息编排模式Orchestrator模型负责拆分任务并调度其他工具或模型复杂任务规划、多步骤工作流资讯日报生成器恰好三种模式都会用到摘要生成是典型的调用模式新闻分类、质量筛选是决策模式最终的“根据日报主题选择哪些文章、如何编排”是轻量的编排模式。这也是为什么这个项目适合用来做实战。它不是只让模型“生成一篇日报”而是让模型在多处参与、各干各擅长的活。2.3 规则与模型的边界在哪里这是 Agent 架构师最核心的工程判断。一个普遍的设计原则是能用规则解决的就不要用模型。模型只负责开放性问题规则负责封闭性问题。封闭性问题指的是答案有限、可枚举、可逻辑判断的。比如说“这个 RSS 条目是否在 24 小时内”“这篇文章是否和昨天已发布的文章重复”这些都是规则问题不需要模型。开放性问题指的是需要理解语义、归纳、组织语言的。比如说“这篇文章的核心观点是什么”“给这篇文章打一个分类标签”“用什么标题适合今天的日报日报主题”这些都是模型任务。这个边界画得越清楚你的系统就越稳定成本也越低。3. 资讯日报生成器的整体架构设计3.1 目标场景设定为了让文章有具体的落点我们设定这样一个业务场景输入一组 RSS 新闻源例如 5 到 10 个技术类博客或资讯站点运行频率每天早上 8 点自动执行一次输出一份 Markdown 格式的资讯日报包含日期、分类、新闻摘要列表附加要求去除重复内容过滤低质量内容控制日报长度。3.2 架构分层整个系统分成五层第一层源管理维护 RSS 源的配置列表。每个源有名称、URL、分类标签、抓取开关。这一层不涉及模型纯粹是配置管理。第二层采集与解析负责下载 RSS 文件解析出条目title、link、published、summary再进一步抓取正文。这里要处理网络超时、编码问题、反爬策略。第三层内容清洗与去重用规则做第一轮清洗过滤掉发布时间过旧的、标题或链接重复的。这部分用简单的字符串匹配和集合操作就能完成。第四层模型理解与决策这是模型参与的核心层摘要模型将正文或摘要压缩成 100 字以内的短摘要分类模型给每个条目打上“AI / 前端 / 后端 / 运维 / 业界动态”等标签质量模型对文章价值做 1 到 5 分打分低于阈值则丢弃。第五层编排与输出根据分类聚合所有通过筛选的条目生成最后的日报 Markdown。这一步可以用模板拼接也可以让模型参与生成“今日导读”部分。3.3 关键技术决策在这个架构里有几个决策必须在写代码之前定下来决策一摘要用抽取式还是生成式抽取式摘要从原文中抽取关键句稳定但生硬生成式摘要由模型重写自然但有“幻觉”风险。稳妥的做法是让模型先抽取三到五个关键句再基于关键句生成通顺摘要。这样既保留原文事实又有可读性。决策二分类用模型还是规则如果新闻源已经自带了分类标签用规则映射就足够了。只有面对无标签的源或需要统一分类体系时才需要模型参与。实战中我们选择统一走模型分类因为 RSS 源自带的分类往往是英文或完全不统一。决策三日报是模板拼接还是模型生成日报的整体排版用模板拼接导读部分用模型生成。模板保证格式稳定模型保证“人性化表达”。这是一个混合方案。如果完全让模型生成整个日报每次输出的格式和结构都可能漂移这在工程上是个大坑。4. 环境准备与前置条件4.1 运行环境本文示例使用 Python版本建议 3.9 及以上。操作系统不限Windows、macOS、Linux 都可以。为方便演示代码可以在本地直接运行。需要安装的依赖如下pip install requests beautifulsoup4 feedparser这三个库的职责requests发送 HTTP 请求获取 RSS 内容和网页正文beautifulsoup4解析 HTML提取正文内容feedparser解析 RSS/Atom 订阅源。如果你使用虚拟环境建议先创建并激活python3 -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate4.2 模型服务配置模型调用部分我们采用“可替换设计”。代码里抽象出一个LlmClient接口具体实现你可以对接任意厂商的模型服务也可以接入本地部署的模型。这里不应该写死任何厂商 SDK原因有两点不同公司实际使用的模型服务不一样写死 SDK 会让代码失去通用性Agent 架构师考虑的是“接入层不变模型可替换”这是生产系统的常态。文章示例中会提供一个FakeLlmClient和一个HttpLlmClient。前者用于本地快速验证工作流不依赖真实模型后者是标准的 HTTP 调用模板。4.3 目录结构规划开始编码前先规划好工程结构news_digest/ ├── config.py # 配置项RSS源、文件路径 ├── models.py # 数据类定义NewsItem ├── sources.py # RSS源管理 ├── fetcher.py # 内容采集与正文提取 ├── processor.py # 清洗、去重、过滤 ├── llm.py # 模型客户端抽象与实现 ├── workflow.py # 工作流编排主逻辑 ├── report.py # 日报生成与输出 └── main.py # 入口这个结构本身就是一个清晰分层。理解它比记住任何框架 API 都重要。5. 完整示例代码实现下面进入实战环节。我会按照工作流的顺序从数据模型开始逐步搭建。5.1 数据模型定义文件路径news_digest/models.pyfrom dataclasses import dataclass, field from datetime import datetime dataclass class Source: RSS源配置 name: str url: str category: str enabled: bool True dataclass class RawItem: 从RSS解析出的原始条目 title: str link: str published: str summary: str source: str dataclass class NewsItem: 经过清洗和模型处理后的标准新闻条目 title: str link: str summary: str category: str 未分类 source: str score: float 3.0 published: str raw_title: str keywords: list field(default_factorylist)这里把RawItem和NewsItem分开是为了区分“从外部拿到的原始数据”和“经过工作流处理后的标准数据”。这是数据流设计中很实用的一招不要让外部数据结构污染你的核心业务逻辑。5.2 模型客户端抽象文件路径news_digest/llm.pyimport json from abc import ABC, abstractmethod class LlmClient(ABC): 模型客户端抽象接口 abstractmethod def chat(self, system_prompt: str, user_prompt: str) - str: 返回模型生成的文本 class FakeLlmClient(LlmClient): 本地模拟客户端。 不调用真实模型用于在没有API Key时验证工作流。 def chat(self, system_prompt: str, user_prompt: str) - str: # 简单分析用户输入中的关键词 if 摘要 in user_prompt: return 这是一个本地模拟生成的摘要用于验证工作流。实际使用时请替换为真实模型服务。 if 分类 in user_prompt: return AI if 质量评分 in user_prompt: return 4 return 模拟输出 class HttpLlmClient(LlmClient): 标准HTTP调用模板。 这里用通用的openai兼容接口为例实际使用时改成你自己的模型服务地址。 def __init__(self, base_url: str, api_key: str, model: str): self.base_url base_url self.api_key api_key self.model model def chat(self, system_prompt: str, user_prompt: str) - str: import requests url f{self.base_url}/chat/completions headers { Authorization: fBearer {self.api_key}, Content-Type: application/json, } payload { model: self.model, messages: [ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature: 0.3, } resp requests.post(url, headersheaders, jsonpayload, timeout30) resp.raise_for_status() data resp.json() return data[choices][0][message][content]这段代码体现了 Agent 架构中的一个重要原则面向接口编程不面向具体模型编程。你的业务逻辑不关心底层是 GPT、文心、通义还是本地开源模型它只依赖LlmClient的chat方法。5.3 RSS 源管理与内容采集文件路径news_digest/sources.pyimport feedparser from models import Source, RawItem def load_sources() - list: 加载RSS源配置。实际项目中可以从配置文件或数据库读取。 return [ Source(name示例科技博客A, urlhttps://example.com/rss.xml, categoryAI), Source(name示例开发者社区B, urlhttps://example.org/feed, category后端), ] def fetch_rss_items(source: Source) - list: 从RSS源获取原始条目。 这里只做网络请求和RSS解析不做业务判断。 try: feed feedparser.parse(source.url) items [] for entry in feed.entries: item RawItem( titleentry.get(title, ), linkentry.get(link, ), publishedentry.get(published, ), summaryentry.get(summary, ), sourcesource.name, ) items.append(item) return items except Exception as e: # 记录日志而不是直接崩溃这是工作流的容错基础 print(f[ERROR] 抓取RSS失败: {source.name} - {e}) return []注意这里的异常处理设计单个源失败不应该影响整个工作流。这在实际项目中是必须的因为外部源不稳定的概率远高于你的代码出错的概率。5.4 正文抓取与清洗文件路径news_digest/fetcher.pyimport requests from bs4 import BeautifulSoup def fetch_article_content(url: str, max_chars: int 3000) - str: 抓取文章正文并截断到最大长度。 考虑到模型输入的成本和性能这里把内容限制在3000字符以内。 headers { User-Agent: Mozilla/5.0 (compatible; NewsDigestAgent/1.0) } try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) # 移除 script 和 style 标签减少噪声 for tag in soup([script, style, nav, footer]): tag.decompose() text soup.get_text(separator\n, stripTrue) return text[:max_chars] except Exception as e: print(f[ERROR] 抓取正文失败: {url} - {e}) return def clean_text(text: str) - str: 基础清洗去除多余空行和空白字符 lines [line.strip() for line in text.splitlines() if line.strip()] return \n.join(lines)正文抓取是这个项目里最容易翻车的环节之一。不同网站 HTML 结构差异巨大get_text()抽取出来的内容可能包含大量导航和广告文本。这里做了一个通用处理在真实项目中你通常需要针对高频源单独写正文提取规则。5.5 工作流编排主逻辑文件路径news_digest/workflow.py这是整个项目的核心。它把前面的工具函数、模型客户端串成一个完整的工作流。from models import NewsItem from sources import load_sources, fetch_rss_items from fetcher import fetch_article_content, clean_text class NewsDigestWorkflow: 资讯日报生成工作流。 整个流程分为五步 1. 加载源并抓取RSS条目 2. 跳过低质量条目规则过滤 3. 抓取正文并预处理 4. 模型处理摘要/分类/评分 5. 输出标准化NewsItem列表 def __init__(self, llm_client): self.llm llm_client def _rule_filter(self, items: list, max_items_per_source: int 20) - list: 规则过滤以最小代价筛掉明显无用的内容。 这里不调用模型因为规则判断的速度和成本都远优于模型。 seen_titles set() result [] for item in items: title item.title.strip() # 基本检查标题不可为空 if not title: continue # 重复标题检查简化版实际可用SimHash或向量相似度 if title in seen_titles: continue seen_titles.add(title) # 长度为0或过短的内容跳过 if len(title) 5: continue result.append(item) if len(result) max_items_per_source: break return result def _model_summarize(self, item: NewsItem, content: str) - str: 用模型生成摘要 system_prompt ( 你是一个资深编辑。请用不超过120字的中文概括一篇技术文章的核心内容。 要求保留关键信息语言通顺不要使用本文介绍了这类套话。 ) user_prompt f文章标题{item.title}\n文章正文截取\n{content}\n\n请输出摘要 try: summary self.llm.chat(system_prompt, user_prompt).strip() return summary except Exception as e: print(f[WARN] 摘要生成失败使用原文摘要兜底: {e}) return item.raw_title def _model_classify(self, item: NewsItem, content: str) - str: 用模型判断分类 system_prompt ( 请将一篇技术文章分类到以下类别之一AI前端后端运维数据库业界动态。 只输出一个类别词不要输出解释。 ) user_prompt f文章标题{item.title}\n正文片段{content[:500]}\n\n分类 try: category self.llm.chat(system_prompt, user_prompt).strip() if category not in [AI, 前端, 后端, 运维, 数据库, 业界动态]: return 业界动态 return category except Exception as e: print(f[WARN] 分类失败使用默认分类: {e}) return 业界动态 def _model_score(self, item: NewsItem) - float: 用模型给内容质量打分分数范围1-5 system_prompt ( 你是严格的内容编辑。请为一篇技术新闻打分分数1到5整数。 5分表示对该领域从业者非常有价值1分表示价值很低。只输出数字。 ) user_prompt f标题{item.title}\n摘要{item.summary[:200]}\n分数 try: score_text self.llm.chat(system_prompt, user_prompt).strip() score float(score_text) return max(1.0, min(5.0, score)) except Exception: return 3.0 def run(self) - list: 执行整个工作流返回处理后的NewsItem列表 all_items [] sources load_sources() # 1. 采集所有源 print([1/5] 开始抓取RSS源...) for source in sources: if not source.enabled: continue raw_items fetch_rss_items(source) print(f - {source.name}: 获取 {len(raw_items)} 条) all_items.extend(raw_items) # 2. 规则过滤 print([2/5] 规则过滤去重...) filtered_items self._rule_filter(all_items) print(f - 过滤后剩余: {len(filtered_items)} 条) # 3. 抓取正文 print([3/5] 抓取正文并清洗...) news_items [] for raw in filtered_items: content fetch_article_content(raw.link) content clean_text(content) if not content: continue news_item NewsItem( titleraw.title, linkraw.link, raw_titleraw.title, sourceraw.source, publishedraw.published, summaryraw.summary, ) news_items.append((news_item, content)) print(f - 正文抓取成功: {len(news_items)} 条) # 4. 模型处理 print([4/5] 模型摘要/分类/评分...) processed_items [] for news_item, content in news_items: news_item.summary self._model_summarize(news_item, content) news_item.category self._model_classify(news_item, content) news_item.score self._model_score(news_item) processed_items.append(news_item) # 5. 质量阈值过滤 print([5/5] 质量过滤并输出...) final_items [item for item in processed_items if item.score 3.0] final_items.sort(keylambda x: x.score, reverseTrue) return final_items5.6 日报生成与输出文件路径news_digest/report.pyfrom datetime import date from collections import defaultdict def generate_markdown_report(items: list) - str: 将处理后的新闻条目组织成Markdown日报。 模板固定保证输出格式稳定。 if not items: return # 今日资讯日报\n\n今日暂无符合条件的资讯。\n today date.today().isoformat() lines [f# 今日资讯日报 - {today}, ] # 按分类聚合 categorized defaultdict(list) for item in items: categorized[item.category].append(item) # 每个分类一个小节 for category, category_items in categorized.items(): lines.append(f## {category}) lines.append() for idx, item in enumerate(category_items, 1): lines.append(f### {idx}. {item.title}) lines.append() lines.append(f 来源{item.source} | 评分{item.score:.1f}) lines.append() lines.append(item.summary) lines.append() lines.append(f[阅读原文]({item.link})) lines.append() lines.append(---) lines.append(*本日报由 Agent 工作流自动生成如有疑问请联系管理员。*) lines.append() return \n.join(lines) def save_report(markdown_text: str, filename: str None) - str: 保存日报到文件返回文件路径 if filename is None: filename freport_{date.today().isoformat()}.md with open(filename, w, encodingutf-8) as f: f.write(markdown_text) return filename5.7 程序入口文件路径news_digest/main.pyfrom llm import FakeLlmClient, HttpLlmClient from workflow import NewsDigestWorkflow from report import generate_markdown_report, save_report def main(): # 本地验证使用 FakeLlmClient # 真实使用请替换为 HttpLlmClient并填入模型服务地址等信息 llm FakeLlmClient() # 如果使用真实模型服务参考下面的方式初始化 # llm HttpLlmClient( # base_urlhttp://your-llm-service:8000, # api_keysk-xxx, # modelyour-model-name # ) workflow NewsDigestWorkflow(llm_clientllm) print( * 50) print(资讯日报生成工作流开始) print( * 50) items workflow.run() print(f\n最终通过筛选的新闻: {len(items)} 条) for item in items: print(f [{item.category}] {item.title} ({item.score:.1f}分)) report_md generate_markdown_report(items) filename save_report(report_md) print(f\n日报已生成: {filename}) if __name__ __main__: main()6. 运行结果与效果验证6.1 运行命令在项目根目录执行cd news_digest python main.py6.2 预期输出使用FakeLlmClient时输出大致如下 资讯日报生成工作流开始 [1/5] 开始抓取RSS源... - 示例科技博客A: 获取 5 条 - 示例开发者社区B: 获取 6 条 [2/5] 规则过滤去重... - 过滤后剩余: 10 条 [3/5] 抓取正文并清洗... - 正文抓取成功: 8 条 [4/5] 模型摘要/分类/评分... [5/5] 质量过滤并输出... 最终通过筛选的新闻: 8 条 [AI] 示例文章标题一 (4.0分) [后端] 示例文章标题二 (4.0分) [AI] 示例文章标题三 (3.5分) ... 日报已生成: report_2025-01-01.md注意由于FakeLlmClient输出的是写死的模拟文本实际运行结果中所有摘要、分类都会是模拟值。这个模拟客户端的作用是验证工作流本身能不能跑通而不是验证模型效果。6.3 如何判断工作流是否成功判断标准有三条流程完整控制台输出五个步骤最后生成日报文件异常隔离即使某些 RSS 源抓取失败流程能继续跑完输出结构正确生成的 Markdown 文件包含标题、日期、分类小节、摘要和原文链接。6.4 切换为真实模型的验证方式把main.py中的FakeLlmClient替换为HttpLlmClient填好模型服务地址和模型名重新运行。此时应重点观察两个现象摘要内容是否仍然准确、简洁。如果模型把摘要写成了“这篇文章很棒”“值得一读”这类空洞表达说明 Prompt 需要加强约束分类是否合理。如果大量条目被分到“业界动态”说明分类 Prompt 或者类别定义需要细化。真实模型的调优不是一个“一跑就完美”的过程而是要反复看输出、改 Prompt、加示例这是 Agent 开发中非常正常的迭代节奏。7. 常见问题与排查方法问题现象可能原因排查方式解决方案RSS 源抓取不到内容网络不通、RSS 地址变更、反爬限制先用curl或浏览器直接访问RSS地址确认是否能返回XML更换RSS地址添加User-Agent请求头更换代理或调整请求频率正文抓取结果为空目标网站需要登录、正文是JS动态加载打开浏览器开发者工具查看页面HTML中是否包含正文增加登录态Cookie使用无头浏览器渲染如 Playwright模型摘要出现幻觉Prompt 约束不够或模型能力有限对比原文和摘要检查是否出现原文没有的信息在 Prompt 中强调“只能基于给定文本概括不得添加原文没有的信息”降低 temperature摘要内容为“模板套话”Prompt 中示例不足检查多个条目的摘要输出是否雷同在 Prompt 中加入正反示例few-shot大量内容被分类为默认类分类 Prompt 的类别边界不清统计分类结果分布增加每个类别的定义和示例调低默认兜底倾向工作流运行到一半卡住模型调用超时、网络不稳定查看日志中阻塞点检查模型服务日志为 HTTP 调用增加超时和重试机制如指数退避日报里的内容重复不同源转载同一篇文章标题不同用正文相似度计算重复率引入 SimHash 或向量相似度去重替代简单标题去重成本过高每个条目都做多次模型调用查看日志统计每个条目消耗的 token在规则过滤阶段更激进减少进入模型阶段的条目对低价值源降频模型分类结果不稳定模型随机性导致多次运行同一批数据观察分类变化将 temperature 设为 0使用结构化输出函数调用约束结果格式这些问题的本质很多不是“模型不够聪明”而是工作流的边界没设计好。规则层应该挡掉的内容没有完全挡住模型层应该稳定的地方没有给足约束。8. 最佳实践与工程建议8.1 始终保留“降级方案”Agent 工作流和传统系统的最大区别在于模型会失败、会超时、会返回非法格式。因此每个模型节点都要有自己的降级策略。摘要失败用 RSS 自带的 summary 或原文首段分类失败用默认分类“业界动态”评分失败给一个中性分数 3.0整个模型服务不可用跳过模型处理阶段直接输出规则过滤后的标题列表。降级不是为了掩盖错误而是保证系统在异常情况下仍然有最低限度的产出。8.2 模型节点要“小”而“专”不要设计一个“超级模型节点”一次性完成摘要、分类、评分。原因有三个单一任务的效果更好Prompt 更清晰任何一个任务失败不会影响其他结果后续维护时可以单独调优某一个任务。资讯日报生成器里摘要、分类、评分是三个独立节点这是刻意为之。实际生产系统中建议更进一步为每个任务保存独立的日志方便回溯是哪一步出了问题。8.3 用规则护住模型的边界我反复强调规则和模型的边界是因为这是 Agent 系统稳定性的核心。具体到这个项目规则负责RSS解析、编码处理、正文抽取、标题去重、文本长度控制、输出模板模型负责摘要、分类、质量打分、导读生成。模型只处理“语义层面”的任务其他所有确定性的任务都交给代码。这样你的系统不会因为模型输出的一点点波动导致整个链路崩掉。8.4 结构化输出优于自然语言输出让模型输出 JSON 格式的摘要、分类和评分是比自然语言更稳妥的方案。虽然示例代码中使用的是普通文本输出但在真实生产环境中建议使用模型的函数调用Function Calling或 JSON Mode 能力强制模型输出结构化结果。例如分类结果可以要求模型输出{category: AI}评分结果可以要求模型输出{score: 4}这样做的好处是在代码层不需要再做“解析自然语言”这一步减少了很多不确定性。8.5 为每个模型调用建立日志追踪这是 Agent 工程中常被忽视但极为重要的一点。没有日志你很难回答这几个问题为什么某个条目被过滤掉了为什么摘要这么差这个月模型调用成本是多少推荐的日志字段包括时间戳、任务类型、输入文本摘要、模型输出、耗时、token 数、是否降级。这些日志既用于问题排查也用于评估模型效果和成本优化。8.6 从定时任务开始逐步走向事件驱动资讯日报生成器最自然的部署方式是定时任务Cron。但如果你想把它升级成一个更通用的 Agent 系统可以考虑事件驱动架构新内容到达时触发采集例如 Webhook采集完成后写入消息队列工作流消费者从队列中取消息执行后续步骤日报生成后发送到目标渠道。这个升级路径从单体脚本变成微服务本质上就是 Agent 架构师的日常工作。9. 总结与后续学习方向通过这个实战课你应该已经理解了模型参与的复杂工作流到底是怎么一回事它不是把“调用模型”堆进脚本里而是要对任务做分层、定边界、设计降级。资讯日报生成器虽小但它包含了一个 Agent 系统最核心的骨架采集工具、模型节点、规则过滤、状态流转、输出模板。如果你照着本文的代码把流程跑通了下一步可以按这个顺序继续深入把 FakeLlmClient 换成真实模型服务体验模型输出的不确定性和调参过程加入向量化去重用 Embedding 相似度替换简单标题去重加入内容持久化把处理结果存入数据库支持历史查询和趋势分析接入消息通知让日报自动推送到钉钉、飞书或企业微信机器人尝试多模型协同用不同模型分别负责摘分类和摘要对比效果。更重要的是你要把这些设计方法迁移到其他 Agent 项目里接到一个需求时先不要急着写代码而是画出工作流标出哪些节点用模型、哪些节点用规则、哪些节点需要降级。这套思考框架比任何具体的代码都值钱。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价