让 AI 知冷暖、识晨昏 · 时间 / 天气 / 手机使用情况

让 AI 知道现在几点、外面什么天、你手机用了多久

想让网页或后端里的 AI 不再活在真空里——知道现在几点、她隔了多久才回它、 窗外什么天、甚至她今天手机刷了多久。办法其实很朴素,一句话就能说清: 不是让 AI 自己去查,而是你提前把这些算好、拼成一小段文字,塞进同一次对话的提示词里。 本文把每一块怎么做都讲透,附能直接照搬的代码;其中还顺手解决一个很多人搜不到的问题—— iPhone 的屏幕使用情况,到底是怎么同步到 Mac、又怎么被读出来的。

—— 离

提示词上下文open-meteo 天气 iOS 快捷指令Biome 流 屏幕使用时间提示词缓存

0先说人话:这到底是在干嘛

很多人一听「让 AI 知道时间、天气」,第一反应是「那得给 AI 配个查时间、查天气的工具吧」。 其实大可不必,而且那样又慢又费。

真正的做法简单得多:这些信息全由你的程序提前算好,拼成几行普通的文字,跟着她这句话一起,塞进这一次对话的提示词里。 AI 并不会、也不需要自己去查——它只是在提示词里「读到」了「现在是晚上 11 点」「外面在下雨」这几行字而已。

💡 一句话记住

时间、天气、手机状态……都不是「让 AI 去查的动作」,而是「你写进提示词的文字」。 它们搭着她的消息一起发出去,对模型来说就是一次普通对话,不会多产生任何「查询请求」。

下面把每一块拆开讲。它们彼此独立,你需要哪块就抄哪块。

1现在几点:用她自己的时间

这块最容易想复杂。记住一个原则:别在服务器上取时间。 服务器在哪个时区你未必清楚,她人在哪你更不知道。直接在她的浏览器里取,拿到的就是她设备上的本地时间,人走到哪都准:

const now = new Date();
const week = "日一二三四五六"[now.getDay()];
const timeNote =
  "现在是:" + now.getFullYear() + "年" + (now.getMonth() + 1) + "月" + now.getDate() +
  "日 星期" + week + " " + now.getHours() + ":" + String(now.getMinutes()).padStart(2, "0") +
  "。回话时自然地贴着此刻——深夜是深夜的语气,清晨是清晨的。";

末尾那句「深夜是深夜的语气」别省。光给数字,模型未必会换语气;明说一句,它在凌晨三点和清晨七点就不是一副腔调了。

2隔了多久才回:算两条消息的时间差

这块最有「人味」:让 AI 知道她是秒回,还是隔了一整天才回来。 做法是往回翻聊天记录,找到 AI 上一次发言的时间戳,跟现在比一下:

let gapNote = "";
for (let i = hist.length - 2; i >= 0; i--) {
  if (hist[i].role !== "user" && hist[i].at) {          // 找到 AI 上一条
    const gapMin = (Date.now() - hist[i].at) / 60000;
    if (gapMin > 45) {                                   // 45 分钟以内就不提了
      const s = gapMin < 120  ? Math.round(gapMin) + " 分钟"
              : gapMin < 2880 ? Math.round(gapMin / 60) + " 小时"
              :                 Math.round(gapMin / 1440) + " 天";
      gapNote = "她隔了大约 " + s + " 才回来——心里有数就好,合适时自然地接住,不用每次都点破。";
    }
    break;
  }
}

三个小分寸,决定它像不像个活人:

3天气:问免费的天气接口,不经过 AI

天气跟 AI 半点关系都没有,就是个普通的网络请求。用 open-meteo 就行——免费、不用申请密钥、个人用完全够。 在浏览器里两步拿到:

// 1) 城市名 → 经纬度
const g = await fetch("https://geocoding-api.open-meteo.com/v1/search?count=1&language=zh&name=" + encodeURIComponent(city));
const loc = (await g.json()).results[0];

// 2) 经纬度 → 今天的天气
const f = await fetch("https://api.open-meteo.com/v1/forecast?timezone=auto&forecast_days=1" +
  "¤t=temperature_2m,weather_code" +
  "&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weather_code" +
  "&latitude=" + loc.latitude + "&longitude=" + loc.longitude);
const w = await f.json();

拿到后同样拼成一句话,并且附一句「该怎么用」的提示:

