langgraph-agent-toolkit 0.9.1__tar.gz → 0.9.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/CHANGELOG.md +27 -0
  2. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/PKG-INFO +1 -1
  3. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/middlewares/__init__.py +2 -0
  4. langgraph_agent_toolkit-0.9.2/langgraph_agent_toolkit/agents/components/middlewares/_history.py +39 -0
  5. langgraph_agent_toolkit-0.9.2/langgraph_agent_toolkit/agents/components/middlewares/token_trim.py +81 -0
  6. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/middlewares/trim_messages.py +5 -1
  7. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/_base_settings.py +3 -0
  8. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/pyproject.toml +1 -1
  9. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_middlewares.py +138 -0
  10. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/uv.lock +100 -100
  11. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.claude/settings.json +0 -0
  12. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.coveragerc +0 -0
  13. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.dockerignore +0 -0
  14. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.env.example +0 -0
  15. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/RELEASE_TEMPLATE/release-template.md +0 -0
  19. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/codecov.yml +0 -0
  20. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/dependabot.yml +0 -0
  21. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/scripts/tag_from_pyproject.sh +0 -0
  22. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/workflows/release.yml +0 -0
  23. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/workflows/sphinx.yml +0 -0
  24. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.github/workflows/test.yml +0 -0
  25. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.gitignore +0 -0
  26. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.pre-commit-config.yaml +0 -0
  27. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/.project-root +0 -0
  28. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/CLAUDE.md +0 -0
  29. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/CONTRIBUTING.md +0 -0
  30. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/LICENSE +0 -0
  31. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/Makefile +0 -0
  32. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/README.md +0 -0
  33. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/configs/clickhouse/.clickhouse.env.example +0 -0
  34. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/configs/langfuse/.langfuse.env.example +0 -0
  35. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/configs/litellm/.litellm.env.example +0 -0
  36. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/configs/litellm/config.example.yaml +0 -0
  37. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/configs/minio/.minio.env.example +0 -0
  38. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/configs/postgres/.postgres.env.example +0 -0
  39. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/configs/redis/.redis.env.example +0 -0
  40. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docker/api/Dockerfile +0 -0
  41. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docker/app/Dockerfile +0 -0
  42. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docker-compose.yaml +0 -0
  43. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/.gitignore +0 -0
  44. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/Makefile +0 -0
  45. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/_static/custom.css +0 -0
  46. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/_templates/class.rst +0 -0
  47. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/_templates/layout.html +0 -0
  48. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/_templates/module.rst +0 -0
  49. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/conf.py +0 -0
  50. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/contributing.rst +0 -0
  51. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/environment_setup.rst +0 -0
  52. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/index.rst +0 -0
  53. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/installation.rst +0 -0
  54. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/make.bat +0 -0
  55. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/media/agent_architecture.excalidraw +0 -0
  56. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/media/agent_architecture.png +0 -0
  57. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/media/agent_diagram.png +0 -0
  58. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/media/logo.svg +0 -0
  59. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/docs/usage.rst +0 -0
  60. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph.json +0 -0
  61. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/__init__.py +0 -0
  62. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/__init__.py +0 -0
  63. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/agent.py +0 -0
  64. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/agent_executor.py +0 -0
  65. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/__init__.py +0 -0
  66. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/bg_task_agent/__init__.py +0 -0
  67. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/bg_task_agent/agent.py +0 -0
  68. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/bg_task_agent/task.py +0 -0
  69. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/bg_task_agent/utils.py +0 -0
  70. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/chatbot/__init__.py +0 -0
  71. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/chatbot/agent.py +0 -0
  72. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/command_agent/__init__.py +0 -0
  73. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/command_agent/agent.py +0 -0
  74. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/create_agent/__init__.py +0 -0
  75. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/create_agent/agent.py +0 -0
  76. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/create_agent_structured/__init__.py +0 -0
  77. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/create_agent_structured/agent.py +0 -0
  78. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/hitl_agent/__init__.py +0 -0
  79. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/hitl_agent/agent.py +0 -0
  80. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/interrupt_agent/__init__.py +0 -0
  81. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/interrupt_agent/agent.py +0 -0
  82. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/knowledge_base_agent/__init__.py +0 -0
  83. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/knowledge_base_agent/agent.py +0 -0
  84. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/react/__init__.py +0 -0
  85. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/react/agent.py +0 -0
  86. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/supervisor_agent/__init__.py +0 -0
  87. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/blueprints/supervisor_agent/agent.py +0 -0
  88. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/__init__.py +0 -0
  89. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/checkpoint/__init__.py +0 -0
  90. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/checkpoint/empty.py +0 -0
  91. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/creators/__init__.py +0 -0
  92. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/creators/create_react_agent.py +0 -0
  93. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/middlewares/clear_intermediate_tool_calls.py +0 -0
  94. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/middlewares/immediate_generation.py +0 -0
  95. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/middlewares/sanitize_history.py +0 -0
  96. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/tools.py +0 -0
  97. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/agents/components/utils.py +0 -0
  98. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/client/__init__.py +0 -0
  99. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/client/client.py +0 -0
  100. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/__init__.py +0 -0
  101. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/memory/__init__.py +0 -0
  102. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/memory/base.py +0 -0
  103. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/memory/factory.py +0 -0
  104. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/memory/postgres.py +0 -0
  105. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/memory/sqlite.py +0 -0
  106. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/memory/types.py +0 -0
  107. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/models/__init__.py +0 -0
  108. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/models/chat_openai.py +0 -0
  109. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/models/factory.py +0 -0
  110. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/models/fake.py +0 -0
  111. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/observability/__init__.py +0 -0
  112. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/observability/base.py +0 -0
  113. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/observability/empty.py +0 -0
  114. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/observability/factory.py +0 -0
  115. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/observability/langfuse.py +0 -0
  116. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/observability/langsmith.py +0 -0
  117. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/observability/types.py +0 -0
  118. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/prompts/__init__.py +0 -0
  119. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/prompts/chat_prompt_template.py +0 -0
  120. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/prompts/prompt_manager.py +0 -0
  121. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/core/settings.py +0 -0
  122. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/helper/__init__.py +0 -0
  123. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/helper/constants.py +0 -0
  124. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/helper/exceptions.py +0 -0
  125. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/helper/logging.py +0 -0
  126. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/helper/types.py +0 -0
  127. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/helper/utils.py +0 -0
  128. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/run_api.py +0 -0
  129. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/run_app.py +0 -0
  130. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/schema/__init__.py +0 -0
  131. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/schema/models.py +0 -0
  132. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/schema/schema.py +0 -0
  133. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/schema/task_data.py +0 -0
  134. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/__init__.py +0 -0
  135. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/exception_handlers.py +0 -0
  136. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/factory.py +0 -0
  137. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/handler.py +0 -0
  138. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/middleware.py +0 -0
  139. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/routes.py +0 -0
  140. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/types.py +0 -0
  141. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/service/utils.py +0 -0
  142. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/__init__.py +0 -0
  143. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/components/__init__.py +0 -0
  144. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/components/draw_message.py +0 -0
  145. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/components/side_panel.py +0 -0
  146. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/main_page.py +0 -0
  147. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/utils/__init__.py +0 -0
  148. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/utils/constants.py +0 -0
  149. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/langgraph_agent_toolkit/ui/utils/message.py +0 -0
  150. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/postgres-init/create_databases.sql +0 -0
  151. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/postgres-init/create_schema.sql +0 -0
  152. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/01-invoke-proxy.py +0 -0
  153. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/02-test-local-pm.py +0 -0
  154. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/03-test-langfuse-pm.py +0 -0
  155. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/04-test-langsmith-pm.py +0 -0
  156. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/05-test-prompt-types.py +0 -0
  157. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/06-test-custom-chat-prompt-template.py +0 -0
  158. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/07-test-get-chat-history.py +0 -0
  159. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/08-test-update-chat-history.py +0 -0
  160. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/09-test-clear-chat-history.py +0 -0
  161. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/098-cls-example.py +0 -0
  162. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/scripts/python/099-t.py +0 -0
  163. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_agent_executor.py +0 -0
  164. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_blueprints.py +0 -0
  165. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_checkpoint_empty.py +0 -0
  166. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_components_utils.py +0 -0
  167. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_create_agent_stack.py +0 -0
  168. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_hitl_agent.py +0 -0
  169. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/agents/test_tools.py +0 -0
  170. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/client/conftest.py +0 -0
  171. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/client/test_client.py +0 -0
  172. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/conftest.py +0 -0
  173. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/core/test_llm.py +0 -0
  174. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/core/test_memory.py +0 -0
  175. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/core/test_observability.py +0 -0
  176. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/core/test_prompt_manager.py +0 -0
  177. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/core/test_prompts.py +0 -0
  178. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/core/test_settings.py +0 -0
  179. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/helper/test_create_ai_message.py +0 -0
  180. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/helper/test_helper_utils.py +0 -0
  181. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/integration/test_docker_e2e.py +0 -0
  182. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/conftest.py +0 -0
  183. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/test_auth.py +0 -0
  184. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/test_exception_handlers.py +0 -0
  185. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/test_factory.py +0 -0
  186. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/test_handler.py +0 -0
  187. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/test_middleware.py +0 -0
  188. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/test_service.py +0 -0
  189. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/service/test_service_e2e.py +0 -0
  190. {langgraph_agent_toolkit-0.9.1 → langgraph_agent_toolkit-0.9.2}/tests/ui/test_draw_messages.py +0 -0
