1483 words
7 minutes
Android app开发导览

写给后端开发者:把一个”个人 AI 助理”从代码层面拆开看——它不是网站,而是一个 7×24 常驻的事件驱动守护进程。本文用一个真实项目(mini-agent)讲清楚它如何运转,并逐条对比它和传统 Web 开发的本质差异。

一、这个 App 到底是什么#

它不是一个”网站”,而是一个 7×24 常驻后台的守护进程(daemon),以 systemd user service 运行。核心入口是 agent.pyasync def main(),跑一个 asyncio 事件循环,直到收到 SIGTERM/SIGINT 才退出。

一句话概括它做的事:“很多入口把消息塞进来 → 中枢分发 → LLM/子进程/SQLite 处理 → 多个出口把结果送出去”

真实数据流(来自代码)#

┌── Telegram 轮询 (channels/telegram.py)
入口 (channels) ─┼── Web API aiohttp (channels/web.py) ──┐
├── inotify 文件监控 (BlogFileHandler) │
└── APScheduler 定时器 (core/scheduler.py) │
┌─────────────────────────────────────┐
│ EventBus (core/event_bus.py) │
│ Event(type, data) 发布/订阅 │
│ publish() → 同步调 handler │
└─────────────────────────────────────┘
│ │ │
"message_received" "cron_tick" "file_changed"
▼ ▼ ▼
MessageHandler CronDispatcher BlogSyncTask
│ │ │
▼ ▼ ▼
IntentRouter/ TodoReminder git add→commit→
ConversationMgr ScheduledMsg pull --rebase→push
CodeTaskExecutor (tasks/code_task.py)
┌──────┴──────┐
▼ ▼
ConversationMgr PiRpcClient
(LangGraph + (起一个 pi 子进程,
SQLite 检查点) JSONL 协议 stdin/stdout,
cwd 隔离在 pi-workspace)

几个关键组件(一句话版)#

组件文件它是什么
EventBuscore/event_bus.py中枢神经。subscribe(type, handler) + publish(Event)。handler 是同步的,异步活儿用 asyncio.ensure_future 派发。异常隔离(一个 handler 挂了不影响别人)。
ConversationManagercore/conversation.py用 LangGraph StateGraph + AsyncSqliteSaver 做多轮对话的持久化检查点;还有”记忆压缩”——超过 token 阈值就用 LLM 把旧消息总结成摘要。
CodeTaskExecutor / PiRpcClienttasks/code_task.py / tasks/pi_rpc.py真正干”代码任务”的地方:起一个 pi --mode rpc --no-session 子进程,通过 stdin 发 JSONL 命令、stdout 读响应(64KB 手动切分避免行缓冲上限)。
CronDispatchertasks/cron_dispatcher.pycron_tick 事件路由给注册的 TaskActor(todo 提醒、定时消息、记忆压缩、AI 日报、双向同步)。
WebChannelchannels/web.py内嵌 aiohttp server。Bearer token 认证(24h TTL)、SSE 流式、本地↔云双向同步的 /sync/* 端点。
TelegramChannelchannels/telegram.py另一个入口,long polling,有 watchdog 自动重启死掉的轮询。

一个完整请求的生命周期(以 /query 为例)#

  1. 用户在 Telegram 发 /query 怎么写一个快速排序
  2. TelegramChannel 收到 → event_bus.publish("query_command", {...})
  3. MessageHandler 订阅了这个事件 → 调 _handle_query()
  4. ConversationManager.get_context_string() 取上下文 → 拼到 prompt 里
  5. CodeTaskExecutor._run_pi()PiRpcClient.prompt_stream() → 起子进程、流式读 text_delta 事件
  6. 结果用 log_result("[query]", text) 落到 data/logs/result-YYYY-MM-DD.log
  7. _chunk_message() 切成 ≤4096 字符块(Telegram 限制)回发
  8. (非 topic 模式下)asyncio.ensure_future(blog_actor.handle_single(...)) 异步判断要不要生成博客

整个链路里,没有一个”HTTP 请求→响应”的同步框架在主导,是事件驱动的。

二、它和网站开发的核心区别#

作为后端开发,你最熟悉的是”请求-响应”模型。这个 app 几乎在每个轴上都不同:

维度传统网站(你熟悉的)这个 AI 助理
运行模型进程按请求拉起(gunicorn/uwsgi worker),请求完即走。或常驻但无状态。单进程常驻,asyncio 事件循环永不退出,靠 systemd 守护。状态全在内存里(会话、连接、offset)。
主导范式HTTP 请求-响应(同步、短生命周期)事件驱动 / pub-sub(EventBus)。HTTP 只是众多入口之一,且 WebChannel 本身也是嵌在同一个 event loop 里的 aiohttp server。
“后端”的边界后端 = 你的业务逻辑进程。外部依赖是 DB/缓存。后端本身要反过来调用 LLM 和起子进程(pi RPC)。你的进程既是 server 又是 client。
延迟特征毫秒~秒级。一个请求等一个响应。LLM 调用动辄几十秒~几分钟。所以大量流式(SSE)+ fire-and-forgetasyncio.ensure_future)。不能阻塞 event loop。
状态管理Redis / DB,进程无状态好横向扩展SQLite + LangGraph checkpoint + 内存 dict,单机单实例,状态就在进程里。_sessions_active_threads_topic_log_offsets 都是内存结构。
入口多样性几乎只有 HTTPHTTP(Web) + Telegram long-polling + inotify 文件事件 + cron 定时器。同一段处理逻辑被四种入口触发
关注点并发、事务、幂等、缓存、水平扩展① 别阻塞 loop ② 子进程隔离(cwd 隔离防误删源码)③ 长任务超时(pi 20 分钟)④ 跨实例状态同步(本地↔云 last-write-wins)⑤ 消息分片(4096 限制)
部署形态容器编排、多副本、负载均衡systemd user service 跑在一个 Linux 用户下;加密凭据用 LoadCredentialEncrypted=;本地+云双实例靠 cron 互相同步。
错误模型500 给用户看,重试靠客户端handler 异常被 EventBus 吞掉只记日志(不影响其他订阅者);轮询死了有 watchdog 自愈。容错优先于事务正确性
安全模型认证授权、SQL 注入、CSRF仍是这些,但多了:子进程能 cd 任何地方(软隔离不是硬隔离)、凭据用 systemd encrypted credentials、云上 nginx + loopback + 444 隐身模式。

一句话总结这个差异#

网站 = “一个进程处理很多并发请求,每个请求独立且短暂”。 这个助理 = “一个常驻进程是主体本身,外界事件只是喂给它的输入;它内部以事件总线解耦多个异步生产者-消费者,并主动调用 LLM/子进程产生输出。”

换句话说,做网站你是在写”被调用的服务”;做这种助理,你是在写一个”主动的、有状态的、多入口的数字员工”。思维方式从 request handler 转到 event loop + actor

三、想动手做类似的,先吃透三个概念#

  1. asyncio 事件循环 + asyncio.ensure_future:怎么在不阻塞的前提下并发跑多个慢任务。
  2. pub-sub 事件总线:为什么不用直接函数调用而要解耦——因为多入口 + 可插拔。
  3. LLM 作为后端依赖的延迟处理:流式 SSE、超时、上下文压缩——这是 AI 应用区别于传统后端最实质的工程难点。
Android app开发导览
https://sgjki547.top/posts/android-app开发导览/
Author
SGJki
Published at
2026-07-28
License
CC BY-NC-SA 4.0