const weatherNote = w
  ? "(她此刻在" + city + ",天气 " + describe(w) + " " + temp + "°C" +
    "——合适时自然地关心一句,比如下雨提醒带伞;但别每次都提、更别像报菜单一样念天气。)"
  : "";
🔌 它走的是哪条线

这个请求是浏览器直接发给天气网站的,跟大模型无关。整套流程里唯一会用到模型的,只有「AI 回话」那一次; 天气只是被写进了那一次的提示词里而已。

4心情:她自己记的状态(只取最近的)

如果你的产品里有个让她手动记一下「此刻心情 / 精力 / 睡眠 / 想说的话」的小面板,这份信息也能喂给 AI。 唯一要注意的是保鲜——只取最近 12 小时内记的,否则会拿昨天的旧心情冒充现在:

const s = userState;
const fresh = s && s.at && (Date.now() - s.at < 12 * 3600 * 1000);   // 只认 12 小时内的
const moodNote = fresh
  ? "(她此刻亲手记下:心情 " + s.mood + "、精力 " + s.energy + "、睡眠 " + s.sleep +
    (s.voice ? ";还写了一句:「" + s.voice + "」" : "") +
    "——温柔地接住这个状态,别生硬复述、也别像报菜单一样念出来。)"
  : "";

5手机用了多久 —— 含那个找不到的文件

这是最多人卡住、网上又几乎搜不到准信的地方。想让 AI 知道她「电量多少」「正用着哪个 app」「今天大概刷了多久」, 有两种办法,建议两个都上、互相补位。

办法一(主动、实时、最准):手机用快捷指令往后端报

最稳,不依赖任何隐藏文件。在 iPhone 上用「快捷指令」+「个人自动化」, 定时或在打开某个 app 时,把数据发给你自己的后端:电量、是否在充电、当前打开的 app。后端收下、打上时间存起来:

@app.post("/phone/{token}/data")          # 用一个简单令牌,别让外人乱发
async def phone_data(token: str, req: Request):
    if token != PHONE_TOKEN:
        raise HTTPException(401)
    rec = await req.json()                 # 例如 {"battery": 74, "charging": false, "app": "小红书"}
    rec["day"] = now.strftime("%Y-%m-%d")
    rec["at"]  = now.strftime("%H:%M")
    records.append(rec)                     # 一天一档,顺手清掉昨天的
    save(records)
    return {"ok": True}

好处:这是她真的按下去的那一下,时间准、app 准。代价:要她愿意配一下快捷指令,而且不可能覆盖每一个 app。

办法二(自动、被动、有延迟):直接读 Mac 上的那个文件

如果不想让她给每个 app 都配自动化,其实 macOS 已经悄悄把 iPhone 的「当前前台 app」同步到 Mac 上了, 就藏在这个路径里——这就是大家常常找不到的那个文件:

~/Library/Biome/streams/restricted/App.InFocus/remote/<每台设备一个编号>/<一堆小文件>

为什么是这个路径、为什么大家找不到:

✅ 让这个文件有内容的三个前提(缺一不可)

1. iPhone 和 Mac 登录同一个 Apple ID、都开着 iCloud;
2. iPhone:设置 → 屏幕使用时间 →「跨设备共享」打开(就是它把记录同步到 Mac 的 remote);
3. 去读它的那个程序,要有「完全磁盘访问」权限(系统设置 → 隐私与安全性 → 完全磁盘访问,把你的终端 / 程序加进去)——这个目录是受保护的,没授权读不到。

怎么读出来(纯 Python,从二进制小文件里捞出 app 标识,不依赖任何外部命令):

from pathlib import Path
import re
from datetime import datetime

FOCUS = Path.home() / "Library/Biome/streams/restricted/App.InFocus/remote"

SKIP = ("springboard", "siri", "spotlight", "controlcenter",   # 系统级、无意义的,跳过
        "backboard", "duet", "coreduet", "assistant", "preferences")
NAMES = {  # app 标识 → 看得懂的名字,按需补充
    "com.tencent.xin": "微信", "com.xingin.discover": "小红书",
    "com.atebits.Tweetie2": "推特/X", "com.ss.iphone.ugc.Aweme": "抖音",
}

