ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent结构化数据收集:Nisuform表单后端集成实战

AI Agent结构化数据收集:Nisuform表单后端集成实战 最近在给几个 AI agent 项目做集成发现一个特别扎眼的新项目Nisuform一句话说就是“给 AI agent 用的漂亮表单后端”。当时我正被大模型在聊天窗口里收集结构化数据这件事折磨得够呛看到这个标题立刻点进去了。如果你也在做 agent 类应用或者正在愁怎么让模型稳定地拿到格式规整的输入这篇内容值得认真看一遍。先说清楚这个东西解决了什么问题。我们现在做的所谓 AI agent核心能力是让模型去调用工具、完成任务但任务往往需要人输入信息。比如一个差旅 agent 要帮你订机票至少得知道你哪天走、从哪到哪、舱位偏好这些都不能靠模型猜。早期做法是让模型在对话里一句一句问问完了再自己整理成 JSON听起来挺聪明实际用起来全是坑。模型可能问漏一项可能把日期格式理解错更不要提多轮对话后的上下文污染回头一看它压根没记住你上轮说过什么。Nisuform 提供的思路是把“收集信息”这件事从聊天流程里剥离出来交给一个专门设计的表单后端去处理模型只负责判断什么时候该调用这个表单以及把表单返回的结构化结果拿去执行后续动作。这其实就是我在多个项目里反复验证过的最佳实践——约束即自由。与其让大模型在开放式对话里自由发挥不如给它一个明确的、机器可校验的表单界面让用户在精心设计的 UI 上填入所有必要信息。模型拿到的是一份永远合法、字段完整的 JSON而不是从一段可能含糊的对话里硬解析出来的不确定结果。下面我会从场景拆解、核心功能、实操接入、问题排查这几个维度把这个工具和这类“表单后端”形态彻底讲透包括我实测下来的一些独家心得。1. 为什么 AI agent 需要独立的表单后端1.1 聊天界面收集结构化数据的四个致命伤我最早做 agent 集成的时候天真地以为大模型天生擅长对话式信息收集。后来发现事情完全不是这么回事。第一是token 成本失控。让模型在聊天里引导用户填信息一个典型的订票流程可能要来回十几轮。每一轮都要把历史对话喂给模型这些历史里至少一半是人类用户的闲聊或者重复描述对任务本身毫无价值但 token 照样扣钱。用表单后端之后信息收集只发生在表单界面里模型在对话流里只需要做一次 tool call整条链路从十几轮压缩到两三轮。第二是信息完整度无法保证。模型是人不是系统它有“自由意志”的幻觉。让它问五个问题它可能只问四句就急着去调 API最后缺了个字段API 报错模型再灰溜溜地回来补问。这个过程你控制不了因为它不是按照 schema 驱动的是跟着概率走的。表单后端则不同字段、校验、必填项全是强制的用户不填完就提交不了。第三是数据格式和语义漂移。今天用户说“下周出发”你让模型解析成 2025-06-16明天用户说“后天下午一点”模型可能给成 2025-06-16T13:00:00输入格式不一致下游 API 全乱。表单后端里日期是一个 date picker用户选的永远是一个 ISO 8601 字符串模型拿到手就是规范格式不需要任何二次解析。第四是状态管理极其恶心。多轮会话里模型需要记住“用户已经说过出发地”这种局部状态。上下文一长它可能把前面几轮的信息忘了或者在处理另一个任务时把记忆搞混。表单后端把状态固化在表单实例里填到一半可以保存草稿下次打开接着填agent 只需要读一次当前状态就知道缺什么、有什么再也不吃上下文丢失的亏。1.2 表单后端到底是个什么形态表单后端这个名字挺有意思它不只是一个“让你创建表单的 SaaS”而是一整套面向程序调用的表单生命周期管理服务。核心形态是表单定义层通过可视化编辑器或者 JSON schema 定义“有哪些字段、什么类型、什么校验规则、有没有条件逻辑”。实例管理层每次调用生成一个独立的表单实例有唯一的 submission token 和状态待填写、已提交、已过期等。API 接入层向外部系统暴露 create、get、list、submit 等接口其中最关键的是支持 LLM function calling 的 schema 描述模型可以直接把调用表单构造为一次标准的 tool call。数据出口层用户提交之后结构化结果可以通过 webhook 推送给 agent 后端也可以由 agent 按实例 ID 主动拉取。这就像把传统网页表单的“展示层”和“数据收集层”解耦但加入了一个面向智能体的接口层。传统表单是给浏览器用户用的表单后端是给程序——尤其是大模型驱动的程序——用的。Nisuform 在“漂亮”这点上做得比较突出。表单前端是一套现代化 React 组件渲染的界面不是那种老掉牙的 iframe 嵌套表单。它可以根据接入场景自动适配界面风格甚至可以嵌入你自己的产品页面里而不是跳转到第三方域名。这一点对于想保持产品体验一致的团队来说价值极高。1.3 Nisuform 在整个 agent 架构中的位置想理解 Nisuform最好画一张架构定位图——不我不画 mermaid直接文字描述。你的 agent 后端在收到用户请求后第一步要做“意图识别 任务规划”。假设识别出用户要订酒店agent 下一步不是问“您想住什么价位的”而是先调用一个工具去创建表单实例。这步调用返回的不只是“表单创建成功”而是直接返回一个可访问的 URL 以及一个表单 ID。agent 把 URL 发给用户说“点击这里填写您的住宿偏好”用户填完点提交数据通过 webhook 回到 agent 后端agent 再调用酒店预订 API 完成后续任务。在这个流程里表单后端承担了“人机接口”的职责。它把人从聊天框里拖出来放到一个适合输入复杂信息的界面里。这在大模型产品落地时是个非常重要的设计——聊天框不是万能的它适合语音式命令不适合精确信息收集。我自己在架构里通常把它放在“工具层”和支付、邮件、短信这类外部服务并列。agent 把它们当作工具调用但工具本身不需要有“智能”它只需要稳定、可靠、可校验。2. Nisuform 的核心能力拆解与设计思路2.1 可视化表单构建比写代码更快的方案选型Nisuform 提供了一套可视化表单构建器类似 Notion 数据库或者 Typeform 的编辑体验。你可以在界面上拖拽字段、配置校验规则、设置条件显示逻辑最终它会生成一份表单定义的 JSON schema。很多开发者看见可视化编辑器就不屑说“我们团队自己写 React 表单不就行了”。这里我想认真劝退一下。表单这种看起来简单的东西真正做起来有一堆看不见的复杂度。日期组件要不要做时区处理地址字段要不要联动地区选择手机号要按哪个国家的格式校验选填和必填的交互提示怎么做错误信息怎么展示才不至于让用户一脸懵这些问题你自己写没有两周下不来而且写完还不一定比人家现成组件打磨得好。可视化构建器最大的价值是让非技术人员也能参与表单的迭代。产品经理改字段、运营同学调文案、业务线加一个“发票信息收集”都不需要提工单等前端排期。这在一个快速迭代的 agent 产品里是巨大的效率杠杆。我在实际操作中发现用可视化构建器还有一个隐藏好处它强制你按产品思维去梳理信息需求而不是直接从代码层面想“我要几个 input”。你会下意识地思考字段语义、分组逻辑、填写顺序这对表单的完成率有直接影响。2.2 核心字段类型与条件逻辑的取舍表单后端要服务的场景千差万别Nisuform 内置的字段类型基本覆盖了多数场景但设计思路上有明确的取舍。我整理了一个字段清单标注了适合的用法和踩坑点供参考。字段类型最佳使用场景踩坑提示单行文本姓名、公司、订单号注意配置最大长度别让用户填整篇文章多行文本备注、补充说明需要决定是否支持 Markdown 渲染单选/多选偏好、标签、分类选项多了会有“选择困难”建议不超过 7 项下拉选择城市、国家、产品线支持搜索时体验大幅提升日期/时间行程、预约、日程必须确认时区处理方式建议统一存 UTC文件上传证件、附件、合同文件大小限制、类型白名单必配数字数量、金额、年龄配置 min/max 比配置正则更可靠JSON 片段高级场景复杂嵌套结构适合填非结构化数据结构但要做 schema 校验条件逻辑是表单后端和普通 HTML 表单拉开差距的关键功能。你可以配置“当用户选择‘企业客户’时显示‘公司名称’和‘税号’字段选择‘个人客户’时隐藏这两个字段”。这个能力在信息收集场景里太常用了没有条件逻辑的表单只能把所有问题铺在一个长页面上填写体验非常糟糕。我建议一定把条件逻辑用起来。因为 agent 后端拿到的数据质量直接取决于表单界面对用户引导的清晰程度。用户看到一个逻辑清晰、只问相关问题的表单愿意认真填完的概率会高很多。2.3 API 设计与大模型 function calling 的无缝衔接Nisuform 的 API 设计是整个项目最有技术含量的部分。它不只是给你一组增删改查接口更重要的是提供了面向大模型的 function schema。我做过不少 function calling 集成最头疼的就是为大模型编写准确的 JSON Schema 描述。一个字段的类型写错模型就可能传错格式描述文字不清晰模型就可能不知道该传什么值。Nisuform 做了件聪明的事把你用可视化构建器定义好的表单自动转化为 OpenAI function calling 的 JSON Schema你拿过去几乎不用改直接塞进 tools 数组。举个实际例子你定义了一个“差旅申请表单”字段包括出发城市、目的地、出发日期、返回日期、舱位等级。Nisuform 会自动生成类似这样的 schema结构示意具体细节以官方文档为准{ type: function, function: { name: create_travel_request_form, description: Create a travel request form for the user to fill in, parameters: { type: object, properties: { form_template_id: { type: string, description: The template ID of the travel request form }, prefill: { type: object, description: Optional pre-filled values extracted from conversation context, properties: { origin_city: { type: string, description: Departure city, e.g. Shanghai }, destination_city: { type: string, description: Destination city, e.g. Beijing }, departure_date: { type: string, format: date, description: YYYY-MM-DD }, return_date: { type: string, format: date, description: YYYY-MM-DD }, cabin_class: { type: string, enum: [economy, business, first] } } } }, required: [form_template_id] } } }值得注意的设计是prefill参数。这意味着模型可以从对话上下文里提取一部分已知信息比如用户说过“我从上海出发”那origin_city就被自动填好了。这些不是必填字段模型可以只填它确定的部分其余留给用户通过表单界面完成。这个设计极大减少了用户的重复填写成本也体现了表单后端和普通表单的本质区别——它是智能体工作流的一部分不是孤立的数据录入工具。2.4 结构化数据回传webhook 优先、主动轮询兜底表单提交后数据怎么回到 agent 后端Nisuform 给了两条路webhook 推送和实例状态查询。Webhook 是首选方案。用户点下提交按钮的那一刻Nisuform 服务端向你要的 callback URL 发一个 HTTP POST 请求里面带着表单实例 ID 和完整的结构化数据。你的 agent 后端收到这个请求就可以从“等待用户填写”状态切换到“执行后续任务”状态了。我在实际工程里强烈建议你不要把关键逻辑只押在 webhook 一个环节上。网络抖动、服务重启、消息队列积压都可能导致 webhook 延迟甚至丢失。最稳妥的架构是 webhook 做即时通知数据库里同时记录 form_instance_id 和 submission_statusagent 每次启动一个新任务前先做一次对账把“提交了但还没处理”的实例捞出来。Nisuform 的查询接口支持按 status 过滤你把 statuspending 的实例拉出来处理就好这种兜底方式我在生产环境里实测非常稳。另外一个容易踩的坑是幂等处理。同一个 webhook 可能因为网络原因被重发两次你的后端必须按 submission_id 做去重不能用户提交一次表单你调了两次酒店预订 API。3. 实操接入从零把 Nisuform 接入你的 agent3.1 准备工作开始动手之前有几个前置条件要确认一下。首先注册一个 Nisuform 账号需要有一个能接收 webhook 的公网可访问 HTTPS 地址。本地开发的话可以用一些内网穿透工具把 localhost 暴露出去但生产环境还是建议放在你的后端服务上。然后设置好 API Key它的鉴权方式比较标准请求头里加Authorization: Bearer YOUR_API_KEY。接下来在可视化编辑器里建一个表单模板。我建议第一次接入时先用一个最简单的“客户回访信息收集”练手字段就三个姓名、联系方式、备注。等流程跑通了再逐步上复杂度高的表单。3.2 用 OpenAI function calling 接入 Nisuform现在进入核心环节把 Nisuform 变成 agent 的一个工具。下面我给一段基于 OpenAI Python SDK 的示例代码这在你自己的项目里几乎可以平替。from openai import OpenAI import requests import json client OpenAI(api_keyYOUR_OPENAI_API_KEY) NISUFORM_API_URL https://api.nisuform.com/v1/forms NISUFORM_API_KEY YOUR_NISUFORM_API_KEY NISUFORM_FORM_TEMPLATE_ID tpl_customer_feedback def create_form_instance(prefill: dict): # 这个函数会被模型在 function calling 中调用 resp requests.post( NISUFORM_API_URL, headers{Authorization: fBearer {NISUFORM_API_KEY}}, json{ form_template_id: NISUFORM_FORM_TEMPLATE_ID, prefill: prefill, }, timeout10, ) resp.raise_for_status() return resp.json() # 返回 form_id, form_url 等字段 # 大模型工具描述 tools [ { type: function, function: { name: create_form_instance, description: Create a form instance for collecting structured information from the user, parameters: { type: object, properties: { prefill: { type: object, description: Pre-filled values extracted from conversation context, properties: { name: {type: string, description: Customer name}, phone: {type: string, description: Customer phone number}, note: {type: string, description: Additional notes} } } }, required: [prefill] } } } ] # 示例用户说“我叫张三电话 138xxxx帮我登记一下其他事项我填表单里” messages [ {role: user, content: 我叫张三电话 138xxxx帮我登记一下其他事项我填表单里} ] # 第一轮模型应该决定调用 create_form_instance response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, tool_choiceauto, ) # 解析模型返回的 tool call实际执行函数 tool_call response.choices[0].message.tool_calls[0] tool_call_args json.loads(tool_call.function.arguments) # 这里 args 里可能包含模型从对话中提取的 prefill 信息 form_result create_form_instance(tool_call_args.get(prefill, {})) # 把工具结果回传给模型 messages.append(response.choices[0].message) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(form_result), }) # 第二轮模型应该告诉用户点击链接继续填写 response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, ) print(response.choices[0].message.content)这段代码的完整流程是模型识别用户意图决定创建一个表单实例把已知的姓名和电话预填进去然后把表单链接作为消息回复给用户。用户点击链接看到的是一个被预填好的漂亮表单补完信息点提交你的 webhook 就收到了结构化数据。这里有几个细节值得反复琢磨。第一工具描述文本和字段描述文本的质量直接决定模型能不能做出正确的调用决策。在description里写清楚“这个工具用于收集结构化信息”模型就知道什么时候该调用它在每个属性里写清楚格式和示例模型就知道怎么填充 prefill。我习惯把所有枚举值都写进描述里比如日期格式统一说明“YYYY-MM-DD”这能显著减少模型的格式幻觉。第二required字段的选择有讲究。我的策略是只把prefill设置为必填但prefill内部的字段全部选填。这样模型永远不会因为某个信息缺失而拒绝调用工具它可以把不确定的部分留空让用户在表单里填写。强迫模型填它不确定的信息是幻觉的根源。第三工具调用的超时设置。Nisuform 的 API 响应时间一般在几百毫秒级别但我在生产环境里会把超时设到 10 秒以上因为你无法控制模型在前一轮思考多久API 端的超时只是兜底。另外不要在主线程里做同步调用用异步或者独立线程更合适。3.3 Webhook 接收端的实现要点表单提交后的 webhook 推送Nisuform 会在 header 里带上签名信息你需要验证来源。实现上至少要做这几件事验签按文档约定的算法用你配置的 webhook secret 对请求体计算签名比对一致才处理。去重按 submission_id 或者 event_id 做幂等处理。最简单的方式是把这个 ID 存到 Redis设置过期时间为 24 小时重复请求直接丢弃。响应快agent 后端要立刻返回 200然后异步处理业务逻辑。如果你在 webhook 回调里同步调用酒店 API、查询库存这种耗时操作Nisuform 那边可能等不及就标记投递失败后续重试会让你重复收到同一个事件。配 webhook 时一个容易忽略的点是成功响应的状态码必须是 200 或 2xx任意非 2xx 状态码Nisuform 都会视为投递失败并按策略重试。重试间隔通常是递增的比如 1 分钟、5 分钟、30 分钟如果你的服务连续挂掉几个小时消息队列里会堆积大量待重试的事件恢复后要小心流量毛刺。# 一个简单的 webhook 处理器示例Flask from flask import Flask, request, jsonify import redis app Flask(__name__) r redis.Redis.from_url(redis://localhost:6379/0) app.route(/webhook/nisuform, methods[POST]) def handle_nisuform_webhook(): # 1. 验签逻辑按官方文档实现 # 2. 幂等检查 event_id request.json.get(event_id) if r.set(fnisuform:{event_id}, processed, nxTrue, ex86400): # 3. 异步处理业务逻辑 # process_submission(request.json) return jsonify(statusok), 200 # 重复事件直接返回成功但不再处理 return jsonify(statusok), 200这个骨架代码看起来简单但在生产环境里幂等和验签这两个点就是决定你能不能安心睡觉的关键。我见过太多团队上线第一天就漏了验签结果第二天收到一堆伪造 webhook业务被刷出无数个订单。3.4 多轮对话中的表单状态管理真实 agent 产品的对话流程不是线性的。用户可能聊着聊着岔开去问别的问题然后又回来继续填表。这时候你需要维护一个“当前会话与表单实例的映射关系”。我常用的做法是在会话状态存 Redis里记录active_form_id当用户再次触发同一个 intent 时先检查有没有未完成的表单实例有就直接把同一个 URL 再发一次。这样用户不会被迫反复填同一个表单。如果用户明确说“我改个地方”那就需要支持表单实例的修改功能。Nisuform 是否支持已提交实例的编辑取决于版本但通常情况下最简单的方式是创建一个新实例并把旧值 prefill 进去让用户确认修改。旧实例标记作废即可。这里有一个前端体验的细节表单实例 URL 要允许用户跨设备打开。用户可能刚开始在手机上聊聊到一半转到电脑上填表。URL 没有和设备绑定直接浏览器打开就能用这是个很实用的产品决策。4. 常见问题与排查技巧实录4.1 模型把表单 URL 甩给用户但用户没点开怎么办这是我在集成这类表单工具时遇到频率最高的问题。模型生成了表单链接但用户没有点开继续在聊天里输入“帮我订明天去北京的票”。这时候你的 agent 必须能识别“用户没有走表单流程而是在用对话方式表达意图”。标准做法是集成意图路由 宽容式兜底。当模型检测到用户继续用自然语言提供与表单字段相关的信息比如“明天下午去北京”你可以调用 Nisuform 的 prefill 更新能力把新信息补填到已创建的表单实例中再次推送链接。实测下来用户第一次没点开通常不是不想填而是不知道点开之后要做什么你在聊天里给一句“我已经帮你填好了出发地和日期您只需补充证件号码点这个链接就行”的引导打开率会大幅提升。另外要对“表单已创建但没有提交”的实例设置过期策略。我通常给实例设置 24 小时有效期超时自动作废避免用户在三天后又点开一个过期链接填完提交后下游收不到任务。4.2 模型在 prefill 里塞了不存在的字段名或错误格式大模型不总是严格遵守你给出的 schema尤其是复杂嵌套结构里的字段名。它可能把departure_date写成departureDate或者在数字字段里填了unknown。Nisuform 在 API 层有 schema 校验不符合定义的 prefill 会报 400 错误。我的策略是永远不要把模型返回的参数直接透传给下游。在调用 Nisuform API 之前先做一层“参数清理”把非白名单字段剥离把空字符串和unknown这类占位符转换为 null把日期字符串强制格式化为规范格式。这层代码写起来不复杂但能帮你省掉大量调试时间。更聪明的做法是把prefill里每个字段的description都写得足够严格。比如departure_date: { type: string, format: date, description: Departure date in ISO 8601 format, e.g. 2025-07-20. ALWAYS use YYYY-MM-DD. }把规则直接写到描述里比依赖模型“自己理解”要可靠得多。我在多个模型的对比测试中发现增加“ALWAYS use YYYY-MM-DD”这类强约束指令后格式错误的概率降低了 80% 以上。4.3 webhook 收到了大量重复事件如果你的 webhook 处理器没有做幂等就注定要被打爆这几乎是所有表单后端接入的成长痛。排查思路首先看 Nisuform 控制台的投递记录确认是否为重复投递其次看你自己的服务有没有在业务逻辑里触发重入比如在处理函数内部又调起了相同任务。第二个不太容易察觉的原因是你没有在前端或者服务端把表单实例标记为“已处理”。Nisuform 的实例状态是独立的你必须在处理完业务逻辑后主动调用 Nisuform 的 update API把实例状态从submitted改为completed。如果不做这一步即使没有重复推送你自己日志里也会看到同一 ID 被处理两次——因为你后端每次启动任务扫描时会重捞。4.4 agent 使用的模型不支持 function calling 怎么办不是所有模型都支持 function calling。如果你用的是开源模型或者某些国产模型可能不支持原生的 tools 参数。这种情况下有两种替代方案。第一种是输出约束方案用正则或 JSON mode 约束模型输出让它输出create_form_instance的 JSON 结果而不是做真正的 tool call。这要求模型在前一轮输出里同时包含意图判断和参数生成效果会差一些但能跑通。第二种是语义分类 独立填参先用一个简单分类模型判断用户意图是不是“收集信息”如果是直接用一套规则模板从对话中提取字段值。这个方案更适合场景固定、字段数量少的产品胜在链路轻、可控性高。我自己的经验是如果是新开的项目直接选择支持 function calling 的模型会省非常多事。function calling 不是营销噱头它把工具调用的工程复杂度降低了一个量级省事程度完全值得为之换模型。表格里整理了几个模型的兼容性参考。模型是否原生支持 function calling备注GPT-4o / GPT-4-turbo是schema 支持丰富Claude 3.5 Sonnet是对描述文本的理解力很好某些开源模型部分支持可能要配合 JSON mode轻量二分类模型否只适合简单意图路由4.5 用户体验层面的失败链接发过去了但表单加载不出来这个问题的原因通常不在 Nisuform而在你自己的集成端。聊天界面里展示链接有些平台会自动将链接转为卡片但卡片可能被安全策略拦截导致用户点击没有反应。排查时先看三点表单链接是否有过期时间过期的实例不能再去打开应该提示重新生成。链接是否是 HTTPS大多数聊天平台不让非 HTTPS 链接可点击。如果你的产品在中国大陆上线并使用了不被支持的 API 域名那前端加载会卡这时要么租用合规域名映射要么直接让团队把表单页面内嵌到自己的应用页面里。Nisuform 支持 iframe 嵌入这个能力在这种场景下很实用。最后再说一个我个人的习惯任何表单收集链路都建议做一个“人工兜底”。在表单界面下方放一个“遇到问题请联系人工客服”的入口用户提交失败或者对表单有疑问时能快速找到人。表面上看这增加了一点客服成本但它能保住信任。聊天机器人 表单的组合本质上是一个自动化链路上的最后一公里服务自动化的尽头必须有人的影子。5. 我的一些补充思考用 Nisuform 这类表单后端做 agent 信息收集我最大的感受是它帮助我把“对话式交互”和“事务式交互”在架构层面做了分离。这是很多 agent 产品打磨到后期必须面对的分野。日常闲聊、快速命令适合对话式复杂数据输入、多字段表单、需要用户思考和核对的任务适合独立的、专门的界面来完成。把这两类需求混在聊天框里处理既不专业也不高效。在团队协作上这个东西也改变了工作流。以前产品经理要加字段得拉上前端、后端一起开会排期现在自己打开编辑器拖一拖就上线了。对独立开发者来说更是省心一个表单定义可以被多个 agent 场景复用一个表单实例可以有完整的生命周期管理和审计日志这些能力自建的话没个几周下不来。如果你正在搭建 agent 产品还在犹豫信息收集到底怎么做我的建议是不要自己造轮子。先想清楚你要收集哪些信息用 Nisuform 快速建一个表单模板接上把全链路跑通再考虑要不要定制。等业务量上来、场景复杂了再评估自建一套内部表单后端也不迟。以目前这个工具的能力覆盖度来说绝大多数场景都可以直接落地。我个人的工作方法是任何集成任务都先把端到端的最小闭环跑通再用灰度和监控去迭代体验。Nisuform 让这个最小闭环的实现成本低到了一个很舒服的位置——从注册账号到 agent 真正能把表单链接发给用户我一般不超过半小时。剩下的时间都在打磨那些让用户体验更好的细节而那些恰恰是产品拉开差距的地方。
返回列表