@@ -6,6 +6,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.9.2]
10
+
11
+ ### Fixed
12
+
13
+ - `TrimMessagesMiddleware` / `TokenTrimMiddleware` no longer silently destroy
14
+ the model's view when the **latest turn does not fit the budget**.
15
+ `trim_messages` (`strategy="last"`, `start_on="human"`) returns `[]` whenever
16
+ the current turn exceeds the budget — e.g. a single user message larger than
17
+ the token budget, or a turn with more tool calls than the message budget —
18
+ which invoked the model with the system prompt only and made it answer with
19
+ the user's question dropped. Both middlewares now floor to the latest turn
20
+ (kept verbatim, over budget but answerable) rather than emptying it.
21
+ Regression tests cover an oversize user message, an oversize tool result, and
22
+ a 6+ tool-call burst.
23
+
24
+ ### Added
25
+
26
+ - `TokenTrimMiddleware` — bounds the model's message view by a **token** budget
27
+ (the token-counting companion to `TrimMessagesMiddleware`, which bounds by
28
+ message count). View-only, so the full history stays in state and the system
29
+ prompt is preserved. The token counter is configurable (`token_counter`,
30
+ default `count_tokens_approximately` — no extra dependency; pass a
31
+ `tiktoken`-backed counter or the model for an exact count), and the budget
32
+ defaults to the new `DEFAULT_MAX_TOKENS_HISTORY_LENGTH` setting (unset by
33
+ default, since a sensible budget is model-specific). Compose it after
34
+ `TrimMessagesMiddleware` to cap both message count and token size.
35
+
9
36
  ## [0.9.1]
