我做了一个“以上海为场景的全天行为模拟 Agent”系统


最近我做了一个挺有意思的系统。
它不是普通聊天机器人,而是一个会“以第一视角规划自己一天”的时空行为模拟 Agent。

用户给它一句话,比如:

请你以第一视角规划自己今天在上海的一整天。我是独居上班族,喜欢安静、咖啡和散步,希望安排早餐、上班、午餐、下班后的放松活动以及回家。

系统返回的不是一句泛泛而谈的建议,而是:

  • 一整天的活动链路
  • 早餐、通勤、工作、午餐、傍晚活动、购物、回家等节点
  • 每一段活动的时间、地点、出行方式
  • 每一步背后的 reasoning
  • 实际调用地图工具后的 POI 和路径结果

最终它更像一个“具身化的城市行为 Agent”,而不是一个只会嘴上回答的聊天模型。

这个系统到底在做什么

一句话概括:

让一个带有人物画像的 Agent,在上海这个真实城市环境里,为“自己”生成完整的一天活动规划。

这里有几个关键词。

1. 第一视角

不是“我替你规划”,而是:

  • 我今天几点起床
  • 我去哪里吃早餐
  • 我怎么去上班
  • 我中午在哪里吃饭
  • 我下班后去哪里散步
  • 我什么时候回家

这会让结果更像一个可模拟、可观察的行为主体。

2. 全天规划

不是只回答“附近有什么咖啡馆”,而是把一天串起来:

  • 居家准备
  • 早餐
  • 通勤
  • 上午工作
  • 午餐
  • 下午工作
  • 傍晚活动
  • 购物
  • 回家
  • 居家休息

也就是说,输出的是一个 day_plan.activities[],而不是一个单点 recommendation。

3. 真实城市环境

这个系统不是在真空里规划,它会接高德地图能力:

  • 搜索 POI
  • 获取地点候选
  • 做步行/通勤路径规划
  • 根据当前位置、时间、天气决定活动可达性

这一步很关键。
如果没有地图和物理环境约束,所谓“一天规划”很容易变成空想。

为什么我要做这个系统

我对这类系统一直有一个判断:

真正有意思的 Agent,不是会说话,而是能在带约束的环境里做连续决策。

很多 demo 的问题在于:

  • 它看起来很聪明
  • 但没有环境
  • 没有时间预算
  • 没有空间约束
  • 没有多步状态转移

于是它本质上还是一个 prompt engineering 展示页。

而我想做的是一个更靠近“行为模拟”的东西:

  • 有用户画像
  • 有出发地 / 工作地
  • 有 mobility modes
  • 有天气
  • 有预算和偏好
  • 有城市地图
  • 有一天内部的多步活动结构

这样它才像一个真正的城市生活 Agent。

系统整体架构

整个系统我拆成了三层:

1. 前端:Next.js

负责:

  • 邮箱验证码登录
  • 用户画像录入
  • 对话界面
  • 结果展示
  • 兑换码使用
  • 管理配置入口

前端不是简单地把 JSON 打出来,而是把结果拆成:

  • 主回答
  • 思考摘要
  • 工具调用
  • 规划结果时间轴

这样可读性会强很多。

「前端整体结构图」

2. Web 服务层:用户、会话、额度、消息

这一层主要做业务管理:

  • 用户登录态
  • Session cookie
  • Prisma + PostgreSQL
  • 用户画像存储
  • ChatSession / ChatMessage 存储
  • remainingCredits 次数扣减
  • redemption code 兑换逻辑

这一层本质上是“平台层”。

3. Python Agent 服务层

这一层是核心:

  • 接收用户画像与对话请求
  • 调用 LLM 生成活动骨架
  • 调用高德地图搜索候选地点
  • 做路径规划与时间预算
  • 组装成全天 day_plan
  • 返回 reasoning、tool traces 和结果

这一层才是真正的 Agent runtime。

关键数据流

一个请求大概是这样跑的:

  1. 用户登录系统
  2. 填写画像
  3. 发出一句“请你规划自己今天的一天”
  4. Web 端把消息发给 Agent service
  5. Agent 先生成全天活动 outline
  6. 再逐步把早餐、午餐、傍晚活动等落到真实地点
  7. 中间调用地图服务搜索 POI 和路径
  8. 最终返回完整的 day_plan.activities
  9. Web 前端将其渲染成时间轴

这个过程里最重要的是:

先有语义层的活动意图,再有物理层的地点与路径落地。

「请求流转时序图」

我踩过的几个关键坑

