middleware.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. """自定义中间件。
  2. 2.0 只保留一个必要中间件:动态时间。其余能力尽量用框架内置
  3. (压缩用框架的摘要中间件、HITL 用 interrupt_on),不自造轮子。
  4. """
  5. from __future__ import annotations
  6. import re
  7. from typing import Any, Callable
  8. from langchain.agents.middleware.types import AgentMiddleware, ModelRequest, ModelResponse
  9. from agents.prompts import build_time_block
  10. from obs import get_logger
  11. logger = get_logger(__name__)
  12. # 系统提示词里的时间段落。编译期写入的是占位内容,每次调用前整体替换。
  13. _TIME_BLOCK_RE = re.compile(r"\n## 系统时间\n.*?(?=\n## |\Z)", re.DOTALL)
  14. def _refresh_time(request: ModelRequest) -> ModelRequest:
  15. """把系统提示词里的「## 系统时间」段落刷新为当前时间。
  16. 没有系统消息、或 content 是结构化块(多模态)时原样返回:不去动它,
  17. 避免破坏块结构。
  18. """
  19. message = request.system_message
  20. if message is None:
  21. return request
  22. content = message.content
  23. if not isinstance(content, str):
  24. return request
  25. refreshed = _TIME_BLOCK_RE.sub(build_time_block(), content, count=1)
  26. if refreshed == content and "## 系统时间" not in content:
  27. refreshed = content + build_time_block()
  28. if refreshed == content:
  29. return request
  30. return request.override(system_message=message.model_copy(update={"content": refreshed}))
  31. class DynamicTimeMiddleware(AgentMiddleware):
  32. """每次模型调用前刷新系统提示词里的「## 系统时间」段落。
  33. 为什么需要:agent 是编译期单例、长驻进程。如果不刷新,系统提示词里的
  34. 时间会永久停在 agent 构建的那一刻——用户第二天问「今天几号」,模型会
  35. 拿到昨天的日期,且因为它「看得见」一个具体日期,比不知道更危险。
  36. **同步与异步两套钩子都要实现**:只实现 ``wrap_model_call`` 时,用
  37. ``astream``/``ainvoke`` 调用会在运行期抛 NotImplementedError——而且报错
  38. 发生在模型节点内部,离「你少写了个异步方法」这个原因很远。本服务全程
  39. 走异步路径,这里两个都实现,同步的实现顺带留给脚本与测试用。
  40. """
  41. def wrap_model_call(
  42. self,
  43. request: ModelRequest,
  44. handler: Callable[[ModelRequest], ModelResponse],
  45. ) -> Any:
  46. return handler(_refresh_time(request))
  47. async def awrap_model_call(
  48. self,
  49. request: ModelRequest,
  50. handler: Callable[[ModelRequest], Any],
  51. ) -> Any:
  52. return await handler(_refresh_time(request))
  53. __all__ = ["DynamicTimeMiddleware"]