AI Agent实战:用Function Calling构建网红筛选自动化系统 当“Arcads 推出 AI Agent 自动寻找品牌合作网红”这类产品信息出现时很多后端开发者和算法工程师的第一反应是把它归类为营销话术。但如果把这条需求拆开看它其实是 AI Agent 技术相当典型的一次落地品牌方给出产品、预算、平台、目标受众和内容方向系统需要在大规模红人数据库里完成条件检索、内容匹配、报价评估和最终推荐。这个过程天然适合交给 Agent因为它不是一条固定流程而是“理解需求、调用工具、检查结果、给出解释”的循环。这篇文章不替任何具体产品背书而是把这类能力从工程角度讲清楚。我们会以一个“品牌合作网红筛选 Agent”为案例用 Python、支持函数调用的大模型接口以及一份模拟红人数据实现一个能理解自然语言需求、自动调用检索工具并输出候选名单的最小 Agent。完整覆盖数据结构、工具设计、执行循环、验证方法、常见问题和生产落地路径。适合正在学习 AI Agent 开发、想把大模型接进业务系统的开发者阅读。1. 先理解“AI Agent 找网红”和普通脚本的本质差异1.1 为什么传统脚本和 RPA 很难做好这件事如果一个需求是“按条件查数据库再按粉丝数排序”传统脚本十分钟就能写完。品牌合作网红筛选真正难的不是排序而是需求本身不确定品牌方可能说“想要小红书偏日常感的美妆博主粉丝不用特别多但互动要真实预算别太高”也可能说“上次合作过的那类账号再找一批类似风格的”。这类输入有三个特点信息密度低关键词不完整。粉丝数、互动率、预算上限都需要模型从自然语言里抽取。判断标准模糊。“真实”“有质感”“像某个品牌调性”这类表述没有明确字段需要借助内容标签、受众画像和评论数据间接衡量。结果必须能解释。品牌方不会接受“系统觉得合适”这种结论至少要说清楚为什么选这个人、参考了哪些字段。传统脚本只能处理结构化字段完全确定的场景。RPA 也只能执行固化操作一旦页面结构或判断规则变化整套流程就失效。而 AI Agent 的核心不是替代查询工具而是承担“需求理解、工具编排和结果解释”这三层工作。1.2 AI Agent 解决的是目标驱动的工具编排问题AI Agent 在“寻找品牌合作网红”场景里的工作方式可以拆成五个环节模型负责把用户需求解析成可执行条件。工具负责执行真实检索比如查红人库、查历史合作记录、查最近内容数据。模型根据工具返回结果决定下一步动作是继续缩小范围还是查详情还是直接输出结论。多轮对话和历史消息充当短期记忆避免重复提问。最后模型必须基于真实工具返回值输出结论不能凭训练记忆编造红人名单。用一个简单例子区分脚本、RPA 和 Agent实现方式工作原理在网红筛选场景中的表现普通脚本写死查询参数按函数执行需求一变就要改代码无法理解模糊表达RPA录制或编排固定界面操作平台界面或字段变化就中断无法动态决策AI Agent模型理解意图动态选择工具观察结果后继续推理能处理“预算别太高”这类模糊条件也能在结果过少时自动放宽过滤条件这里的 Agent 不再是一个单独的模型输出而是一个循环模型提出下一步执行计划代码执行工具执行结果再交回给模型直到模型认为自己掌握足够信息并生成结束回答。1.3 这个场景里 Agent 的边界在哪里有一点必须提前认清AI Agent 不会比数据源更聪明。如果底层红人库只有粉丝数、报价、基础分类Agent 就不可能判断“内容质量是否高级”。它能做到的是尽量准确地把约束转成查询条件并告诉你结果依据什么排序。所以做这类系统时有三条边界要写进设计文档大模型不能直接访问红人库必须通过工具函数。否则模型会自行编造不存在的账号数据。模型不能直接执行任意 SQL 或代码只能调用白名单工具。否则提示词注入可能让 Agent 越权。最终名单必须通过工具返回值校验。Agent 输出的账号 ID 如果在数据层查不到就应该拒绝展示。理解了这些边界再看后面最小实现就会容易很多。2. 从业务目标到最小架构Agent、工具和数据层先分层2.1 先确定一次完整任务包含哪些能力我们要实现的“品牌合作网红筛选 Agent”目标是处理这类输入想在小红书找 3 位美妆博主合作粉丝数在 5 万到 30 万之间女性粉丝占比希望不低于 70%互动率争取 5% 以上单篇报价 2 万以内。要完成这个任务系统至少需要两部分能力search_influencers按平台、粉丝数、互动率、报价、受众性别占比等字段执行初筛。get_influencer_detail对初筛命中的头部账号查询更完整的内容标签、近期主题和过往合作方向。Agent 的工作顺序应该是先调用 search 获得候选达不到数量就自动放宽条件达到数量后对前几名调用 detail 看内容风格是否匹配最后基于详情和初筛字段综合排序。这里不要一开始就做“自动报价协商”或“自动发送合作私信”。那些环节涉及合同、资金和平台规则风险高应该在最小系统跑通后再逐步接入。2.2 演示数据没有真实红人库时先建一份合法模拟数据真实红人库数据可能来自平台开放接口、第三方数据服务或公司历史合作记录但开发阶段直接接线上数据既慢又不安全。先在本项目里放一份模拟数据字段贴近真实库的常用结构# influencer_db.py INFLUENCER_DB [ { id: R001, name: Amy美妆日记, platform: 小红书, category: 美妆, followers: 153000, engagement_rate: 8.1, female_ratio: 0.88, avg_likes: 14200, avg_comments: 860, price_per_post: 15000, city: 上海, recent_topics: [早八通勤妆, 平价彩妆测评], brand_past: [某国货护肤品牌, 某日系彩妆品牌] }, { id: R002, name: 雾屿森林日志, platform: 小红书, category: 护肤, followers: 68000, engagement_rate: 6.4, female_ratio: 0.82, avg_likes: 4300, avg_comments: 280, price_per_post: 6500, city: 杭州, recent_topics: [敏感肌护理, 平价护肤] }, { id: R003, name: 宋小野穿搭手册, platform: 小红书, category: 穿搭, followers: 212000, engagement_rate: 4.1, female_ratio: 0.67, avg_likes: 8600, avg_comments: 420, price_per_post: 18000, city: 成都, recent_topics: [小个子穿搭, 通勤风] }, { id: R004, name: 彭彭测评社, platform: 抖音, category: 美妆, followers: 436000, engagement_rate: 2.6, female_ratio: 0.55, avg_likes: 12000, avg_comments: 350, price_per_post: 35000, city: 广州, recent_topics: [热门彩妆测评, 开箱] }, { id: R005, name: 小鹿生活方式, platform: 小红书, category: 美妆, followers: 97000, engagement_rate: 5.9, female_ratio: 0.91, avg_likes: 6700, avg_comments: 510, price_per_post: 10000, city: 北京, recent_topics: [干皮底妆, 新手化妆教程] } ]字段说明engagement_rate 表示互动率单位是百分数8.1 就是 8.1%。female_ratio 表示女性受众占比范围 0 到 1。price_per_post 表示单篇合作报价单位是人民币。recent_topics 和 brand_past 用于 detail 工具做内容风格判断初筛时不必返回。这份数据可以放在文件里先跑通链路生产环境再替换成数据库或接口服务。2.3 技术选型和项目目录选定 Python 3.10 以上版本。Agent 底层的大模型需要支持工具调用也就是 function calling / tool calling。主流程中我会直接调用兼容 OpenAI 格式的 SDK不引入 LangChain因为这样更能看清 Agent 循环本身。安装依赖pip install openai python-dotenv如果完全使用本地模型或第三方兼容服务只需额外配置 base_url 和模型名。项目结构保持简单influencer_agent/ ├── influencer_db.py # 模拟红人库 ├── tools.py # Agent 可调用的工具函数 ├── agent.py # Agent 执行主循环 └── .env # 模型服务 API Key 配置.env 文件中放模型调用凭证和可选的接口地址OPENAI_API_KEYsk-your-key # 如果使用兼容 OpenAI 格式的服务取消下一行注释并填写 # OPENAI_BASE_URLhttps://your-endpoint.example.com这里要明确一点大模型厂商和加密策略可能变化实际配置以你使用的服务文档为准。关键不是某个具体模型而是“模型负责决策、代码负责执行工具”的架构。3. 逐步实现构建一个网红筛选 Agent 主循环3.1 先写工具层所有数据访问都收敛在代码里工具层是 Agent 与数据的唯一桥梁。不要在大模型提示词里写 SQL也不要让模型直接读数据库因为那会带来严重的参数注入和越权风险。tools.py 里实现两个工具# tools.py import json from influencer_db import INFLUENCER_DB def search_influencers( platformNone, categoryNone, min_followers0, max_followersNone, min_engagement_rate0.0, max_price_per_postNone, min_female_ratio0.0, sort_byengagement_rate, ): results [] for inf in INFLUENCER_DB: if platform and inf[platform] ! platform: continue if category and inf[category] ! category: continue if inf[followers] min_followers: continue if max_followers is not None and inf[followers] max_followers: continue if inf[engagement_rate] min_engagement_rate: continue if max_price_per_post is not None and inf[price_per_post] max_price_per_post: continue if inf[female_ratio] min_female_ratio: continue results.append(inf) results.sort(keylambda x: x.get(sort_by, 0), reverseTrue) # 初筛只返回关键字段避免上下文被不必要的信息塞满 return [ { id: inf[id], name: inf[name], platform: inf[platform], category: inf[category], followers: inf[followers], engagement_rate: inf[engagement_rate], female_ratio: inf[female_ratio], price_per_post: inf[price_per_post], city: inf[city], } for inf in results[:10] ] def get_influencer_detail(influencer_id): for inf in INFLUENCER_DB: if inf[id] influencer_id: return { id: inf[id], name: inf[name], full_profile: inf, } return {error: fnot found: {influencer_id}} def dispatch_tool(name, arguments): if name search_influencers: return search_influencers(**arguments) if name get_influencer_detail: influencer_id arguments.get(influencer_id) if not influencer_id: return {error: 缺少 influencer_id} return get_influencer_detail(influencer_id) return {error: funknown tool: {name}}初筛为什么要限制返回 10 条而不是全部因为大模型的上下文长度有限。如果一次把几千个账号的完整信息塞回去不仅费 token还会让模型忽略关键排序。让模型面对一个“小而有序”的结果集反而更容易选出最合适的人。detail 工具解决的是“初筛字段不够用”的问题需要判断内容风格时再单独查某几个账号的完整档案。3.2 用函数调用声明 Tools Schema告诉模型能做什么agent.py 里先定义两个工具的 Schema。Schema 里的 description 很重要它直接影响模型能否正确传参。# agent.py import json import os from dotenv import load_dotenv from openai import OpenAI from tools import dispatch_tool load_dotenv() client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL, None), ) TOOLS [ { type: function, function: { name: search_influencers, description: 按品牌方条件检索网红。当用户需求包含平台、内容领域、粉丝数、互动率、报价或受众占比时调用。缺字段不要乱猜使用默认值。, parameters: { type: object, properties: { platform: {type: string, description: 平台名称例如小红书、抖音}, category: {type: string, description: 内容领域例如美妆、护肤、穿搭}, min_followers: {type: integer, description: 最低粉丝数, default: 0}, max_followers: {type: integer, description: 最高粉丝数}, min_engagement_rate: {type: number, description: 最低互动率百分数例如 5 表示 5%}, max_price_per_post: {type: integer, description: 单篇报价上限单位人民币}, min_female_ratio: {type: number, description: 最低女性受众占比范围 0 到 1}, sort_by: { type: string, enum: [engagement_rate, followers, price_per_post], default: engagement_rate, description: 排序字段, }, }, required: [], }, }, }, { type: function, function: { name: get_influencer_detail, description: 获取单个网红完整资料包括近期内容主题和过往合作品牌用于判断内容风格是否匹配。, parameters: { type: object, properties: { influencer_id: {type: string, description: 从 search_influencers 返回结果中获取的网红 ID} }, required: [influencer_id], }, }, }, ]需要注意Schema 里的 min_engagement_rate 与 min_female_ratio 单位不同。如果写错单位结果会完全错乱。描述中必须明确写清“5 表示 5%”和“0 到 1”这是 AI Agent 工程中很容易在这一层埋坑。3.3 编写 Agent 循环不断判断是否需要调用工具系统提示词要把“禁止编造数据”放在最前面SYSTEM_PROMPT 你是品牌合作网红筛选助手。 工作规则 1. 用户提出合作需求后先调用 search_influencers 获取候选名单。 2. 如果候选数量明显不够可以适当放宽不重要的条件并重新检索。 3. 对需要重点判断的前 1 到 3 名调用 get_influencer_detail 获取内容风格资料。 4. 禁止根据训练记忆编造网红账号、粉丝数、互动率或报价。 5. 输出结论时每条推荐都要说明依据哪些真实检索数据并提示人工复核。这里有一条容易被忽略的产品逻辑Agent 不是一次性调完工具就结束它可能因为候选太少而再次调整参数。所以主循环必须允许多个来回。def run_agent(user_query, max_rounds5): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_query}, ] final_answer for step in range(max_rounds): response client.chat.completions.create( modelos.getenv(AGENT_MODEL, gpt-4o-mini), messagesmessages, toolsTOOLS, tool_choiceauto, temperature0.2, ) assistant_message response.choices[0].message messages.append(assistant_message) # 如果模型没有请求调用工具说明它已经准备好生成结论 if not assistant_message.tool_calls: final_answer assistant_message.content or break print(f[round {step 1}] Agent 请求调用 {len(assistant_message.tool_calls)} 个工具) for tool_call in assistant_message.tool_calls: fn_name tool_call.function.name try: fn_args json.loads(tool_call.function.arguments or {}) result dispatch_tool(fn_name, fn_args) except Exception as exc: result {error: f{fn_name} 执行异常: {str(exc)}} print(f - 工具: {fn_name}, 参数: {json.dumps(fn_args, ensure_asciiFalse)}) print(f - 返回: {json.dumps(result, ensure_asciiFalse)[:300]}) messages.append( { role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), } ) if not final_answer: final_answer f经过 {max_rounds} 轮仍未得到可用结论请检查工具返回或调整条件。 return final_answer if __name__ __main__: query 想在小红书找 3 位美妆博主粉丝数 5 万到 30 万女性粉丝占比不低于 0.7互动率至少 5%单篇报价 2 万以内。 print(品牌需求, query) print(Agent 输出) print(run_agent(query))这个循环是对 Agent 核心机制最直接的解释模型先生成“要不要调用工具、调用哪个、传什么参数”的决策代码执行完之后把结果以 tool 角色消息放回对话。模型看到结果后再决定下一步。max_rounds是安全阀防止模型无限循环。temperature调低是为了让模型在生成工具参数时更稳定减少随机发挥。3.4 运行后的预期链路正常运行时会看到类似打印品牌需求 想在小红书找 3 位美妆博主粉丝数 5 万到 30 万女性粉丝占比不低于 0.7互动率至少 5%单篇报价 2 万以内。 [round 1] Agent 请求调用 1 个工具 - 工具: search_influencers, 参数: {platform: 小红书, category: 美妆, min_followers: 50000, max_followers: 300000, min_engagement_rate: 5.0, max_price_per_post: 20000, min_female_ratio: 0.7}由于模型服务版本不同同一段代码返回结果会有差异但链路结构是一致的先初筛再对部分账号查详情最后汇总。如果模型一次返回两个工具调用例如 search 后又立刻请求 detail那是正常的只要 detail 请求来自 search 返回的真实 ID 即可。实际模型输出不一定会严格打印在示例位置最终的推荐名单至少应该包含候选人姓名、平台、关键指标和入选理由。推荐名单中出现数据库里不存在的账号就是系统异常需要回查日志。4. Agent 的关键机制与参数控制为什么“让模型调用工具”不等于“把任务交给模型”4.1 工具调用背后的安全边界上面的实现里模型从来没有直接访问INFLUENCER_DB。它只能提交一段 JSON 参数给代码层由代码决定是否允许执行、按什么方式执行。这就是工具调用的安全价值模型是“决策者”不是“执行者”。即使模型被用户提示词诱导它最多只能请求某个工具无法自行执行系统级命令。在真实项目中还要增加权限校验、参数校验和操作审计因为 Agent 最终暴露的权限必须比模型本身权限更小。4.2 为什么系统提示词要强调“先调用工具再根据结果回答”大模型训练数据里有很多公开网红名单、账号信息但没有一份是实时、准确、已授权可用的。如果模型跳过工具直接凭记忆回答它给出的名单可能看起来很合理但实际账号已经停更、粉丝数过期、报价偏差极大甚至压根是编造的组合。系统提示词这层约束还不够。模型可能因为上下文太长、工具结果不清晰而回到“自由发挥”模式。所以工程上要加两层保险一是主循环里判断最终答案是否只依赖工具结果二是在输出端做字段校验例如验证推荐 ID 确实出现在某次 tool 返回值中。字段校验不是模型做的事而是下游代码做的事。4.3 核心参数的选择逻辑参数含义建议值调大影响调小影响max_roundsAgent 最多循环轮数3 到 6更可能拿到完整信息但 token 消耗增长可能没查 detail 就急着总结temperature输出随机性0.1 到 0.3表达更丰富但工具参数可能不稳定回答更可控语言略显机械tool result topNsearch 返回条数10 到 20候选更全但上下文更拥挤上下文更精简但可能漏掉合适账号min_female_ratio 范围受众占比合法性校验必须在 0 到 1传值大于 1 会导致永远无结果不设置导致判断没有意义max_rounds 并不是越大越好。真实项目中一次品牌合作筛选通常三轮以内就能完成第一次 search第二次