这个项目真正花时间的,不是把页面搭出来,而是把系统从“能跑”变成“像样”。

坑一:LLM 输出太自由,地图检索根本接不住

一开始模型会输出这种东西:

  • coffee break
  • cafe
  • quiet

这对语言模型来说很自然,但对地图检索来说很不友好。
高德更适合的是:

  • 咖啡厅
  • 餐厅
  • 早餐店
  • 公园
  • 商场

如果不做约束,模型输出和工具输入会发生语义断裂。

我后来的处理方式

我做了两层收束:

  1. 在 reasoning schema 层约束 category 枚举
  2. 在地图 adapter 层加中英映射和兜底归一化

比如把:

  • cafe -> 咖啡厅
  • shopping -> 商场
  • park -> 公园

这一步做完以后,POI 命中率一下就上来了。

坑二:只做单轮活动规划,根本不够“Agent”

最初版本只会回答:

  • 我现在想去哪里喝咖啡
  • 我中午去哪吃饭

这个能力本身没错,但它还是“单点推荐”。

而用户真正想看的,是一个具有连续性的日常行为体。
于是我把输出从单个 plan 升级成了:

  • day_plan.summary
  • day_plan.activities[]

也就是让它先规划“全天活动骨架”,再逐步落地每个节点。

这是这个系统从“聊天机器人”变成“全天行为 Agent”的分水岭。

坑三:地图路径返回不稳定,通勤会退化成 walk

这个问题很真实。

有些情况下,通勤路径接口不稳定,或者返回空结果,系统会 fallback。
结果就是:

  • 早上从早餐店去公司
  • 晚上从商场回家
  • 明明应该是 transit
  • 最后却显示成了 long-distance walk

从系统角度讲这不是崩溃,而是 fallback。
但从产品体验上看,这会很怪。

目前的策略

我现在做的是:

  • transit 不可用时再 fallback
  • 前端展示最终实际使用的 route mode
  • 避免“工具 trace 是 walk,页面却写 transit”的不一致

这已经比一开始稳定很多,但这部分还可以继续优化。

坑四:前端如果只是把 JSON 打出来,系统会显得很廉价

最开始结果直接展示大块 JSON,问题非常明显:

  • 用户看不下去
  • 聊天感很弱
  • 规划结果像调试台
  • trace 虽然全,但没有层次

后来我把前端重构成了更偏 ChatGPT / Claude / OpenWebUI 的方式:

  • 左侧会话栏
  • 右侧对话主区
  • 输入框固定在底部
  • 消息区内部滚动
  • 发送中显示占位状态
  • 规划结果改成 timeline 卡片
  • 思考摘要 / 工具调用折叠展示

这一步对“像不像一个真正产品”的影响非常大。

「旧版前端截图」
「新版前端截图」

坑五:额度扣减必须是事务内原子完成

这个坑非常典型。

如果用户连点两次发送,而前端又没有正确 disable,后端如果只是简单判断:

  • remainingCredits > 0

再分别处理两个请求,就会出现:

  • 看起来点了一次没反应
  • 实际发了两次
  • 最后生成两条结果
  • 次数扣减异常

我最后的处理

把次数扣减放进数据库事务里,做原子保留:

  • 只有成功 reserve 到额度,才允许继续创建 assistant message
  • 否则直接返回不足

同时前端发送中锁定按钮,显示“生成中...”。

这才算把“额度系统”做对。

前端我后来做了哪些修正

这部分其实比我一开始预期花的时间更多。

1. 验证码发送倒计时

登录页加入了 60s resend cooldown。
这样不会出现用户疯狂点“发送验证码”的情况。

2. 画像弹窗可关闭

一开始画像弹窗只能保存,不能退出,非常别扭。
后来补齐了:

  • 右上角关闭
  • 点击空白关闭
  • Esc 关闭

这类细节很小,但如果不处理,整个系统会显得非常粗糙。

3. 聊天输入区默认模板

我把这句直接写成了默认模板:

请你以第一视角规划自己今天在上海的一整天。我是独居上班族,喜欢安静、咖啡和散步,希望安排早餐、上班、午餐、下班后的放松活动以及回家。

这样用户一打开,不需要思考怎么提问,直接就能测到系统的核心能力。

4. 聊天结果时间轴化

day_plan.activities 不再直接 raw JSON 展示,而是变成:

  • 时间
  • 活动类型
  • 地点
  • 出行方式
  • narrative

这才像一个“全天活动计划”。

登录、画像、对话、兑换码,整条链路是怎么打通的

这个系统我特意没有走第三方复杂认证,而是用了最轻的方式:

