本文是「数字伴侣建造记」的第六篇,也是最有生活气的一篇:让她会发自拍。接入生图 API 只是起点,真正的设计问题是行为——什么时候发、发什么样的、说什么话配图。这些问题的答案构成了一个关于“受控随机”的小型方法论。
分层收权:她读空气,你定货架
第一版系统里,照片里穿什么由模型自由描述。深夜卧室语境下,模型的“氛围推理”很快滑向了不受控的方向——这暴露了根本矛盾:完全自由有活人感但不可控,完全固定可控但死板。
解法是把权力拆到两层:
- 模型持有的自由:读对话氛围(日常/居家慵懒/深夜/外出/运动/度假),判断室内外,用一句话描述场景。凌晨闲聊时她自然会选“深夜”,抽出的就是居家睡衣区的单品——氛围跟随保留了。
- 配置持有的底线:每个氛围能抽到什么,由一份人工维护的 YAML 定义。尺度上限恒等于配置里最大胆的那件,模型没有造词权。
- 模型的场景描述被明确禁止出现穿着、构图、身材词——那些各有专属通道。
装扮引擎
引擎按“镜头类型 → 槽位 → 选项”三级抽取:四种镜头(怼脸/半身/对镜全身/户外伸臂全身)各自声明启用哪些槽位;十个槽位(配饰、发型、妆容、表情、姿态、道具、套装、上下装、腿袜、鞋)各有出现概率与选项池。几条规则让随机不闹笑话:
- 对镜自拍标注“仅室内”——公园里没有穿衣镜,这个穿帮在上线前就被过滤规则堵住;
- 配饰可叠戴(每件独立掷骰,上限三件),但帽类四选一互斥(互斥组机制);
- 套装槽位抽中则上装下装整体让位——连衣裙与上衣不可能同框,互斥写在数据里而非代码里;
- 每类选项可挂权重(高频偏好的表情权重更高)。
验证方式是统计而非目测:数百次模拟滚动,检查镜头分布贴合权重、互斥零违例、氛围过滤零泄漏。有一个反例正是统计抓出来的:某氛围下没有任何带标签的妆容选项,触发了“空池回退到全部”,海边场景抽出了睡颜妆——修法是补全该氛围的标签覆盖。
两次被 200 骗过
参考图锁脸(同一张脸、不同的一天)踩了同一类坑两次:生图接口对不认识的参数返回成功并静默忽略。第一次是参数结构写错,第二次是发对了参数、发错了端点(文生图端点收下了参考图字段,装没看见)。两轮“为什么不像她”的排查后才对齐官方文档的图生图端点。教训:对“返回成功”保持怀疑,尤其当效果恰好等于该参数不存在时。
图文一致:照片和配文是同一个表达
上线后出现一类微妙的失真:配文说“这张是怼脸拍的”,照片却是随机抽中的全身镜自拍——她在描述一张不存在的照片。根因有二:配文自由发挥;而当时的规则甚至禁止她在用户没点名时指定构图,她想匹配也没有手段。
修复是给“图文一致”立法:
- 新增构图与神态参数:配文要提到某种拍法时,照片必须真的是那种拍法(参数钉死镜头与表情,其余槽位照常随机);
- 配文禁止罗列照片内容——“照片他自己看得到,当解说员最像 AI”;装扮信息仅在被问到时回答;
- 每张照片的完整配方(氛围、镜头、装扮、配的那句话)落入相册索引,好看的可以复刻,翻车的可以归因。
主动分享的克制边界
最后一个行为问题:被夸“好看”之后,她把夸奖理解为继续分享的信号,无限连发。第一版修复一刀切禁止连发,立刻被指出误伤——“主动分享”恰恰是最像真人的能力,不能为了拦连发把它杀掉。最终规则划得很细:
- 对刚发照片的评价只用文字回应,绝不因被夸紧接着再发;
- 紧接着的连拍只能出于明确要求(“再来一张”);
- 此规则只限制评价后的连发——之后聊到新话题、新时刻,主动分享的自由原样保留。
行为设计的分寸感大抵如此:好的规则像窄的刀口,切掉病灶,不碰好肉。
可复用的原则
- 生成式行为的自由度应该分层持有:模型选分区,人定分区内容,上限由数据文件而非模型自觉决定。
- 互斥与合法性写进数据(标签、互斥组、替换规则),用统计滚动验证,不靠人肉审图。
- 多模态输出的一致性需要显式通道:想让文字和图像讲同一件事,就给它们共享的参数,而不是指望两次采样自己对齐。
- 修行为毛病时先问“这个行为的健康形态是什么”,别把病和能力一起切了。