10
37
 
11
38
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-agent-toolkit
3
- Version: 0.9.1
3
+ Version: 0.9.2
4
4
  Summary: Full toolkit for running an AI agent service built with LangGraph, FastAPI and Streamlit
5
5
  Project-URL: documentation, https://kryvokhyzha.github.io/langgraph-agent-toolkit
6
6
  Project-URL: repository, https://github.com/kryvokhyzha/langgraph-agent-toolkit
@@ -5,6 +5,7 @@ from langgraph_agent_toolkit.agents.components.middlewares.clear_intermediate_to
5
5
  )
6
6
  from langgraph_agent_toolkit.agents.components.middlewares.immediate_generation import ImmediateGenerationMiddleware
7
7
  from langgraph_agent_toolkit.agents.components.middlewares.sanitize_history import SanitizeHistoryMiddleware
8
+ from langgraph_agent_toolkit.agents.components.middlewares.token_trim import TokenTrimMiddleware
8
9
  from langgraph_agent_toolkit.agents.components.middlewares.trim_messages import TrimMessagesMiddleware
9
10
 
10
11
 
@@ -12,5 +13,6 @@ __all__ = [
12
13
  "ClearIntermediateToolCallsMiddleware",
13
14
  "ImmediateGenerationMiddleware",
14
15
  "SanitizeHistoryMiddleware",
16
+ "TokenTrimMiddleware",
15
17
  "TrimMessagesMiddleware",
16
18
  ]