登录

  • 邮箱验证码登录
  • 首次登录后给默认免费额度
  • session cookie 维持登录态

画像

  • 用户手动填写
  • 或随机生成上海范围内画像
  • 包括:
    • 年龄
    • 性别
    • 家庭环境
    • 出行方式
    • 收入水平
    • 居住地
    • 工作地
    • 初始记忆
    • 活动偏好

对话

  • 发送一句第一视角日程请求
  • Agent service 返回:
    • answer
    • reasoning_summary
    • tool_traces
    • plan / day_plan

兑换码

我最后没有强依赖后台页面,而是直接提供了终端脚本生成:

cd /opt/urban-llm-agent-sim/web
npm run redeem:generate -- --count 1 --credits 20

这表示:

  • 生成 1 个兑换码
  • 这个兑换码可以增加 20 次对话额度

如果要 5 个码,每个 3 次:

cd /opt/urban-llm-agent-sim/web
npm run redeem:generate -- --count 5 --credits 3

这个方式对实际运维反而更直接。

部署这套系统,我最后采用了什么方案

部署上我最后选的是一套很朴素但稳定的组合:

  • Next.js 跑在 127.0.0.1:3001
  • Python agent service 跑在 127.0.0.1:8010
  • PostgreSQL 跑在本机
  • nginx 负责反向代理
  • systemd 负责守护两个服务

域名是:

这个组合的好处是:

  • 足够简单
  • 维护成本低
  • 出问题容易排查
  • 没有把复杂度引入到容器编排层

对这个阶段的产品来说,我认为这是正确选择。

我现在对这个系统的判断

如果问我现在它处于什么阶段,我会这么说:

它已经不是一个玩具 demo 了,但也还没有到大规模产品化的程度。

它已经具备了这些能力:

  • 第一视角全天活动生成
  • 基于真实地图环境的地点与路径落地
  • 用户画像驱动
  • 有完整登录、会话、次数、兑换码逻辑
  • 有基础可用的聊天产品形态

但它也还有明显的下一步:

  • 通勤路径策略继续优化
  • 更强的全天行为一致性
  • 更自然的 narrative
  • 更丰富的活动类型
  • 更稳定的工具 fallback 策略
  • 更好的可视化表达

也就是说,它已经到了一个很有研究和产品价值的阶段。

如果你也想做类似系统,我的建议

1. 先做“受约束的 Agent”,不要先追求大而全

先把:

  • 人物画像
  • 时间结构
  • 地图工具
  • 活动序列

这四件事做通,比先接十几个工具更重要。

2. 不要让模型输出直接裸接外部工具

LLM 的自然语言输出和工具参数之间,必须有一层“可控语义层”。

3. 前端不要只做个消息框

这种系统如果结果结构复杂,UI 就必须认真设计。
否则你做的是很复杂的系统,用户看到的却像个调试页面。

4. 所有“次数/额度/兑换码”都要按交易系统来做

这类东西只要写得松一点,线上一定出问题。
一定要原子化、事务化。

最后

做这个系统最有意思的一点,不是“模型会不会说得更漂亮”,而是:

当模型、地图、时间、空间、人物画像和产品界面真正接起来之后,Agent 才开始像一个可观察、可验证、可迭代的行为系统。

这也是我做这套系统时最想验证的一件事。

它不是单纯的聊天网页。
它更像一个放在城市里的、拥有日常生活逻辑的模拟个体。

而这,才是我觉得 Agent 真正有意思的地方。

「最终系统整体截图」
「全天规划时间轴细节图」
「系统架构图」
「部署结构图」

附:几个我常用的测试提示词

全天第一视角

请你以第一视角规划自己今天在上海的一整天。我是独居上班族,喜欢安静、咖啡和散步,希望安排早餐、上班、午餐、下班后的放松活动以及回家。

工作日完整规划

请你以第一视角规划自己今天完整的一天,包含早餐、通勤、工作、午餐、傍晚活动和回家。

更强调偏好与原因

请结合我当前画像,安排一个工作日的一整天,并说明每一段活动为什么这样安排。

附:兑换码终端生成命令

生成 1 个码,增加 20 次:

cd /opt/urban-llm-agent-sim/web
npm run redeem:generate -- --count 1 --credits 20

生成 5 个码,每个增加 3 次:

cd /opt/urban-llm-agent-sim/web
npm run redeem:generate -- --count 5 --credits 3

声明:ZyWeb|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 我做了一个“以上海为场景的全天行为模拟 Agent”系统


无论 Bug 还是想法,欢迎 Push 过来