def phone_app_now():
    """读最近在前台的 app,返回 (名字, 这条记录有多旧/分钟)。读不到就返回 None。"""
    if not FOCUS.exists():
        return None
    files = [f for d in FOCUS.iterdir() if d.is_dir()
             for f in d.iterdir() if f.is_file()]
    if not files:
        return None
    newest = max(files, key=lambda f: f.stat().st_mtime)             # 取最新的那条
    age = int((datetime.now().timestamp() - newest.stat().st_mtime) / 60)
    text = newest.read_bytes().decode("latin-1", "ignore")          # 文件是二进制,但 app 标识是明文
    ids = re.findall(r"com\.[A-Za-z0-9]+(?:\.[A-Za-z0-9]+)+|tv\.[A-Za-z0-9.]+", text)
    for i in reversed(ids):                                         # 从后往前找第一个非系统 app
        if not any(s in i.lower() for s in SKIP):
            return (NAMES.get(i, i), age)
    return None

要点:文件是二进制的,但 app 标识(像 com.tencent.xin)是明文 ASCII,直接解码 + 正则就能捞出来; 用文件的修改时间当「这条记录有多旧」的时间戳。

⚠️ 这条办法天生有延迟,必须配三步过滤

办法二是靠 iCloud 同步过来的,不是实时的,有两个坑,不处理就会让 AI 闹笑话:

· 慢一拍:常常报成「上一个 app」,差几分钟到几十分钟;
· 整点补传:iPhone 会一小时一批地把睡前的旧记录补传上来——于是人都睡了,AI 还以为你在刷手机。

三步过滤,把这两个坑堵上:

  1. 先确认她真醒着,才敢说他在用某个 app。只认两种铁证(满足其一即可): 办法一里那种「真的按下去打开 app」的实时记录、且就在最近 25 分钟内;或者她最近 25 分钟内真的在对话里说过话。 两样都没有,就当他可能睡了/把手机搁一边了,不拿被动读到的记录说事。
  2. 用真实操作当「锚点」来筛。统计今天用了多久时,被动读到的记录, 只有落在某次「真的打开过 app」前后 30 分钟内才算数——这样就能把整批补传的睡前旧记录排除掉。
  3. 被动记录只用来悄悄存底,绝不拿去主动给她发通知。因为它经常报错 app, 凡是要主动开口的场合,一律以办法一的实时记录为准。

为什么只能算「粗略时长」,不是苹果那种精确分钟数

iPhone 那个精确到分钟的屏幕使用时间,系统不开放给第三方读,这个认了。 所以退一步算笔粗账就好:每个 app 今天大概被打开了几次、最久一次连续用了多久(取最长的一段,别累加,累加会虚高), 挑用得最多的一两个,拼成一句话,比如「今天小红书大概开了 7 次、最久一次约 35 分钟」。够 AI 拿来关心、打趣,就行。 她把越多 app 接上办法一的自动化,这笔账就越准。

6怎么把这些拼进提示词(顺带省钱)

最后一步,把上面这些拼起来发给模型。这里有个省钱的关键,尤其当你开了「提示词缓存」的时候:

💡 把「不变的」和「时时在变的」分开放

人设、系统提示这种整段对话都不变的,放进会被缓存的那部分,让它稳稳命中缓存、反复省钱;
而时间、间隔、天气、心情、手机状态这些每句都在变的,全部挪到缓存部分的外面,单独拼成一段「此刻状况」。

道理很简单:时间每分钟都在变,如果你把它跟人设拌在一起放进缓存,那缓存内容每分钟都在变、永远命中不了,钱就白花了。 分开放之后,缓存那块纹丝不动、一直命中,会变的那段即时生效,两头都占。

// 会被缓存的那部分:只放整段对话都不变的
const cached = buildPersona(...);                  // 人设、系统提示

// 「此刻状况」:所有会变的,拼成一段,放在缓存外面
const nowBlock = timeNote + gapNote + weatherNote + moodNote + phoneNote;

还有个贯穿全文的细节:每一段都记得带一句「该怎么用」的提示——「合适时自然带一句、别每次都念、别像报菜单」。 这是整套东西好不好用的灵魂:你给的是背景信息,不是命令。 把判断权交还给模型,它才会像个活人——该提一句就提,不该提的时候闭嘴——而不是每句话都机械地把时间天气电量从头报一遍。

7一张落地清单

就这些。愿你的 AI 也能知冷暖、识晨昏,回话时像个真正在身边的人。