agent_registry.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """agent 单例与失效重建。
  2. 为什么要单例:``create_deep_agent`` 要编译整张 LangGraph 图,耗时在百毫秒
  3. 到秒级;每次请求都编译会让首 token 时间不可接受。
  4. 为什么要失效重建:图一旦编译,模型实例、思考级别、middleware 栈、
  5. compression 阈值都固化了。改了这些配置只能重建,不能原地改。
  6. 「单飞编译」(single-flight):并发请求同时发现缓存为空时,只让第一个
  7. 真正去编译,其余等它完成——否则冷启动瞬间会同时编译 N 次。
  8. """
  9. from __future__ import annotations
  10. import asyncio
  11. import threading
  12. from typing import Any
  13. from obs import get_logger
  14. logger = get_logger(__name__)
  15. class _AgentSlot:
  16. """持有编译好的 agent 及其代数(generation)。
  17. 代数用于判断「我拿到的是不是已经被失效过的那一版」——失效后旧实例
  18. 可能仍在服务在途请求,不能强行打断,只能让新请求拿新的。
  19. """
  20. def __init__(self) -> None:
  21. self._agent: Any | None = None
  22. self._generation = 0
  23. self._lock = threading.Lock()
  24. @property
  25. def generation(self) -> int:
  26. return self._generation
  27. def peek(self) -> Any | None:
  28. """快路径:已编译则直接返回,否则 None(不触发编译)。"""
  29. return self._agent
  30. def get(self) -> Any:
  31. """取 agent,必要时编译。并发调用只编译一次。"""
  32. agent = self._agent
  33. if agent is not None:
  34. return agent
  35. with self._lock:
  36. # 二次检查:可能在等锁期间已被别的线程编译好
  37. if self._agent is None:
  38. logger.debug("首次编译 agent(generation=%d)", self._generation + 1)
  39. self._agent = self._compile()
  40. self._generation += 1
  41. logger.info("agent 编译完成(generation=%d)", self._generation)
  42. return self._agent
  43. def _compile(self) -> Any:
  44. from agents.agent_factory import build_agent
  45. return build_agent()
  46. def invalidate(self) -> int:
  47. """丢弃当前实例,返回新代数。下次 ``get()`` 会重新编译。"""
  48. with self._lock:
  49. self._agent = None
  50. self._generation += 1
  51. logger.debug("agent 已失效,下次请求将重建(generation=%d)", self._generation)
  52. return self._generation
  53. _slot = _AgentSlot()
  54. def get_agent() -> Any:
  55. """取主 agent(必要时编译)。"""
  56. return _slot.get()
  57. def peek_agent() -> Any | None:
  58. """快路径取 agent;未编译返回 None。热路径上先用它避免持锁。"""
  59. return _slot.peek()
  60. def invalidate() -> int:
  61. """使当前 agent 失效。改模型/思考级别/压缩配置后调用。"""
  62. from agents import model_factory
  63. model_factory.clear_cache()
  64. return _slot.invalidate()
  65. def generation() -> int:
  66. """当前代数。"""
  67. return _slot.generation
  68. async def prewarm() -> bool:
  69. """后台预热:把耗时的图编译挪到线程池,不阻塞事件循环。
  70. 启动时调用。失败不抛——预热失败应当在首次真实请求时暴露,
  71. 而不是让整个服务起不来。
  72. """
  73. try:
  74. await asyncio.to_thread(get_agent)
  75. return True
  76. except Exception as exc:
  77. logger.exception("agent 预热失败,将在首次请求时重试", extra={"kv": {"event": "agent.prewarm_failed"}})
  78. return False