synapsekit 0.6.0__tar.gz → 0.6.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 (183) hide show
  1. {synapsekit-0.6.0 → synapsekit-0.6.2}/CHANGELOG.md +43 -0
  2. {synapsekit-0.6.0 → synapsekit-0.6.2}/PKG-INFO +10 -2
  3. {synapsekit-0.6.0 → synapsekit-0.6.2}/pyproject.toml +17 -3
  4. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/__init__.py +33 -1
  5. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/__init__.py +4 -0
  6. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/__init__.py +4 -0
  7. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/http_request.py +1 -3
  8. synapsekit-0.6.2/src/synapsekit/agents/tools/human_input.py +63 -0
  9. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/regex_tool.py +3 -1
  10. synapsekit-0.6.2/src/synapsekit/agents/tools/wikipedia.py +103 -0
  11. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/__init__.py +7 -1
  12. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/compiled.py +77 -2
  13. synapsekit-0.6.2/src/synapsekit/graph/interrupt.py +53 -0
  14. synapsekit-0.6.2/src/synapsekit/graph/node.py +70 -0
  15. synapsekit-0.6.2/src/synapsekit/graph/subgraph.py +60 -0
  16. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/_sqlite_cache.py +2 -5
  17. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/groq.py +1 -3
  18. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/structured.py +1 -1
  19. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/excel.py +1 -3
  20. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/pptx.py +1 -3
  21. synapsekit-0.6.2/src/synapsekit/memory/__init__.py +11 -0
  22. synapsekit-0.6.2/src/synapsekit/memory/hybrid.py +115 -0
  23. synapsekit-0.6.2/src/synapsekit/memory/sqlite.py +119 -0
  24. synapsekit-0.6.2/src/synapsekit/memory/summary_buffer.py +118 -0
  25. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/__init__.py +14 -0
  26. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/contextual.py +1 -3
  27. synapsekit-0.6.2/src/synapsekit/retrieval/contextual_compression.py +68 -0
  28. synapsekit-0.6.2/src/synapsekit/retrieval/crag.py +123 -0
  29. synapsekit-0.6.2/src/synapsekit/retrieval/cross_encoder.py +105 -0
  30. synapsekit-0.6.2/src/synapsekit/retrieval/ensemble.py +50 -0
  31. synapsekit-0.6.2/src/synapsekit/retrieval/parent_document.py +104 -0
  32. synapsekit-0.6.2/src/synapsekit/retrieval/query_decomposition.py +87 -0
  33. synapsekit-0.6.2/src/synapsekit/retrieval/self_query.py +98 -0
  34. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/sentence_window.py +7 -5
  35. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/test_v053_features.py +1 -3
  36. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/test_v060_features.py +10 -5
  37. synapsekit-0.6.2/tests/test_v061_features.py +633 -0
  38. synapsekit-0.6.2/tests/test_v062_features.py +531 -0
  39. {synapsekit-0.6.0 → synapsekit-0.6.2}/uv.lock +360 -2
  40. synapsekit-0.6.0/src/synapsekit/graph/node.py +0 -34
  41. synapsekit-0.6.0/src/synapsekit/memory/__init__.py +0 -3
  42. {synapsekit-0.6.0 → synapsekit-0.6.2}/.github/DISCUSSION_TEMPLATE/ideas.yml +0 -0
  43. {synapsekit-0.6.0 → synapsekit-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  44. {synapsekit-0.6.0 → synapsekit-0.6.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  45. {synapsekit-0.6.0 → synapsekit-0.6.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  46. {synapsekit-0.6.0 → synapsekit-0.6.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  47. {synapsekit-0.6.0 → synapsekit-0.6.2}/.github/profile/README.md +0 -0
  48. {synapsekit-0.6.0 → synapsekit-0.6.2}/.github/workflows/ci.yml +0 -0
  49. {synapsekit-0.6.0 → synapsekit-0.6.2}/.gitignore +0 -0
  50. {synapsekit-0.6.0 → synapsekit-0.6.2}/.pre-commit-config.yaml +0 -0
  51. {synapsekit-0.6.0 → synapsekit-0.6.2}/CODE_OF_CONDUCT.md +0 -0
  52. {synapsekit-0.6.0 → synapsekit-0.6.2}/CONTRIBUTING.md +0 -0
  53. {synapsekit-0.6.0 → synapsekit-0.6.2}/LICENSE +0 -0
  54. {synapsekit-0.6.0 → synapsekit-0.6.2}/Makefile +0 -0
  55. {synapsekit-0.6.0 → synapsekit-0.6.2}/README.md +0 -0
  56. {synapsekit-0.6.0 → synapsekit-0.6.2}/SECURITY.md +0 -0
  57. {synapsekit-0.6.0 → synapsekit-0.6.2}/assets/banner.svg +0 -0
  58. {synapsekit-0.6.0 → synapsekit-0.6.2}/assets/favicon.svg +0 -0
  59. {synapsekit-0.6.0 → synapsekit-0.6.2}/assets/logo.svg +0 -0
  60. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/_compat.py +0 -0
  61. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/base.py +0 -0
  62. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/executor.py +0 -0
  63. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/function_calling.py +0 -0
  64. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/memory.py +0 -0
  65. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/react.py +0 -0
  66. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/registry.py +0 -0
  67. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tool_decorator.py +0 -0
  68. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/calculator.py +0 -0
  69. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/datetime_tool.py +0 -0
  70. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/file_list.py +0 -0
  71. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/file_read.py +0 -0
  72. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/file_write.py +0 -0
  73. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/json_query.py +0 -0
  74. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/python_repl.py +0 -0
  75. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/sql_query.py +0 -0
  76. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/agents/tools/web_search.py +0 -0
  77. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/embeddings/__init__.py +0 -0
  78. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/embeddings/backend.py +0 -0
  79. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/checkpointers/__init__.py +0 -0
  80. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/checkpointers/base.py +0 -0
  81. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/checkpointers/memory.py +0 -0
  82. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/checkpointers/sqlite.py +0 -0
  83. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/edge.py +0 -0
  84. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/errors.py +0 -0
  85. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/graph.py +0 -0
  86. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/mermaid.py +0 -0
  87. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/graph/state.py +0 -0
  88. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/__init__.py +0 -0
  89. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/_cache.py +0 -0
  90. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/_rate_limit.py +0 -0
  91. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/_retry.py +0 -0
  92. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/anthropic.py +0 -0
  93. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/azure_openai.py +0 -0
  94. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/base.py +0 -0
  95. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/bedrock.py +0 -0
  96. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/cohere.py +0 -0
  97. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/deepseek.py +0 -0
  98. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/fireworks.py +0 -0
  99. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/gemini.py +0 -0
  100. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/mistral.py +0 -0
  101. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/ollama.py +0 -0
  102. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/openai.py +0 -0
  103. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/openrouter.py +0 -0
  104. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/llm/together.py +0 -0
  105. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/__init__.py +0 -0
  106. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/base.py +0 -0
  107. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/csv.py +0 -0
  108. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/directory.py +0 -0
  109. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/html.py +0 -0
  110. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/json_loader.py +0 -0
  111. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/pdf.py +0 -0
  112. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/text.py +0 -0
  113. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/loaders/web.py +0 -0
  114. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/memory/conversation.py +0 -0
  115. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/observability/__init__.py +0 -0
  116. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/observability/tracer.py +0 -0
  117. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/parsers/__init__.py +0 -0
  118. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/parsers/json_parser.py +0 -0
  119. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/parsers/list_parser.py +0 -0
  120. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/parsers/pydantic_parser.py +0 -0
  121. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/prompts/__init__.py +0 -0
  122. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/prompts/template.py +0 -0
  123. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/py.typed +0 -0
  124. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/rag/__init__.py +0 -0
  125. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/rag/facade.py +0 -0
  126. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/rag/pipeline.py +0 -0
  127. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/base.py +0 -0
  128. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/chroma.py +0 -0
  129. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/faiss.py +0 -0
  130. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/pinecone.py +0 -0
  131. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/qdrant.py +0 -0
  132. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/rag_fusion.py +0 -0
  133. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/retriever.py +0 -0
  134. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/retrieval/vectorstore.py +0 -0
  135. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/text_splitters/__init__.py +0 -0
  136. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/text_splitters/base.py +0 -0
  137. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/text_splitters/character.py +0 -0
  138. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/text_splitters/recursive.py +0 -0
  139. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/text_splitters/semantic.py +0 -0
  140. {synapsekit-0.6.0 → synapsekit-0.6.2}/src/synapsekit/text_splitters/token.py +0 -0
  141. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/__init__.py +0 -0
  142. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/agents/__init__.py +0 -0
  143. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/agents/test_executor.py +0 -0
  144. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/agents/test_function_calling.py +0 -0
  145. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/agents/test_memory.py +0 -0
  146. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/agents/test_react.py +0 -0
  147. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/agents/test_tool_decorator.py +0 -0
  148. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/agents/test_tools.py +0 -0
  149. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/conftest.py +0 -0
  150. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/__init__.py +0 -0
  151. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/test_build.py +0 -0
  152. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/test_checkpointing.py +0 -0
  153. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/test_cycles.py +0 -0
  154. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/test_mermaid.py +0 -0
  155. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/test_run.py +0 -0
  156. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/test_state.py +0 -0
  157. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/graph/test_stream.py +0 -0
  158. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/llm/__init__.py +0 -0
  159. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/llm/test_cache_retry.py +0 -0
  160. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/llm/test_function_calling_providers.py +0 -0
  161. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/llm/test_llm.py +0 -0
  162. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/llm/test_providers.py +0 -0
  163. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/loaders/__init__.py +0 -0
  164. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/loaders/test_loaders.py +0 -0
  165. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/memory/__init__.py +0 -0
  166. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/memory/test_memory.py +0 -0
  167. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/observability/__init__.py +0 -0
  168. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/observability/test_tracer.py +0 -0
  169. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/parsers/__init__.py +0 -0
  170. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/parsers/test_parsers.py +0 -0
  171. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/prompts/__init__.py +0 -0
  172. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/prompts/test_prompts.py +0 -0
  173. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/rag/__init__.py +0 -0
  174. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/rag/test_facade.py +0 -0
  175. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/rag/test_pipeline.py +0 -0
  176. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/retrieval/__init__.py +0 -0
  177. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/retrieval/test_backends.py +0 -0
  178. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/retrieval/test_retriever.py +0 -0
  179. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/retrieval/test_vectorstore.py +0 -0
  180. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/test_v051_features.py +0 -0
  181. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/test_v052_features.py +0 -0
  182. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/text_splitters/__init__.py +0 -0
  183. {synapsekit-0.6.0 → synapsekit-0.6.2}/tests/text_splitters/test_splitters.py +0 -0
@@ -7,6 +7,49 @@ SynapseKit uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.6.1] — 2026-03-13
11
+
12
+ ### Added
13
+
14
+ - **Human-in-the-Loop** — `GraphInterrupt` exception pauses graph execution for human review; `InterruptState` holds interrupt details; `resume(updates=...)` applies human edits and continues from checkpoint
15
+ - **Subgraphs** — `subgraph_node(compiled_graph, input_mapping, output_mapping)` nests a `CompiledGraph` as a node in a parent graph with key mapping
16
+ - **Token-level streaming** — `llm_node(llm, stream=True)` wraps any `BaseLLM` as a graph node; `stream_tokens()` yields `{"type": "token", ...}` events for real-time output
17
+ - **Self-Query retrieval** — `SelfQueryRetriever` uses an LLM to decompose natural-language queries into semantic search + metadata filters automatically
18
+ - **Parent Document retrieval** — `ParentDocumentRetriever` embeds small chunks for precision search, returns full parent documents for richer context
19
+ - **Cross-Encoder reranking** — `CrossEncoderReranker` reranks retrieval results with cross-encoder models for higher accuracy (requires `synapsekit[semantic]`)
20
+ - **Hybrid memory** — `HybridMemory` keeps a sliding window of recent messages in full, plus an LLM-generated summary of older messages for token-efficient long conversations
21
+ - 30 new tests (482 total)
22
+
23
+ ---
24
+
25
+ ## [0.6.0] — 2026-03-13
26
+
27
+ ### Added
28
+
29
+ - **Built-in tools** (6 new):
30
+ - `HTTPRequestTool` — GET/POST/PUT/DELETE/PATCH with aiohttp, configurable timeout and max response length
31
+ - `FileWriteTool` — write/append files with auto-mkdir
32
+ - `FileListTool` — list directories with glob patterns, recursive mode
33
+ - `DateTimeTool` — current time, parse, format with timezone support
34
+ - `RegexTool` — findall, match, search, replace, split with flag support
35
+ - `JSONQueryTool` — dot-notation path queries on JSON data
36
+ - **LLM providers** (3 new, all OpenAI-compatible):
37
+ - `OpenRouterLLM` — unified API for 200+ models (auto-detected from `/` in model name)
38
+ - `TogetherLLM` — Together AI fast inference
39
+ - `FireworksLLM` — Fireworks AI optimized serving
40
+ - **Advanced retrieval** (2 new):
41
+ - `ContextualRetriever` — Anthropic-style contextual retrieval (LLM prepends context before embedding)
42
+ - `SentenceWindowRetriever` — sentence-level embedding with configurable window expansion at retrieval time
43
+ - RAG facade auto-detects `openrouter` (model names with `/`), `together`, and `fireworks` providers
44
+ - 37 new tests (452 total)
45
+
46
+ ### Changed
47
+
48
+ - Lazy imports extended for new providers (`OpenRouterLLM`, `TogetherLLM`, `FireworksLLM`)
49
+ - `agents/tools/__init__.py` exports 11 built-in tools (was 5)
50
+
51
+ ---
52
+
10
53
  ## [0.5.3] — 2026-03-12
11
54
 
12
55
  ### Added
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synapsekit
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Async-native Python framework for building production-grade LLM applications. Streaming-first, 2 dependencies, fully transparent.
5
5
  Project-URL: Homepage, https://github.com/SynapseKit/SynapseKit
6
6
  Project-URL: Repository, https://github.com/SynapseKit/SynapseKit
7
- Author-email: Amit Nautiyal <de.amit.nautiyal@gmail.com>
7
+ Author-email: Amit <de.amit.nautiyal@gmail.com>
8
8
  License: MIT
9
9
  License-File: LICENSE
10
10
  Keywords: ai,async,llm,rag,retrieval,streaming
@@ -43,13 +43,19 @@ Provides-Extra: chroma
43
43
  Requires-Dist: chromadb>=0.5; extra == 'chroma'
44
44
  Provides-Extra: cohere
45
45
  Requires-Dist: cohere>=5.0; extra == 'cohere'
46
+ Provides-Extra: excel
47
+ Requires-Dist: openpyxl>=3.1; extra == 'excel'
46
48
  Provides-Extra: faiss
47
49
  Requires-Dist: faiss-cpu>=1.7; extra == 'faiss'
48
50
  Provides-Extra: gemini
49
51
  Requires-Dist: google-generativeai>=0.7; extra == 'gemini'
52
+ Provides-Extra: groq
53
+ Requires-Dist: groq>=0.9; extra == 'groq'
50
54
  Provides-Extra: html
51
55
  Requires-Dist: beautifulsoup4>=4.12; extra == 'html'
52
56
  Requires-Dist: lxml>=5.0; extra == 'html'
57
+ Provides-Extra: http
58
+ Requires-Dist: aiohttp>=3.9; extra == 'http'
53
59
  Provides-Extra: mistral
54
60
  Requires-Dist: mistralai>=1.0; extra == 'mistral'
55
61
  Provides-Extra: ollama
@@ -60,6 +66,8 @@ Provides-Extra: pdf
60
66
  Requires-Dist: pypdf>=4.0; extra == 'pdf'
61
67
  Provides-Extra: pinecone
62
68
  Requires-Dist: pinecone>=3.0; extra == 'pinecone'
69
+ Provides-Extra: pptx
70
+ Requires-Dist: python-pptx>=0.6; extra == 'pptx'
63
71
  Provides-Extra: qdrant
64
72
  Requires-Dist: qdrant-client>=1.9; extra == 'qdrant'
65
73
  Provides-Extra: search
@@ -4,9 +4,9 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "synapsekit"
7
- version = "0.6.0"
7
+ version = "0.6.2"
8
8
  description = "Async-native Python framework for building production-grade LLM applications. Streaming-first, 2 dependencies, fully transparent."
9
- authors = [{ name = "Amit Nautiyal", email = "de.amit.nautiyal@gmail.com" }]
9
+ authors = [{ name = "Amit", email = "de.amit.nautiyal@gmail.com" }]
10
10
  license = { text = "MIT" }
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.14"
@@ -44,6 +44,10 @@ cohere = ["cohere>=5.0"]
44
44
  mistral = ["mistralai>=1.0"]
45
45
  gemini = ["google-generativeai>=0.7"]
46
46
  bedrock = ["boto3>=1.34"]
47
+ groq = ["groq>=0.9"]
48
+ excel = ["openpyxl>=3.1"]
49
+ pptx = ["python-pptx>=0.6"]
50
+ http = ["aiohttp>=3.9"]
47
51
  search = ["duckduckgo-search>=6.0"]
48
52
  all = [
49
53
  "openai>=1.0",
@@ -130,6 +134,14 @@ module = [
130
134
  "synapsekit.retrieval.faiss",
131
135
  "synapsekit.retrieval.qdrant",
132
136
  "synapsekit.retrieval.pinecone",
137
+ "synapsekit.llm.groq",
138
+ "synapsekit.llm.azure_openai",
139
+ "synapsekit.llm.deepseek",
140
+ "synapsekit.llm.openrouter",
141
+ "synapsekit.llm.together",
142
+ "synapsekit.llm.fireworks",
143
+ "synapsekit.loaders.excel",
144
+ "synapsekit.loaders.pptx",
133
145
  ]
134
146
  warn_return_any = false
135
147
  # from __future__ import annotations causes mypy to misinterpret
@@ -143,7 +155,9 @@ pep621_dev_dependency_groups = ["dev"]
143
155
  google-generativeai = "google"
144
156
  beautifulsoup4 = "bs4"
145
157
  faiss-cpu = "faiss"
158
+ python-pptx = "pptx"
146
159
 
147
160
  [tool.deptry.per_rule_ignores]
148
- DEP001 = ["sqlalchemy", "pydantic"]
161
+ DEP001 = ["sqlalchemy", "pydantic", "aiohttp"]
149
162
  DEP002 = ["numpy", "rank-bm25", "beautifulsoup4", "lxml", "faiss-cpu", "google-generativeai"]
163
+ DEP004 = ["pydantic"]
@@ -27,6 +27,7 @@ from .agents import (
27
27
  FileWriteTool,
28
28
  FunctionCallingAgent,
29
29
  HTTPRequestTool,
30
+ HumanInputTool,
30
31
  JSONQueryTool,
31
32
  PythonREPLTool,
32
33
  ReActAgent,
@@ -35,6 +36,7 @@ from .agents import (
35
36
  ToolRegistry,
36
37
  ToolResult,
37
38
  WebSearchTool,
39
+ WikipediaTool,
38
40
  tool,
39
41
  )
40
42
  from .embeddings.backend import SynapsekitEmbeddings
@@ -46,15 +48,19 @@ from .graph import (
46
48
  ConditionFn,
47
49
  Edge,
48
50
  GraphConfigError,
51
+ GraphInterrupt,
49
52
  GraphRuntimeError,
50
53
  GraphState,
51
54
  InMemoryCheckpointer,
55
+ InterruptState,
52
56
  Node,
53
57
  NodeFn,
54
58
  SQLiteCheckpointer,
55
59
  StateGraph,
56
60
  agent_node,
61
+ llm_node,
57
62
  rag_node,
63
+ subgraph_node,
58
64
  )
59
65
  from .llm.base import BaseLLM, LLMConfig
60
66
  from .llm.structured import generate_structured
@@ -67,6 +73,9 @@ from .loaders.pdf import PDFLoader
67
73
  from .loaders.text import StringLoader, TextLoader
68
74
  from .loaders.web import WebLoader
69
75
  from .memory.conversation import ConversationMemory
76
+ from .memory.hybrid import HybridMemory
77
+ from .memory.sqlite import SQLiteConversationMemory
78
+ from .memory.summary_buffer import SummaryBufferMemory
70
79
  from .observability.tracer import TokenTracer
71
80
  from .parsers.json_parser import JSONParser
72
81
  from .parsers.list_parser import ListParser
@@ -76,8 +85,15 @@ from .rag.facade import RAG
76
85
  from .rag.pipeline import RAGConfig, RAGPipeline
77
86
  from .retrieval.base import VectorStore
78
87
  from .retrieval.contextual import ContextualRetriever
88
+ from .retrieval.contextual_compression import ContextualCompressionRetriever
89
+ from .retrieval.crag import CRAGRetriever
90
+ from .retrieval.cross_encoder import CrossEncoderReranker
91
+ from .retrieval.ensemble import EnsembleRetriever
92
+ from .retrieval.parent_document import ParentDocumentRetriever
93
+ from .retrieval.query_decomposition import QueryDecompositionRetriever
79
94
  from .retrieval.rag_fusion import RAGFusionRetriever
80
95
  from .retrieval.retriever import Retriever
96
+ from .retrieval.self_query import SelfQueryRetriever
81
97
  from .retrieval.sentence_window import SentenceWindowRetriever
82
98
  from .retrieval.vectorstore import InMemoryVectorStore
83
99
  from .text_splitters import (
@@ -88,7 +104,7 @@ from .text_splitters import (
88
104
  TokenAwareSplitter,
89
105
  )
90
106
 
91
- __version__ = "0.6.0"
107
+ __version__ = "0.6.2"
92
108
  __all__ = [
93
109
  # Facade
94
110
  "RAG",
@@ -117,9 +133,19 @@ __all__ = [
117
133
  "Retriever",
118
134
  "RAGFusionRetriever",
119
135
  "ContextualRetriever",
136
+ "ContextualCompressionRetriever",
137
+ "CRAGRetriever",
138
+ "CrossEncoderReranker",
139
+ "EnsembleRetriever",
140
+ "ParentDocumentRetriever",
141
+ "QueryDecompositionRetriever",
142
+ "SelfQueryRetriever",
120
143
  "SentenceWindowRetriever",
121
144
  # Memory / observability
122
145
  "ConversationMemory",
146
+ "HybridMemory",
147
+ "SQLiteConversationMemory",
148
+ "SummaryBufferMemory",
123
149
  "TokenTracer",
124
150
  # Loaders
125
151
  "Document",
@@ -160,11 +186,13 @@ __all__ = [
160
186
  "FileReadTool",
161
187
  "FileWriteTool",
162
188
  "HTTPRequestTool",
189
+ "HumanInputTool",
163
190
  "JSONQueryTool",
164
191
  "PythonREPLTool",
165
192
  "RegexTool",
166
193
  "SQLQueryTool",
167
194
  "WebSearchTool",
195
+ "WikipediaTool",
168
196
  # Text splitters
169
197
  "BaseSplitter",
170
198
  "CharacterTextSplitter",
@@ -179,7 +207,11 @@ __all__ = [
179
207
  "Node",
180
208
  "NodeFn",
181
209
  "agent_node",
210
+ "llm_node",
182
211
  "rag_node",
212
+ "subgraph_node",
213
+ "GraphInterrupt",
214
+ "InterruptState",
183
215
  "Edge",
184
216
  "ConditionalEdge",
185
217
  "ConditionFn",
@@ -12,11 +12,13 @@ from .tools import (
12
12
  FileReadTool,
13
13
  FileWriteTool,
14
14
  HTTPRequestTool,
15
+ HumanInputTool,
15
16
  JSONQueryTool,
16
17
  PythonREPLTool,
17
18
  RegexTool,
18
19
  SQLQueryTool,
19
20
  WebSearchTool,
21
+ WikipediaTool,
20
22
  )
21
23
 
22
24
  __all__ = [
@@ -40,9 +42,11 @@ __all__ = [
40
42
  "FileReadTool",
41
43
  "FileWriteTool",
42
44
  "HTTPRequestTool",
45
+ "HumanInputTool",
43
46
  "JSONQueryTool",
44
47
  "PythonREPLTool",
45
48
  "RegexTool",
46
49
  "SQLQueryTool",
47
50
  "WebSearchTool",
51
+ "WikipediaTool",
48
52
  ]
@@ -4,11 +4,13 @@ from .file_list import FileListTool
4
4
  from .file_read import FileReadTool
5
5
  from .file_write import FileWriteTool
6
6
  from .http_request import HTTPRequestTool
7
+ from .human_input import HumanInputTool
7
8
  from .json_query import JSONQueryTool
8
9
  from .python_repl import PythonREPLTool
9
10
  from .regex_tool import RegexTool
10
11
  from .sql_query import SQLQueryTool
11
12
  from .web_search import WebSearchTool
13
+ from .wikipedia import WikipediaTool
12
14
 
13
15
  __all__ = [
14
16
  "CalculatorTool",
@@ -17,9 +19,11 @@ __all__ = [
17
19
  "FileReadTool",
18
20
  "FileWriteTool",
19
21
  "HTTPRequestTool",
22
+ "HumanInputTool",
20
23
  "JSONQueryTool",
21
24
  "PythonREPLTool",
22
25
  "RegexTool",
23
26
  "SQLQueryTool",
24
27
  "WebSearchTool",
28
+ "WikipediaTool",
25
29
  ]
@@ -56,9 +56,7 @@ class HTTPRequestTool(BaseTool):
56
56
  try:
57
57
  import aiohttp
58
58
  except ImportError:
59
- raise ImportError(
60
- "aiohttp required for HTTPRequestTool: pip install aiohttp"
61
- ) from None
59
+ raise ImportError("aiohttp required for HTTPRequestTool: pip install aiohttp") from None
62
60
 
63
61
  method = method.upper()
64
62
  req_headers = headers or {}
@@ -0,0 +1,63 @@
1
+ """Human Input Tool: allows agents to ask the user a question mid-execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Any
7
+
8
+ from ..base import BaseTool, ToolResult
9
+
10
+
11
+ class HumanInputTool(BaseTool):
12
+ """Tool that asks the user for input during agent execution.
13
+
14
+ This enables human-in-the-loop agent workflows where the agent can
15
+ request clarification or additional information from the user.
16
+
17
+ Usage::
18
+
19
+ tool = HumanInputTool()
20
+ # Or with a custom input function:
21
+ tool = HumanInputTool(input_fn=my_custom_input)
22
+
23
+ By default, uses Python's built-in ``input()`` function.
24
+ Pass a custom ``input_fn`` for non-terminal environments (web, API, etc.).
25
+ """
26
+
27
+ name = "human_input"
28
+ description = (
29
+ "Ask the user a question and get their response. "
30
+ "Use this when you need clarification, additional information, "
31
+ "or confirmation from the user before proceeding."
32
+ )
33
+ parameters = {
34
+ "type": "object",
35
+ "properties": {
36
+ "question": {
37
+ "type": "string",
38
+ "description": "The question to ask the user",
39
+ },
40
+ },
41
+ "required": ["question"],
42
+ }
43
+
44
+ def __init__(self, input_fn: Any = None) -> None:
45
+ self._input_fn = input_fn
46
+
47
+ async def run(self, question: str = "", **kwargs: Any) -> ToolResult:
48
+ prompt = question or kwargs.get("input", "")
49
+ if not prompt:
50
+ return ToolResult(output="", error="No question provided.")
51
+
52
+ try:
53
+ if self._input_fn is not None:
54
+ result = self._input_fn(prompt)
55
+ if asyncio.iscoroutine(result):
56
+ result = await result
57
+ else:
58
+ loop = asyncio.get_event_loop()
59
+ result = await loop.run_in_executor(None, input, f"\n{prompt}\n> ")
60
+
61
+ return ToolResult(output=str(result))
62
+ except Exception as e:
63
+ return ToolResult(output="", error=f"Failed to get input: {e}")
@@ -66,7 +66,9 @@ class RegexTool(BaseTool):
66
66
 
67
67
  if action == "findall":
68
68
  matches = re.findall(pattern, text, re_flags)
69
- return ToolResult(output="\n".join(str(m) for m in matches) if matches else "(no matches)")
69
+ return ToolResult(
70
+ output="\n".join(str(m) for m in matches) if matches else "(no matches)"
71
+ )
70
72
 
71
73
  elif action == "match":
72
74
  m = re.match(pattern, text, re_flags)
@@ -0,0 +1,103 @@
1
+ """Wikipedia Tool: search and fetch Wikipedia articles."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from urllib.parse import quote_plus
7
+
8
+ from ..base import BaseTool, ToolResult
9
+
10
+
11
+ class WikipediaTool(BaseTool):
12
+ """Search and fetch Wikipedia article summaries.
13
+
14
+ Uses the Wikipedia REST API — no API key required, no extra dependencies.
15
+
16
+ Usage::
17
+
18
+ tool = WikipediaTool()
19
+ result = await tool.run(query="Python programming language")
20
+ """
21
+
22
+ name = "wikipedia"
23
+ description = (
24
+ "Search Wikipedia and return article summaries. "
25
+ "Input: a search query. "
26
+ "Returns: the title and summary of the most relevant Wikipedia article."
27
+ )
28
+ parameters = {
29
+ "type": "object",
30
+ "properties": {
31
+ "query": {
32
+ "type": "string",
33
+ "description": "The search query for Wikipedia",
34
+ },
35
+ "max_results": {
36
+ "type": "integer",
37
+ "description": "Maximum number of articles to return (default: 1)",
38
+ "default": 1,
39
+ },
40
+ },
41
+ "required": ["query"],
42
+ }
43
+
44
+ def __init__(self, max_chars: int = 4000) -> None:
45
+ self._max_chars = max_chars
46
+
47
+ async def run(self, query: str = "", max_results: int = 1, **kwargs: Any) -> ToolResult:
48
+ search_query = query or kwargs.get("input", "")
49
+ if not search_query:
50
+ return ToolResult(output="", error="No search query provided.")
51
+
52
+ try:
53
+ import urllib.request
54
+
55
+ # Search for articles
56
+ search_url = (
57
+ f"https://en.wikipedia.org/w/api.php?action=opensearch"
58
+ f"&search={quote_plus(search_query)}&limit={max_results}&format=json"
59
+ )
60
+
61
+ import asyncio
62
+ import json
63
+
64
+ loop = asyncio.get_event_loop()
65
+
66
+ def _fetch_search():
67
+ req = urllib.request.Request(search_url, headers={"User-Agent": "SynapseKit/1.0"})
68
+ with urllib.request.urlopen(req, timeout=10) as resp:
69
+ return json.loads(resp.read().decode())
70
+
71
+ search_data = await loop.run_in_executor(None, _fetch_search)
72
+
73
+ if len(search_data) < 2 or not search_data[1]:
74
+ return ToolResult(output="No Wikipedia articles found.")
75
+
76
+ results = []
77
+ titles = search_data[1][:max_results]
78
+
79
+ for title in titles:
80
+ # Fetch article summary
81
+ summary_url = (
82
+ f"https://en.wikipedia.org/api/rest_v1/page/summary/{quote_plus(title)}"
83
+ )
84
+
85
+ def _fetch_summary(url=summary_url):
86
+ req = urllib.request.Request(url, headers={"User-Agent": "SynapseKit/1.0"})
87
+ with urllib.request.urlopen(req, timeout=10) as resp:
88
+ return json.loads(resp.read().decode())
89
+
90
+ summary_data = await loop.run_in_executor(None, _fetch_summary)
91
+
92
+ article_title = summary_data.get("title", title)
93
+ extract = summary_data.get("extract", "No summary available.")
94
+ url = summary_data.get("content_urls", {}).get("desktop", {}).get("page", "")
95
+
96
+ if len(extract) > self._max_chars:
97
+ extract = extract[: self._max_chars] + "..."
98
+
99
+ results.append(f"**{article_title}**\n{url}\n\n{extract}")
100
+
101
+ return ToolResult(output="\n\n---\n\n".join(results))
102
+ except Exception as e:
103
+ return ToolResult(output="", error=f"Wikipedia search failed: {e}")
@@ -3,8 +3,10 @@ from .compiled import CompiledGraph
3
3
  from .edge import ConditionalEdge, ConditionFn, Edge
4
4
  from .errors import GraphConfigError, GraphRuntimeError
5
5
  from .graph import StateGraph
6
- from .node import Node, NodeFn, agent_node, rag_node
6
+ from .interrupt import GraphInterrupt, InterruptState
7
+ from .node import Node, NodeFn, agent_node, llm_node, rag_node
7
8
  from .state import END, GraphState
9
+ from .subgraph import subgraph_node
8
10
 
9
11
  __all__ = [
10
12
  "END",
@@ -14,13 +16,17 @@ __all__ = [
14
16
  "ConditionalEdge",
15
17
  "Edge",
16
18
  "GraphConfigError",
19
+ "GraphInterrupt",
17
20
  "GraphRuntimeError",
18
21
  "GraphState",
19
22
  "InMemoryCheckpointer",
23
+ "InterruptState",
20
24
  "Node",
21
25
  "NodeFn",
22
26
  "SQLiteCheckpointer",
23
27
  "StateGraph",
24
28
  "agent_node",
29
+ "llm_node",
25
30
  "rag_node",
31
+ "subgraph_node",
26
32
  ]
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any
7
7
 
8
8
  from .edge import ConditionalEdge, Edge
9
9
  from .errors import GraphRuntimeError
10
+ from .interrupt import GraphInterrupt
10
11
  from .mermaid import get_mermaid
11
12
  from .state import END
12
13
 
@@ -71,12 +72,22 @@ class CompiledGraph:
71
72
  self,
72
73
  graph_id: str,
73
74
  checkpointer: BaseCheckpointer,
75
+ updates: dict[str, Any] | None = None,
74
76
  ) -> dict[str, Any]:
75
- """Resume execution from a checkpointed state."""
77
+ """Resume execution from a checkpointed state.
78
+
79
+ Args:
80
+ graph_id: The graph execution ID to resume.
81
+ checkpointer: The checkpointer that holds the saved state.
82
+ updates: Optional state updates to apply before resuming
83
+ (e.g. human-provided edits after a ``GraphInterrupt``).
84
+ """
76
85
  saved = checkpointer.load(graph_id)
77
86
  if saved is None:
78
87
  raise GraphRuntimeError(f"No checkpoint found for graph_id={graph_id!r}.")
79
88
  _step, state = saved
89
+ if updates:
90
+ state.update(updates)
80
91
  return await self.run(state, checkpointer=checkpointer, graph_id=graph_id)
81
92
 
82
93
  def run_sync(
@@ -90,6 +101,62 @@ class CompiledGraph:
90
101
 
91
102
  return run_sync(self.run(state, checkpointer=checkpointer, graph_id=graph_id))
92
103
 
104
+ async def stream_tokens(
105
+ self,
106
+ state: dict[str, Any],
107
+ ) -> AsyncGenerator[dict[str, Any]]:
108
+ """Yield token-level events from LLM nodes.
109
+
110
+ Yields dicts with either:
111
+ - ``{"type": "token", "node": name, "token": str}`` for streaming tokens
112
+ - ``{"type": "node_complete", "node": name, "state": dict}`` for non-streaming nodes
113
+
114
+ LLM nodes are detected by checking if the node function's return dict
115
+ contains a ``"__stream__"`` key with an async generator.
116
+ """
117
+ state = dict(state)
118
+ graph = self._graph
119
+ current_wave: list[str] = [graph._entry_point] # type: ignore[list-item]
120
+ steps = 0
121
+
122
+ while current_wave:
123
+ if steps >= self._max_steps:
124
+ raise GraphRuntimeError(
125
+ f"Graph exceeded _MAX_STEPS={self._max_steps}. "
126
+ "Check for infinite loops in conditional edges."
127
+ )
128
+ steps += 1
129
+
130
+ for name in current_wave:
131
+ node = graph._nodes.get(name)
132
+ if node is None:
133
+ raise GraphRuntimeError(f"Node {name!r} not found in graph.")
134
+
135
+ result = node.fn(state)
136
+ if inspect.isawaitable(result):
137
+ result = await result
138
+
139
+ if not isinstance(result, dict):
140
+ raise GraphRuntimeError(
141
+ f"Node {name!r} must return a dict, got {type(result).__name__!r}."
142
+ )
143
+
144
+ # Check for streaming token generator
145
+ stream_gen = result.pop("__stream__", None)
146
+ if stream_gen is not None:
147
+ collected: list[str] = []
148
+ async for token in stream_gen:
149
+ collected.append(token)
150
+ yield {"type": "token", "node": name, "token": token}
151
+ # Store the full text in the result
152
+ if "__stream_key__" in result:
153
+ result[result.pop("__stream_key__")] = "".join(collected)
154
+
155
+ state.update(result)
156
+ yield {"type": "node_complete", "node": name, "state": dict(state)}
157
+
158
+ current_wave = await self._next_wave(current_wave, state)
159
+
93
160
  def get_mermaid(self) -> str:
94
161
  return get_mermaid(self._graph)
95
162
 
@@ -116,7 +183,15 @@ class CompiledGraph:
116
183
  steps += 1
117
184
 
118
185
  # Run all nodes in this wave concurrently
119
- results = await asyncio.gather(*[self._call_node(name, state) for name in current_wave])
186
+ try:
187
+ results = await asyncio.gather(
188
+ *[self._call_node(name, state) for name in current_wave]
189
+ )
190
+ except GraphInterrupt as exc:
191
+ # Save state and raise InterruptState for the caller
192
+ if checkpointer is not None and graph_id is not None:
193
+ checkpointer.save(graph_id, steps, dict(state))
194
+ raise GraphInterrupt(exc.message, exc.data) from None
120
195
 
121
196
  # Merge partial results into state and yield events
122
197
  for name, partial in zip(current_wave, results, strict=False):