最近我做了一个挺有意思的系统。
它不是普通聊天机器人,而是一个会“以第一视角规划自己一天”的时空行为模拟 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。
关键数据流
一个请求大概是这样跑的:
- 用户登录系统
- 填写画像
- 发出一句“请你规划自己今天的一天”
- Web 端把消息发给 Agent service
- Agent 先生成全天活动 outline
- 再逐步把早餐、午餐、傍晚活动等落到真实地点
- 中间调用地图服务搜索 POI 和路径
- 最终返回完整的
day_plan.activities - Web 前端将其渲染成时间轴
这个过程里最重要的是:
先有语义层的活动意图,再有物理层的地点与路径落地。
「请求流转时序图」
我踩过的几个关键坑
这个项目真正花时间的,不是把页面搭出来,而是把系统从“能跑”变成“像样”。
坑一:LLM 输出太自由,地图检索根本接不住
一开始模型会输出这种东西:
-
coffee break -
cafe -
quiet
这对语言模型来说很自然,但对地图检索来说很不友好。
高德更适合的是:
- 咖啡厅
- 餐厅
- 早餐店
- 公园
- 商场
如果不做约束,模型输出和工具输入会发生语义断裂。
我后来的处理方式
我做了两层收束:
- 在 reasoning schema 层约束 category 枚举
- 在地图 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





Comments | NOTHING