@@ -0,0 +1,39 @@
1
+ """Internal helper shared by the history-trimming middleware (``TrimMessages`` / ``TokenTrim``)."""
2
+
3
+ from langchain_core.messages import BaseMessage
4
+
5
+
6
+ def keep_latest_turn_if_emptied(
7
+ original: list[BaseMessage],
8
+ trimmed: list[BaseMessage],
9
+ ) -> list[BaseMessage]:
10
+ """Guard against ``trim_messages`` destroying the model's view.
11
+
12
+ ``trim_messages(strategy="last", start_on="human", allow_partial=False)`` returns an empty (or
13
+ system-only) list whenever the latest human turn does not fit the budget — e.g. a single user
14
+ message larger than the budget, or a current turn with more tool calls than the budget allows. The
15
+ model would then be invoked with no user content and answer from the system prompt alone.
16
+
17
+ When trimming leaves no human/tool message, fall back to the latest turn (from the last
18
+ ``HumanMessage`` to the end, plus any leading system messages) verbatim, bypassing the budget for
19
+ that turn: a too-long-but-answerable prompt beats a hallucinated answer to nothing, and an
20
+ over-budget turn surfaces as a visible model context error rather than silent corruption.
21
+
22
+ Args:
23
+ original: The messages before trimming.
24
+ trimmed: The result of ``trim_messages``.
25
+
26
+ Returns:
27
+ ``trimmed`` when it still carries user content, else the latest turn from ``original``.
28
+
29
+ """
30
+ if any(m.type in ("human", "tool") for m in trimmed):
31
+ return trimmed
32
+
33
+ last_human = next((i for i in range(len(original) - 1, -1, -1) if original[i].type == "human"), None)
34
+ if last_human is None:
35
+ # No human turn to anchor on (pathological); preserve everything rather than wipe the view.
36
+ return original
37
+
38
+ leading_system = [m for m in original[:last_human] if m.type == "system"]
39
+ return leading_system + original[last_human:]
@@ -0,0 +1,81 @@
1
+ """Middleware that trims the model's input to a bounded token budget (view-only)."""
2
+
3
+ from collections.abc import Awaitable, Callable
4
+
5
+ from langchain.agents.middleware import AgentMiddleware
6
+ from langchain.agents.middleware.types import ModelRequest, ModelResponse
7
+ from langchain_core.language_models import BaseLanguageModel
8
+ from langchain_core.messages import BaseMessage
9
+ from langchain_core.messages.utils import count_tokens_approximately, trim_messages
10
+
11
+ from langgraph_agent_toolkit.agents.components.middlewares._history import keep_latest_turn_if_emptied
12
+ from langgraph_agent_toolkit.core.settings import settings
13
+
14
+
15
+ class TokenTrimMiddleware(AgentMiddleware):
16
+ """Bound the model's input to a token budget, non-destructively.
17
+
18
+ The token-counting companion to ``TrimMessagesMiddleware`` (which bounds by *message* count). Each
19
+ model call sees at most ``max_tokens`` tokens of recent history (trimmed to start on a human turn
20
+ and to keep the system prompt), while the full history stays in state. Compose the two to cap both
21
+ message count and token size, as the custom ``create_react_agent``'s ``pre_model_hook`` did.
22
+
23
+ Args:
24
+ max_tokens: Token budget for the model's message view. Defaults to
25
+ ``settings.DEFAULT_MAX_TOKENS_HISTORY_LENGTH``. ``None`` (the toolkit default) disables
26
+ trimming — set it, or pass ``max_tokens``, to a budget appropriate for your model.
27
+ token_counter: How to count tokens — a per-message or per-list callable, or a chat model.
28
+ Defaults to ``count_tokens_approximately`` (fast, model-agnostic, no extra dependency).
29
+ Pass a ``tiktoken``-backed counter (or the model itself) for an exact count.
30
+
31
+ Note:
32
+ On ``create_agent`` the system prompt is never part of ``request.messages`` (it lives on
33
+ ``request.system_message`` and is prepended at call time), so it is always preserved
34
+ regardless of the budget.
35
+
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ max_tokens: int | None = None,
41
+ token_counter: (
42
+ Callable[[list[BaseMessage]], int] | Callable[[BaseMessage], int] | BaseLanguageModel
43
+ ) = count_tokens_approximately,
44
+ ) -> None:
45
+ super().__init__()
46
+ resolved = max_tokens if max_tokens is not None else settings.DEFAULT_MAX_TOKENS_HISTORY_LENGTH
47
+ if resolved is not None and resolved < 1:
48
+ raise ValueError("max_tokens must be >= 1 (or None to disable)")
49
+ self.max_tokens = resolved
50
+ self.token_counter = token_counter
51
+
52
+ def _trim(self, messages: list[BaseMessage]) -> list[BaseMessage]:
53
+ if not self.max_tokens:
54
+ return messages
55
+ trimmed = trim_messages(
56
+ messages,
57
+ token_counter=self.token_counter,
58
+ max_tokens=self.max_tokens,
59
+ strategy="last",
60
+ start_on="human",
61
+ end_on=("human", "tool"),
62
+ include_system=True,
63
+ allow_partial=False,
64
+ )
65
+ # A latest turn larger than the budget collapses to []; keep that turn (over budget but
66
+ # answerable) instead of invoking the model with no user content.
67
+ return keep_latest_turn_if_emptied(messages, trimmed)
68
+
69
+ def wrap_model_call(
70
+ self,
71
+ request: ModelRequest,
72
+ handler: Callable[[ModelRequest], ModelResponse],
73
+ ) -> ModelResponse:
74
+ return handler(request.override(messages=self._trim(request.messages)))
75
+
76
+ async def awrap_model_call(
77
+ self,
78
+ request: ModelRequest,
79
+ handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
80
+ ) -> ModelResponse:
81
+ return await handler(request.override(messages=self._trim(request.messages)))
@@ -7,6 +7,7 @@ from langchain.agents.middleware.types import ModelRequest, ModelResponse
7
7
  from langchain_core.messages import BaseMessage
8
8
  from langchain_core.messages.utils import trim_messages
9
9
 
10
+ from langgraph_agent_toolkit.agents.components.middlewares._history import keep_latest_turn_if_emptied
10
11
  from langgraph_agent_toolkit.core.settings import settings
11
12
 
12
13
 
@@ -32,7 +33,7 @@ class TrimMessagesMiddleware(AgentMiddleware):
32
33
  self.max_messages = resolved
33
34
 
34
35
  def _trim(self, messages: list[BaseMessage]) -> list[BaseMessage]:
35
- return trim_messages(
36
+ trimmed = trim_messages(
36
37
  messages,
37
38
  token_counter=len, # count messages, not tokens
38
39
  max_tokens=self.max_messages,
@@ -42,6 +43,9 @@ class TrimMessagesMiddleware(AgentMiddleware):
42
43
  include_system=True,
43
44
  allow_partial=False,
44
45
  )
46
+ # A current turn longer than max_messages collapses to []; keep that turn instead of
47
+ # invoking the model with no user content.
48
+ return keep_latest_turn_if_emptied(messages, trimmed)
45
49
 
46
50
  def wrap_model_call(
47
51
  self,
@@ -162,6 +162,9 @@ class Settings(BaseSettings):
162
162
  # Agent configuration
163
163
  DEFAULT_AGENT: str = "create-agent"
164
164
  DEFAULT_MAX_MESSAGE_HISTORY_LENGTH: int = 18
165
+ # Token budget for the model's message view (TokenTrimMiddleware). None disables it (opt-in,
166
+ # since a sensible budget is model-specific); set a value to bound history by tokens.
167
+ DEFAULT_MAX_TOKENS_HISTORY_LENGTH: int | None = None
165
168
  DEFAULT_RECURSION_LIMIT: int = 64
166
169
  MULTIMODAL_MAX_ATTACHMENTS: int | None = Field(
167
170
  default=None,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langgraph-agent-toolkit"
7
- version = "0.9.1"
7
+ version = "0.9.2"
8
8
  description = "Full toolkit for running an AI agent service built with LangGraph, FastAPI and Streamlit"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -11,6 +11,7 @@ from langgraph_agent_toolkit.agents.components.middlewares import (
11
11
  ClearIntermediateToolCallsMiddleware,
12
12
  ImmediateGenerationMiddleware,
13
13
  SanitizeHistoryMiddleware,
14
+ TokenTrimMiddleware,
14
15
  TrimMessagesMiddleware,
15
16
  )
16
17
  from langgraph_agent_toolkit.core.models.fake import FakeToolModel
@@ -246,6 +247,143 @@ def test_trim_messages_rejects_bad_max():
246
247
  TrimMessagesMiddleware(max_messages=0)
247
248
 
248
249
 
250
+ def _content_len(messages: list) -> int:
251
+ """Per-list token-counter stub: total characters of message content."""
252
+ return sum(len(m.content) for m in messages)
253
+
254
+
255
+ def test_token_trim_bounds_to_token_budget():
256
+ """The model's view is trimmed until the token count fits the budget, starting on a human turn."""
257
+ mw = TokenTrimMiddleware(max_tokens=12, token_counter=_content_len)
258
+ request = MagicMock()
259
+ request.messages = [
260
+ HumanMessage("aaaa"),
261
+ AIMessage("bbbb"),
262
+ HumanMessage("cccc"),
263
+ AIMessage("dddd"),
264
+ HumanMessage("eeee"),
265
+ ]
266
+ request.override.return_value = "TRIMMED"
267
+ handler = MagicMock(return_value="R")
268
+
269
+ mw.wrap_model_call(request, handler)
270
+
271
+ trimmed = request.override.call_args.kwargs["messages"]
272
+ assert _content_len(trimmed) <= 12
273
+ assert trimmed[0].type == "human"
274
+ handler.assert_called_once_with("TRIMMED")
275
+
276
+
277
+ def test_token_trim_keeps_system_message():
278
+ """include_system keeps a system message even when the rest is trimmed to fit the budget."""
279
+ mw = TokenTrimMiddleware(max_tokens=8, token_counter=_content_len)
280
+ request = MagicMock()
281
+ request.messages = [SystemMessage("sys"), HumanMessage("aaaa"), AIMessage("bbbb"), HumanMessage("cccc")]
282
+ handler = MagicMock()
283
+
284
+ mw.wrap_model_call(request, handler)
285
+
286
+ trimmed = request.override.call_args.kwargs["messages"]
287
+ assert any(m.type == "system" for m in trimmed)
288
+
289
+
290
+ def test_token_trim_disabled_passes_through():
291
+ """With no budget (None), the middleware is a no-op."""
292
+ mw = TokenTrimMiddleware(max_tokens=None)
293
+ assert mw.max_tokens is None # toolkit default is opt-in
294
+ request = MagicMock()
295
+ request.messages = [HumanMessage("q1"), AIMessage("a1"), HumanMessage("q2")]
296
+ handler = MagicMock()
297
+
298
+ mw.wrap_model_call(request, handler)
299
+
300
+ assert request.override.call_args.kwargs["messages"] == request.messages
301
+
302
+
303
+ def test_token_trim_default_from_settings():
304
+ assert TokenTrimMiddleware().max_tokens == settings.DEFAULT_MAX_TOKENS_HISTORY_LENGTH
305
+
306
+
307
+ def test_token_trim_rejects_bad_max():
308
+ with pytest.raises(ValueError, match="max_tokens"):
309
+ TokenTrimMiddleware(max_tokens=0)
310
+
311
+
312
+ def _latest_human_present(view: list, text: str) -> bool:
313
+ return any(m.type == "human" and text in str(m.content) for m in view)
314
+
315
+
316
+ def test_token_trim_preserves_oversize_latest_human():
317
+ """A single user message larger than the budget is kept, not dropped to an empty/system-only view."""
318
+ mw = TokenTrimMiddleware(max_tokens=5, token_counter=_content_len)
319
+ request = MagicMock()
320
+ request.messages = [HumanMessage("this question is far longer than the tiny budget UNIQUE")]
321
+ handler = MagicMock()
322
+
323
+ mw.wrap_model_call(request, handler)
324
+
325
+ kept = request.override.call_args.kwargs["messages"]
326
+ assert _latest_human_present(kept, "UNIQUE") # question preserved despite exceeding the budget
327
+
328
+
329
+ def test_token_trim_preserves_oversize_tool_turn():
330
+ """A current turn (human + big tool result) over budget is kept verbatim, not emptied."""
331
+ mw = TokenTrimMiddleware(max_tokens=5, token_counter=_content_len)
332
+ request = MagicMock()
333
+ request.messages = [
334
+ HumanMessage("q UNIQUE"),
335
+ _ai_tool("search", "s1"),
336
+ ToolMessage("x" * 200, tool_call_id="s1", name="search"),
337
+ ]
338
+ handler = MagicMock()
339
+
340
+ mw.wrap_model_call(request, handler)
341
+
342
+ kept = request.override.call_args.kwargs["messages"]
343
+ assert _latest_human_present(kept, "UNIQUE")
344
+ ai_ids = {c["id"] for m in kept if m.type == "ai" and m.tool_calls for c in m.tool_calls}
345
+ assert all(m.tool_call_id in ai_ids for m in kept if m.type == "tool") # pairing intact
346
+
347
+
348
+ def test_trim_messages_preserves_long_tool_burst_turn():
349
+ """A current turn with more messages than max_messages is kept, not wiped to an empty view."""
350
+ mw = TrimMessagesMiddleware(max_messages=12)
351
+ burst = [HumanMessage("BURST_Q")]
352
+ for i in range(6): # 1 human + 6*(AI tool_call, Tool) = 13 messages > 12
353
+ burst += [_ai_tool("search", f"s{i}"), ToolMessage("r", tool_call_id=f"s{i}", name="search")]
354
+ request = MagicMock()
355
+ request.messages = burst
356
+ handler = MagicMock()
357
+
358
+ mw.wrap_model_call(request, handler)
359
+
360
+ kept = request.override.call_args.kwargs["messages"]
361
+ assert _latest_human_present(kept, "BURST_Q") # the human survives the burst
362
+ ai_ids = {c["id"] for m in kept if m.type == "ai" and m.tool_calls for c in m.tool_calls}
363
+ assert all(m.tool_call_id in ai_ids for m in kept if m.type == "tool")
364
+
365
+
366
+ def test_keep_latest_turn_floor_keeps_only_latest_turn_and_leading_system():
367
+ """The floor preserves leading system messages + the latest turn, dropping older over-budget turns."""
368
+ from langgraph_agent_toolkit.agents.components.middlewares._history import keep_latest_turn_if_emptied
369
+
370
+ original = [SystemMessage("sys"), HumanMessage("old"), AIMessage("a"), HumanMessage("latest")]
371
+ floored = keep_latest_turn_if_emptied(original, []) # [] simulates a budget collapse
372
+
373
+ assert floored[0].type == "system"
374
+ assert floored[-1].content == "latest"
375
+ assert [m.content for m in floored if m.type == "human"] == ["latest"] # only the latest turn's human
376
+
377
+
378
+ def test_keep_latest_turn_floor_passes_through_healthy_trim():
379
+ """When trimming kept user content, the floor returns it unchanged."""
380
+ from langgraph_agent_toolkit.agents.components.middlewares._history import keep_latest_turn_if_emptied
381
+
382
+ original = [HumanMessage("a"), AIMessage("b"), HumanMessage("c")]
383
+ trimmed = [HumanMessage("c")]
384
+ assert keep_latest_turn_if_emptied(original, trimmed) is trimmed
385
+
386
+
249
387
  def test_create_agent_with_middleware_runs_end_to_end():
250
388
  """create_agent composes both middleware and runs without error (FAKE model, no tool loop)."""
251
389