tabulaflow 0.1.0__py3-none-any.whl

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 (370) hide show
  1. tabulaflow/__init__.py +3 -0
  2. tabulaflow/_paths.py +5 -0
  3. tabulaflow/agents/__init__.py +42 -0
  4. tabulaflow/agents/_cache.py +73 -0
  5. tabulaflow/agents/chat/__init__.py +80 -0
  6. tabulaflow/agents/chat/compaction.py +323 -0
  7. tabulaflow/agents/chat/events.py +161 -0
  8. tabulaflow/agents/chat/input.py +30 -0
  9. tabulaflow/agents/chat/session.py +926 -0
  10. tabulaflow/agents/chat/system_prompt.md +254 -0
  11. tabulaflow/agents/chat/tools.py +57 -0
  12. tabulaflow/agents/chat/turn.py +325 -0
  13. tabulaflow/agents/config.py +48 -0
  14. tabulaflow/agents/enrichment.py +411 -0
  15. tabulaflow/agents/extraction/__init__.py +26 -0
  16. tabulaflow/agents/extraction/extractor.py +237 -0
  17. tabulaflow/agents/extraction/markdown.py +439 -0
  18. tabulaflow/agents/llm.py +326 -0
  19. tabulaflow/agents/media.py +258 -0
  20. tabulaflow/agents/message_store.py +254 -0
  21. tabulaflow/agents/runtime.py +146 -0
  22. tabulaflow/agents/summarization.py +157 -0
  23. tabulaflow/agents/tools/__init__.py +151 -0
  24. tabulaflow/agents/tools/_sql.py +158 -0
  25. tabulaflow/agents/tools/add_canonical_name.py +915 -0
  26. tabulaflow/agents/tools/browser/__init__.py +1 -0
  27. tabulaflow/agents/tools/browser/aria.py +1312 -0
  28. tabulaflow/agents/tools/browser/manager.py +154 -0
  29. tabulaflow/agents/tools/browser/tool.py +1438 -0
  30. tabulaflow/agents/tools/connect_data_source.py +102 -0
  31. tabulaflow/agents/tools/create_parameterized_source.py +375 -0
  32. tabulaflow/agents/tools/extract_rows_from_documents.py +387 -0
  33. tabulaflow/agents/tools/filesystem/__init__.py +1 -0
  34. tabulaflow/agents/tools/filesystem/access.py +95 -0
  35. tabulaflow/agents/tools/filesystem/edit.py +173 -0
  36. tabulaflow/agents/tools/filesystem/patch.py +174 -0
  37. tabulaflow/agents/tools/filesystem/patch_engine.py +526 -0
  38. tabulaflow/agents/tools/filesystem/view.py +297 -0
  39. tabulaflow/agents/tools/get_column_json_schema.py +253 -0
  40. tabulaflow/agents/tools/get_table_schema.py +204 -0
  41. tabulaflow/agents/tools/protocols.py +120 -0
  42. tabulaflow/agents/tools/registry/__init__.py +1 -0
  43. tabulaflow/agents/tools/registry/get_column_json_schema.py +104 -0
  44. tabulaflow/agents/tools/registry/get_data_source_document.py +176 -0
  45. tabulaflow/agents/tools/registry/get_schema.py +118 -0
  46. tabulaflow/agents/tools/registry/get_table_schema.py +125 -0
  47. tabulaflow/agents/tools/registry/run_query.py +174 -0
  48. tabulaflow/agents/tools/registry/write_result_table.py +112 -0
  49. tabulaflow/agents/tools/render_chart.py +402 -0
  50. tabulaflow/agents/tools/render_graph.py +137 -0
  51. tabulaflow/agents/tools/render_map.py +145 -0
  52. tabulaflow/agents/tools/run_query.py +406 -0
  53. tabulaflow/agents/tools/run_subagent_for_each_row.py +583 -0
  54. tabulaflow/agents/tools/shell/__init__.py +1 -0
  55. tabulaflow/agents/tools/shell/guard.py +61 -0
  56. tabulaflow/agents/tools/shell/tool.py +442 -0
  57. tabulaflow/agents/tools/show_artifacts.py +88 -0
  58. tabulaflow/agents/trace.py +321 -0
  59. tabulaflow/app/__init__.py +0 -0
  60. tabulaflow/app/assets/__init__.py +0 -0
  61. tabulaflow/app/assets/samples/__init__.py +0 -0
  62. tabulaflow/app/assets/samples/sample.sqlite +0 -0
  63. tabulaflow/app/config.py +331 -0
  64. tabulaflow/app/main.py +80 -0
  65. tabulaflow/app/pane/__init__.py +5 -0
  66. tabulaflow/app/pane/assets/__init__.py +1 -0
  67. tabulaflow/app/pane/assets/fonts/figtree/Figtree-Variable.woff2 +0 -0
  68. tabulaflow/app/pane/assets/fonts/figtree/OFL.txt +93 -0
  69. tabulaflow/app/pane/assets/ui/__init__.py +1 -0
  70. tabulaflow/app/pane/assets/ui/contract.d.ts +180 -0
  71. tabulaflow/app/pane/assets/ui/index.html +40 -0
  72. tabulaflow/app/pane/assets/ui/pane.css +911 -0
  73. tabulaflow/app/pane/assets/ui/pane.js +1567 -0
  74. tabulaflow/app/pane/assets/ui/render/chart.js +201 -0
  75. tabulaflow/app/pane/assets/ui/render/code.js +35 -0
  76. tabulaflow/app/pane/assets/ui/render/color-domains.js +27 -0
  77. tabulaflow/app/pane/assets/ui/render/graph.js +756 -0
  78. tabulaflow/app/pane/assets/ui/render/map.js +1100 -0
  79. tabulaflow/app/pane/assets/ui/render/markdown.js +95 -0
  80. tabulaflow/app/pane/assets/ui/render/query.js +15 -0
  81. tabulaflow/app/pane/assets/ui/render/shared.js +258 -0
  82. tabulaflow/app/pane/assets/ui/render/table.js +411 -0
  83. tabulaflow/app/pane/assets/vendor/cytoscape/LICENSE-cytoscape-dagre.txt +21 -0
  84. tabulaflow/app/pane/assets/vendor/cytoscape/LICENSE-cytoscape.txt +19 -0
  85. tabulaflow/app/pane/assets/vendor/cytoscape/LICENSE-dagre.txt +19 -0
  86. tabulaflow/app/pane/assets/vendor/cytoscape/cytoscape-dagre.min.js +397 -0
  87. tabulaflow/app/pane/assets/vendor/cytoscape/cytoscape.min.js +32 -0
  88. tabulaflow/app/pane/assets/vendor/cytoscape/dagre.min.js +3809 -0
  89. tabulaflow/app/pane/assets/vendor/katex/LICENSE.txt +21 -0
  90. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  91. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  92. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  93. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  94. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  95. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  96. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  97. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  98. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  99. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  100. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  101. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  102. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  103. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  104. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  105. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  106. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  107. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  108. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  109. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  110. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  111. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  112. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  113. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  114. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  115. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  116. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  117. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  118. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  119. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  120. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  121. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  122. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  123. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  124. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  125. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  126. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  127. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  128. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  129. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  130. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  131. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  132. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  133. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  134. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  135. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  136. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  137. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  138. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  139. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  140. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  141. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  142. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  143. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  144. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  145. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  146. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  147. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  148. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  149. tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  150. tabulaflow/app/pane/assets/vendor/katex/katex.min.css +1 -0
  151. tabulaflow/app/pane/assets/vendor/katex/katex.min.js +1 -0
  152. tabulaflow/app/pane/assets/vendor/maplibre/LICENSE.txt +116 -0
  153. tabulaflow/app/pane/assets/vendor/maplibre/__init__.py +1 -0
  154. tabulaflow/app/pane/assets/vendor/maplibre/airport-labels.geojson +1 -0
  155. tabulaflow/app/pane/assets/vendor/maplibre/continent-labels.geojson +12 -0
  156. tabulaflow/app/pane/assets/vendor/maplibre/maplibre-gl.css +1 -0
  157. tabulaflow/app/pane/assets/vendor/maplibre/maplibre-gl.js +59 -0
  158. tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-admin0-boundaries.geojson +1 -0
  159. tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-admin1-boundaries.geojson +1 -0
  160. tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-airports-source.txt +8 -0
  161. tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-boundaries-source.txt +10 -0
  162. tabulaflow/app/pane/assets/vendor/maplibre/ocean-labels.geojson +10 -0
  163. tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite-source.txt +26 -0
  164. tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite.json +709 -0
  165. tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite.png +0 -0
  166. tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite@2x.json +709 -0
  167. tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite@2x.png +0 -0
  168. tabulaflow/app/pane/assets/vendor/maplibre/shortbread-light.json +2535 -0
  169. tabulaflow/app/pane/assets/vendor/maplibre/tf-airport-icon-draft.svg +36 -0
  170. tabulaflow/app/pane/assets/vendor/maplibre/tf-interstate-shield-draft.svg +88 -0
  171. tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite-source.txt +20 -0
  172. tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite.json +30 -0
  173. tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite.png +0 -0
  174. tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite@2x.json +30 -0
  175. tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite@2x.png +0 -0
  176. tabulaflow/app/pane/assets/vendor/markdown-it/LICENSE.txt +22 -0
  177. tabulaflow/app/pane/assets/vendor/markdown-it/markdown-it.min.js +3 -0
  178. tabulaflow/app/pane/assets/vendor/markdown-it-texmath/LICENSE.txt +21 -0
  179. tabulaflow/app/pane/assets/vendor/markdown-it-texmath/texmath.css +19 -0
  180. tabulaflow/app/pane/assets/vendor/markdown-it-texmath/texmath.js +368 -0
  181. tabulaflow/app/pane/assets/vendor/tabulator/LICENSE.txt +21 -0
  182. tabulaflow/app/pane/assets/vendor/tabulator/__init__.py +0 -0
  183. tabulaflow/app/pane/assets/vendor/tabulator/tabulator.min.css +2 -0
  184. tabulaflow/app/pane/assets/vendor/tabulator/tabulator.min.js +3 -0
  185. tabulaflow/app/pane/assets/vendor/vega/LICENSE-vega-embed.txt +27 -0
  186. tabulaflow/app/pane/assets/vendor/vega/LICENSE-vega-lite.txt +27 -0
  187. tabulaflow/app/pane/assets/vendor/vega/LICENSE-vega.txt +27 -0
  188. tabulaflow/app/pane/assets/vendor/vega/__init__.py +0 -0
  189. tabulaflow/app/pane/assets/vendor/vega/vega-embed.min.js +7 -0
  190. tabulaflow/app/pane/assets/vendor/vega/vega-lite.min.js +2 -0
  191. tabulaflow/app/pane/assets/vendor/vega/vega.min.js +2 -0
  192. tabulaflow/app/pane/cards.py +301 -0
  193. tabulaflow/app/pane/charts.py +179 -0
  194. tabulaflow/app/pane/contract.py +237 -0
  195. tabulaflow/app/pane/graphs.py +91 -0
  196. tabulaflow/app/pane/maps.py +215 -0
  197. tabulaflow/app/pane/server.py +661 -0
  198. tabulaflow/app/pane/tables.py +293 -0
  199. tabulaflow/app/runtime_paths.py +109 -0
  200. tabulaflow/app/sample_data.py +104 -0
  201. tabulaflow/app/session.py +289 -0
  202. tabulaflow/app/theme.py +87 -0
  203. tabulaflow/app/tui/__init__.py +5 -0
  204. tabulaflow/app/tui/app.py +1362 -0
  205. tabulaflow/app/tui/banner.py +185 -0
  206. tabulaflow/app/tui/cells.py +60 -0
  207. tabulaflow/app/tui/clipboard.py +30 -0
  208. tabulaflow/app/tui/commands.py +367 -0
  209. tabulaflow/app/tui/rendering.py +447 -0
  210. tabulaflow/app/tui/screens/__init__.py +1 -0
  211. tabulaflow/app/tui/screens/config.py +346 -0
  212. tabulaflow/app/tui/screens/results.py +827 -0
  213. tabulaflow/app/tui/screens/schema.py +839 -0
  214. tabulaflow/app/tui/spinner.py +15 -0
  215. tabulaflow/app/tui/theme.py +215 -0
  216. tabulaflow/app/tui/tui.tcss +140 -0
  217. tabulaflow/app/tui/widgets/__init__.py +1 -0
  218. tabulaflow/app/tui/widgets/chat.py +120 -0
  219. tabulaflow/app/tui/widgets/chat_log.py +27 -0
  220. tabulaflow/app/tui/widgets/choice.py +176 -0
  221. tabulaflow/app/tui/widgets/input.py +596 -0
  222. tabulaflow/app/tui/widgets/markdown.py +372 -0
  223. tabulaflow/app/tui/widgets/progress.py +760 -0
  224. tabulaflow/app/tui/widgets/result.py +908 -0
  225. tabulaflow/app/tui/widgets/suggestions.py +143 -0
  226. tabulaflow/app/turn.py +22 -0
  227. tabulaflow/cli.py +95 -0
  228. tabulaflow/core/__init__.py +111 -0
  229. tabulaflow/core/_cache.py +65 -0
  230. tabulaflow/core/dataframe.py +487 -0
  231. tabulaflow/core/media.py +156 -0
  232. tabulaflow/core/registry.py +34 -0
  233. tabulaflow/core/results.py +78 -0
  234. tabulaflow/core/schema.py +318 -0
  235. tabulaflow/core/serialization.py +44 -0
  236. tabulaflow/data/__init__.py +70 -0
  237. tabulaflow/data/_cache.py +66 -0
  238. tabulaflow/data/catalog.py +72 -0
  239. tabulaflow/data/config.py +109 -0
  240. tabulaflow/data/connect.py +420 -0
  241. tabulaflow/data/json_schema.py +160 -0
  242. tabulaflow/data/loaders/__init__.py +53 -0
  243. tabulaflow/data/loaders/_runner.py +75 -0
  244. tabulaflow/data/loaders/files.py +254 -0
  245. tabulaflow/data/loaders/huggingface.py +781 -0
  246. tabulaflow/data/neo4j.py +647 -0
  247. tabulaflow/data/protocols.py +87 -0
  248. tabulaflow/data/registry.py +104 -0
  249. tabulaflow/data/sparql.py +452 -0
  250. tabulaflow/data/sql.py +2896 -0
  251. tabulaflow/examples/__init__.py +1 -0
  252. tabulaflow/examples/ambiguity_aware_queries.py +65 -0
  253. tabulaflow/examples/chat_sessions.py +75 -0
  254. tabulaflow/examples/cli.py +58 -0
  255. tabulaflow/examples/compare_research_agents.py +64 -0
  256. tabulaflow/examples/custom_agents.py +80 -0
  257. tabulaflow/examples/data_enrichment.py +62 -0
  258. tabulaflow/examples/document_extraction.py +41 -0
  259. tabulaflow/examples/quick_start.py +70 -0
  260. tabulaflow/examples/research_quick_start.py +46 -0
  261. tabulaflow/examples/structured_outputs.py +116 -0
  262. tabulaflow/examples/support/__init__.py +1 -0
  263. tabulaflow/examples/support/dock-guide.pdf +0 -0
  264. tabulaflow/examples/support/faq.txt +16 -0
  265. tabulaflow/examples/support/travel_guide.txt +116 -0
  266. tabulaflow/examples/table_linking_agent.py +116 -0
  267. tabulaflow/examples/working_with_data.py +70 -0
  268. tabulaflow/output/__init__.py +14 -0
  269. tabulaflow/output/charts.py +167 -0
  270. tabulaflow/output/formatting/__init__.py +77 -0
  271. tabulaflow/output/formatting/_core.py +369 -0
  272. tabulaflow/output/formatting/_sql.py +124 -0
  273. tabulaflow/output/formatting/_table_grouping.py +242 -0
  274. tabulaflow/output/formatting/cypher.py +82 -0
  275. tabulaflow/output/formatting/resolution.py +60 -0
  276. tabulaflow/output/formatting/schema.py +54 -0
  277. tabulaflow/output/formatting/sparql.py +20 -0
  278. tabulaflow/output/formatting/sql_compact.py +191 -0
  279. tabulaflow/output/formatting/sql_ddl.py +288 -0
  280. tabulaflow/output/graphs.py +446 -0
  281. tabulaflow/output/maps.py +465 -0
  282. tabulaflow/output/resolver.py +316 -0
  283. tabulaflow/output/specs.py +291 -0
  284. tabulaflow/output/store.py +523 -0
  285. tabulaflow/research/__init__.py +1 -0
  286. tabulaflow/research/agents/__init__.py +33 -0
  287. tabulaflow/research/agents/ambig_flat.py +340 -0
  288. tabulaflow/research/agents/ambig_simple.py +123 -0
  289. tabulaflow/research/agents/ambig_structured.py +369 -0
  290. tabulaflow/research/agents/dbt.py +262 -0
  291. tabulaflow/research/agents/direct_prompt.py +91 -0
  292. tabulaflow/research/agents/ensemblers/__init__.py +11 -0
  293. tabulaflow/research/agents/ensemblers/agent.py +283 -0
  294. tabulaflow/research/agents/ensemblers/dbt.py +214 -0
  295. tabulaflow/research/agents/ensemblers/llm.py +211 -0
  296. tabulaflow/research/agents/ensemblers/majority.py +74 -0
  297. tabulaflow/research/agents/ensemblers/utils.py +31 -0
  298. tabulaflow/research/agents/full_schema.py +117 -0
  299. tabulaflow/research/agents/registry.py +71 -0
  300. tabulaflow/research/agents/schema_discovery.py +143 -0
  301. tabulaflow/research/agents/schema_linking.py +515 -0
  302. tabulaflow/research/agents/user_simulator.py +274 -0
  303. tabulaflow/research/agents/utils.py +119 -0
  304. tabulaflow/research/ambiguity.py +65 -0
  305. tabulaflow/research/benchmarks/__init__.py +56 -0
  306. tabulaflow/research/benchmarks/ambrosia_s.py +284 -0
  307. tabulaflow/research/benchmarks/arcs.py +246 -0
  308. tabulaflow/research/benchmarks/beaver.py +239 -0
  309. tabulaflow/research/benchmarks/bird_sql.py +357 -0
  310. tabulaflow/research/benchmarks/cypherbench.py +322 -0
  311. tabulaflow/research/benchmarks/installation.py +218 -0
  312. tabulaflow/research/benchmarks/registry.py +146 -0
  313. tabulaflow/research/benchmarks/runtime.py +110 -0
  314. tabulaflow/research/benchmarks/spider2_dbt.py +376 -0
  315. tabulaflow/research/benchmarks/spider2_lite.py +507 -0
  316. tabulaflow/research/benchmarks/spider2_snow.py +348 -0
  317. tabulaflow/research/cli.py +123 -0
  318. tabulaflow/research/metrics/__init__.py +59 -0
  319. tabulaflow/research/metrics/aggregators.py +250 -0
  320. tabulaflow/research/metrics/ambig_point_stats.py +398 -0
  321. tabulaflow/research/metrics/bird_sql_ex.py +31 -0
  322. tabulaflow/research/metrics/bird_sql_ex_soft.py +64 -0
  323. tabulaflow/research/metrics/cypherbench_ex.py +181 -0
  324. tabulaflow/research/metrics/executable.py +19 -0
  325. tabulaflow/research/metrics/found_one.py +65 -0
  326. tabulaflow/research/metrics/gold_ambig_point_stats.py +29 -0
  327. tabulaflow/research/metrics/gold_executable.py +16 -0
  328. tabulaflow/research/metrics/gold_result_not_empty.py +17 -0
  329. tabulaflow/research/metrics/pred_success.py +15 -0
  330. tabulaflow/research/metrics/psjs.py +183 -0
  331. tabulaflow/research/metrics/raw_pred_bird_sql_ex.py +19 -0
  332. tabulaflow/research/metrics/raw_pred_simple_ex.py +23 -0
  333. tabulaflow/research/metrics/registry.py +28 -0
  334. tabulaflow/research/metrics/schema_linking_stats.py +66 -0
  335. tabulaflow/research/metrics/simple_ex.py +136 -0
  336. tabulaflow/research/metrics/spider2_duckdb_match.py +106 -0
  337. tabulaflow/research/metrics/spider2_ex.py +123 -0
  338. tabulaflow/research/metrics/utils.py +84 -0
  339. tabulaflow/research/observability.py +96 -0
  340. tabulaflow/research/pipelines/__init__.py +42 -0
  341. tabulaflow/research/pipelines/ensemble.py +290 -0
  342. tabulaflow/research/pipelines/evaluate.py +149 -0
  343. tabulaflow/research/pipelines/execute.py +89 -0
  344. tabulaflow/research/pipelines/predict.py +387 -0
  345. tabulaflow/research/pipelines/preprocess.py +106 -0
  346. tabulaflow/research/pipelines/utils.py +75 -0
  347. tabulaflow/research/preprocessing/__init__.py +25 -0
  348. tabulaflow/research/preprocessing/column_profiler.py +76 -0
  349. tabulaflow/research/preprocessing/erd.py +322 -0
  350. tabulaflow/research/preprocessing/fk_predictor.py +98 -0
  351. tabulaflow/research/preprocessing/question_embedding.py +162 -0
  352. tabulaflow/research/preprocessing/registry.py +53 -0
  353. tabulaflow/research/preprocessing/schema.py +83 -0
  354. tabulaflow/research/query_analysis.py +59 -0
  355. tabulaflow/research/query_execution.py +71 -0
  356. tabulaflow/research/reporting.py +374 -0
  357. tabulaflow/research/tools/__init__.py +34 -0
  358. tabulaflow/research/tools/ask_user.py +43 -0
  359. tabulaflow/research/tools/finish.py +52 -0
  360. tabulaflow/research/tools/get_column_description.py +60 -0
  361. tabulaflow/research/tools/get_schema.py +45 -0
  362. tabulaflow/research/tools/run_dbt.py +176 -0
  363. tabulaflow/research/tools/search_keywords.py +90 -0
  364. tabulaflow/research/types.py +737 -0
  365. tabulaflow-0.1.0.dist-info/METADATA +496 -0
  366. tabulaflow-0.1.0.dist-info/RECORD +370 -0
  367. tabulaflow-0.1.0.dist-info/WHEEL +5 -0
  368. tabulaflow-0.1.0.dist-info/entry_points.txt +2 -0
  369. tabulaflow-0.1.0.dist-info/licenses/LICENSE +29 -0
  370. tabulaflow-0.1.0.dist-info/top_level.txt +1 -0
tabulaflow/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("tabulaflow")
tabulaflow/_paths.py ADDED
@@ -0,0 +1,5 @@
1
+ """Package-wide filesystem defaults."""
2
+
3
+ from pathlib import Path
4
+
5
+ DEFAULT_HOME_DIR = Path.home() / ".tabulaflow"
@@ -0,0 +1,42 @@
1
+ """Agent runtime, reusable chat sessions, tools, and LLM-powered modules."""
2
+
3
+ from importlib import import_module
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ from tabulaflow.agents.chat.input import ChatInput
8
+ from tabulaflow.agents.chat.session import ChatSession
9
+ from tabulaflow.agents.config import AgentRuntimeConfig
10
+ from tabulaflow.agents.runtime import initialize_agent_runtime, set_llm_requests_per_minute
11
+ from tabulaflow.agents.trace import instrument_agents
12
+
13
+ _LAZY_EXPORTS = {
14
+ "AgentRuntimeConfig": ("tabulaflow.agents.config", "AgentRuntimeConfig"),
15
+ "ChatInput": ("tabulaflow.agents.chat.input", "ChatInput"),
16
+ "ChatSession": ("tabulaflow.agents.chat", "ChatSession"),
17
+ "initialize_agent_runtime": ("tabulaflow.agents.runtime", "initialize_agent_runtime"),
18
+ "set_llm_requests_per_minute": ("tabulaflow.agents.runtime", "set_llm_requests_per_minute"),
19
+ "instrument_agents": ("tabulaflow.agents.trace", "instrument_agents"),
20
+ }
21
+
22
+ __all__ = [
23
+ "AgentRuntimeConfig",
24
+ "ChatInput",
25
+ "ChatSession",
26
+ "initialize_agent_runtime",
27
+ "instrument_agents",
28
+ "set_llm_requests_per_minute",
29
+ ]
30
+
31
+
32
+ def __getattr__(name: str) -> Any:
33
+ if name not in _LAZY_EXPORTS:
34
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
35
+ module_name, attr_name = _LAZY_EXPORTS[name]
36
+ value = getattr(import_module(module_name), attr_name)
37
+ globals()[name] = value
38
+ return value
39
+
40
+
41
+ def __dir__() -> list[str]:
42
+ return sorted({*globals(), *_LAZY_EXPORTS})
@@ -0,0 +1,73 @@
1
+ """Cache-mode orchestration for expensive agent capabilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable
6
+ from pathlib import Path
7
+ from typing import TypeVar
8
+
9
+ from pydantic import BaseModel, ValidationError
10
+
11
+ from tabulaflow.agents.config import AgentCacheMode
12
+ from tabulaflow.core._cache import cache_lock, read_cached_model, remove_cached_file, write_cached_model
13
+
14
+ _T = TypeVar("_T")
15
+ _ModelT = TypeVar("_ModelT", bound=BaseModel)
16
+
17
+
18
+ class InvalidCacheEntry(ValueError):
19
+ """A cache file exists but cannot be decoded as its expected value."""
20
+
21
+
22
+ async def load_or_compute(
23
+ *,
24
+ path: Path,
25
+ mode: AgentCacheMode,
26
+ load: Callable[[Path], Awaitable[_T]],
27
+ compute: Callable[[], Awaitable[_T]],
28
+ store: Callable[[Path, _T], Awaitable[None]],
29
+ ) -> _T:
30
+ """Resolve one cache entry according to the configured cache mode."""
31
+ if mode == "off":
32
+ return await compute()
33
+
34
+ async with cache_lock(path):
35
+ if mode in ("read_write", "cache_only") and path.exists():
36
+ try:
37
+ return await load(path)
38
+ except InvalidCacheEntry:
39
+ if mode == "cache_only":
40
+ raise
41
+ await remove_cached_file(path)
42
+
43
+ if mode == "cache_only":
44
+ raise FileNotFoundError(f"Cache entry not found: {path}")
45
+
46
+ value = await compute()
47
+ if mode in ("read_write", "refresh"):
48
+ await store(path, value)
49
+ return value
50
+
51
+
52
+ async def load_or_compute_model(
53
+ *,
54
+ path: Path,
55
+ mode: AgentCacheMode,
56
+ model_type: type[_ModelT],
57
+ compute: Callable[[], Awaitable[_ModelT]],
58
+ ) -> _ModelT:
59
+ """Load or compute a Pydantic model under the configured cache policy."""
60
+
61
+ async def load(cache_path: Path) -> _ModelT:
62
+ try:
63
+ return await read_cached_model(cache_path, model_type)
64
+ except ValidationError as exc:
65
+ raise InvalidCacheEntry(f"Invalid cache entry: {cache_path}") from exc
66
+
67
+ return await load_or_compute(
68
+ path=path,
69
+ mode=mode,
70
+ load=load,
71
+ compute=compute,
72
+ store=write_cached_model,
73
+ )
@@ -0,0 +1,80 @@
1
+ """Stateful chat sessions and their semantic event stream.
2
+
3
+ Use :meth:`ChatSession.run` for a final result or :meth:`ChatSession.run_stream`
4
+ for live events ending in :class:`TurnFinished`. Close each session with
5
+ ``await session.aclose()``.
6
+ """
7
+
8
+ from importlib import import_module
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from tabulaflow.agents.chat.compaction import CompactionConfig
13
+ from tabulaflow.agents.chat.input import ChatInput
14
+ from tabulaflow.agents.chat.events import (
15
+ AnswerDelta,
16
+ ChatEvent,
17
+ ChatResult,
18
+ CompactionFinished,
19
+ CompactionStarted,
20
+ TurnFinished,
21
+ NarrationDelta,
22
+ ThinkingDelta,
23
+ ToolCallOutcome,
24
+ ToolFinished,
25
+ ToolProgress,
26
+ ToolStarted,
27
+ UsageUpdated,
28
+ )
29
+ from tabulaflow.agents.chat.session import ChatSession
30
+
31
+ _LAZY_EXPORTS = {
32
+ "AnswerDelta": ("tabulaflow.agents.chat.events", "AnswerDelta"),
33
+ "ChatEvent": ("tabulaflow.agents.chat.events", "ChatEvent"),
34
+ "ChatResult": ("tabulaflow.agents.chat.events", "ChatResult"),
35
+ "CompactionFinished": ("tabulaflow.agents.chat.events", "CompactionFinished"),
36
+ "CompactionStarted": ("tabulaflow.agents.chat.events", "CompactionStarted"),
37
+ "ChatSession": ("tabulaflow.agents.chat.session", "ChatSession"),
38
+ "ChatInput": ("tabulaflow.agents.chat.input", "ChatInput"),
39
+ "CompactionConfig": ("tabulaflow.agents.chat.compaction", "CompactionConfig"),
40
+ "TurnFinished": ("tabulaflow.agents.chat.events", "TurnFinished"),
41
+ "NarrationDelta": ("tabulaflow.agents.chat.events", "NarrationDelta"),
42
+ "ThinkingDelta": ("tabulaflow.agents.chat.events", "ThinkingDelta"),
43
+ "ToolCallOutcome": ("tabulaflow.agents.chat.events", "ToolCallOutcome"),
44
+ "ToolFinished": ("tabulaflow.agents.chat.events", "ToolFinished"),
45
+ "ToolProgress": ("tabulaflow.agents.chat.events", "ToolProgress"),
46
+ "ToolStarted": ("tabulaflow.agents.chat.events", "ToolStarted"),
47
+ "UsageUpdated": ("tabulaflow.agents.chat.events", "UsageUpdated"),
48
+ }
49
+
50
+ __all__ = [
51
+ "AnswerDelta",
52
+ "ChatEvent",
53
+ "ChatResult",
54
+ "CompactionFinished",
55
+ "CompactionStarted",
56
+ "ChatSession",
57
+ "ChatInput",
58
+ "CompactionConfig",
59
+ "TurnFinished",
60
+ "NarrationDelta",
61
+ "ThinkingDelta",
62
+ "ToolCallOutcome",
63
+ "ToolFinished",
64
+ "ToolProgress",
65
+ "ToolStarted",
66
+ "UsageUpdated",
67
+ ]
68
+
69
+
70
+ def __getattr__(name: str) -> Any:
71
+ if name not in _LAZY_EXPORTS:
72
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
73
+ module_name, attr_name = _LAZY_EXPORTS[name]
74
+ value = getattr(import_module(module_name), attr_name)
75
+ globals()[name] = value
76
+ return value
77
+
78
+
79
+ def __dir__() -> list[str]:
80
+ return sorted({*globals(), *_LAZY_EXPORTS})
@@ -0,0 +1,323 @@
1
+ """Simple, provider-agnostic checkpoint compaction for interactive chat sessions.
2
+
3
+ Rather than combining multiple trimming strategies or sending a flattened transcript
4
+ to a separate summarizer, the current agent writes one checkpoint from its native
5
+ conversation. This preserves recent tool context, keeps provider-carried reasoning
6
+ state available, reuses the existing prompt prefix for the checkpoint request, and
7
+ avoids a second model configuration.
8
+
9
+ The remaining rewrite is deterministic: retain only turns after the previous
10
+ checkpoint, replace tool-result payloads, append the new checkpoint, then progressively
11
+ remove execution details and assistant dialogue before evicting oldest user prompts.
12
+ Tool structure is removed as a whole, preserving provider-valid pairing while keeping
13
+ the user's recent requests for as long as the budget permits.
14
+
15
+ The checkpoint and latest user request are never silently truncated. If those alone
16
+ cannot fit, compaction fails explicitly.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import math
23
+ from dataclasses import dataclass, replace
24
+ from typing import Any, cast
25
+
26
+ import genai_prices
27
+ from pydantic_ai.messages import (
28
+ BinaryContent,
29
+ ModelMessage,
30
+ ModelRequest,
31
+ ModelResponse,
32
+ TextPart,
33
+ ToolCallPart,
34
+ ToolReturnPart,
35
+ UserPromptPart,
36
+ )
37
+
38
+ from tabulaflow.agents.chat.input import ChatInput, describe_chat_input
39
+
40
+
41
+ _CHARS_PER_TOKEN = 4
42
+ _MESSAGE_OVERHEAD_CHARS = 16
43
+ _PART_OVERHEAD_CHARS = 8
44
+ _CONTEXT_RESERVE_TOKENS = 32_000
45
+ _CHECKPOINT_TOKEN_LIMIT = 4_000
46
+ _TOOL_RESULT_PLACEHOLDER = "[tool result omitted after execution during context compaction]"
47
+ # Marks synthetic checkpoint messages so a later compaction replaces, rather than
48
+ # summarizes and retains, the previous checkpoint exchange.
49
+ _CHECKPOINT_METADATA = "tabulaflow.context-checkpoint"
50
+ # Host notifications use UserPromptPart for model visibility, but are not user turns.
51
+ HOST_EVENT_METADATA_KEY = "tabulaflow.host-event"
52
+ _CHECKPOINT_PROMPT = """
53
+ Create a compact, self-contained checkpoint that will replace the earlier conversation.
54
+
55
+ <requirements>
56
+ - Preserve goals, constraints, decisions, important exact details, current state, and next steps.
57
+ - Preserve durable information not recoverable from the local repository, such as user preferences, external paths, and external state.
58
+ - Preserve recurring workflows, operating conventions, and preferred procedures established in the conversation.
59
+ - Record work already performed, its outcomes, and enough workflow or provenance to avoid repeating or contradicting it.
60
+ - Omit filler, superseded information, and low-level execution traces.
61
+ - Do not use tools unless needed to retrieve referenced content, and do not change external state.
62
+ - Write at most approximately {checkpoint_tokens:,} tokens.
63
+ - Return only the checkpoint.
64
+ </requirements>
65
+ """.strip()
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class CompactionConfig:
70
+ """User-facing controls for automatic conversation compaction.
71
+
72
+ ``trigger_tokens`` starts checkpointing and ``target_tokens`` bounds the rewritten
73
+ history.
74
+ """
75
+
76
+ trigger_tokens: int = 240_000
77
+ target_tokens: int = 32_000
78
+
79
+ def __post_init__(self) -> None:
80
+ if not 0 < self.target_tokens < self.trigger_tokens:
81
+ raise ValueError("expected 0 < target_tokens < trigger_tokens")
82
+
83
+
84
+ def checkpoint_prompt(config: CompactionConfig) -> str:
85
+ """Return the internal request, deriving its size from the context target."""
86
+ return _CHECKPOINT_PROMPT.format(checkpoint_tokens=min(_CHECKPOINT_TOKEN_LIMIT, config.target_tokens // 4)).strip()
87
+
88
+
89
+ def effective_compaction_config(config: CompactionConfig, model: str) -> CompactionConfig:
90
+ """Scale the compaction policy to fit the model context window.
91
+
92
+ Unknown models use the configured policy. Known smaller models preserve its
93
+ trigger-to-target ratio while leaving room to generate the checkpoint.
94
+ """
95
+ context_window = _context_window(model)
96
+ if context_window is None:
97
+ return config
98
+ reserve = min(_CONTEXT_RESERVE_TOKENS, context_window // 4)
99
+ trigger = min(config.trigger_tokens, context_window - reserve)
100
+ if trigger == config.trigger_tokens:
101
+ return config
102
+ target = config.target_tokens * trigger // config.trigger_tokens
103
+ if target <= 0:
104
+ raise ValueError(f"model context window is too small for context compaction: {context_window}")
105
+ return CompactionConfig(trigger_tokens=trigger, target_tokens=target)
106
+
107
+
108
+ def estimate_context_tokens(messages: list[ModelMessage], additional_content: ChatInput = "") -> int:
109
+ """Estimate a prospective request from its latest provider-usage anchor.
110
+
111
+ The latest uncompacted response's provider-reported input plus output usage is
112
+ ground truth for history through that response. Only later messages and pending
113
+ content use the four-characters-per-token estimate. A checkpoint is a rewrite
114
+ boundary, so estimates after one start from the compacted messages instead of an
115
+ obsolete pre-compaction usage anchor.
116
+ """
117
+ additional = _estimate_text_tokens(describe_chat_input(additional_content))
118
+ for index in range(len(messages) - 1, -1, -1):
119
+ message = messages[index]
120
+ if not isinstance(message, ModelResponse):
121
+ continue
122
+ if message.metadata and message.metadata.get(_CHECKPOINT_METADATA):
123
+ break
124
+ anchored = message.usage.input_tokens + message.usage.output_tokens
125
+ if anchored:
126
+ return anchored + _estimate_messages_tokens(messages[index + 1 :]) + additional
127
+ return _estimate_messages_tokens(messages) + additional
128
+
129
+
130
+ def compact_history(
131
+ messages: list[ModelMessage],
132
+ *,
133
+ checkpoint_request: str,
134
+ checkpoint_text: str,
135
+ config: CompactionConfig,
136
+ ) -> list[ModelMessage]:
137
+ """Return provider-valid history bounded by ``config.target_tokens``.
138
+
139
+ The previous checkpoint supersedes every raw turn before it. Newer turns initially
140
+ retain their native messages with tool-result payloads replaced. If the result is
141
+ too large, turns degrade oldest-first to dialogue-only and then user-only before
142
+ oldest turns are evicted.
143
+ """
144
+ turns = _group_turns_since_checkpoint(messages)
145
+ compacted = [_project_execution(turn) for turn in turns]
146
+ compacted = [turn for turn in compacted if turn]
147
+
148
+ checkpoint_exchange: list[ModelMessage] = [
149
+ ModelRequest(
150
+ parts=[UserPromptPart(content=checkpoint_request)],
151
+ metadata={_CHECKPOINT_METADATA: True},
152
+ ),
153
+ ModelResponse(
154
+ parts=[TextPart(content=checkpoint_text)],
155
+ metadata={_CHECKPOINT_METADATA: True},
156
+ ),
157
+ ]
158
+
159
+ for index in range(len(compacted)):
160
+ if _estimate_turns(compacted, checkpoint_exchange) <= config.target_tokens:
161
+ break
162
+ compacted[index] = _project_dialogue(turns[index])
163
+
164
+ for index in range(len(compacted)):
165
+ if _estimate_turns(compacted, checkpoint_exchange) <= config.target_tokens:
166
+ break
167
+ compacted[index] = _project_user(turns[index])
168
+
169
+ while len(compacted) > 1 and _estimate_turns(compacted, checkpoint_exchange) > config.target_tokens:
170
+ compacted.pop(0)
171
+
172
+ rewritten = [message for turn in compacted for message in turn] + checkpoint_exchange
173
+ if _estimate_messages_tokens(rewritten) > config.target_tokens:
174
+ raise ValueError("checkpoint and latest user message exceed the compaction target")
175
+ return rewritten
176
+
177
+
178
+ def _group_turns_since_checkpoint(messages: list[ModelMessage]) -> list[list[ModelMessage]]:
179
+ """Group real user turns completed after the previous checkpoint.
180
+
181
+ The new checkpoint summarizes the whole supplied context, so the previous checkpoint
182
+ and every raw turn it already covered are superseded. Host notifications do not open
183
+ turns.
184
+ """
185
+ start = max(
186
+ (
187
+ index + 1
188
+ for index, message in enumerate(messages)
189
+ if message.metadata and message.metadata.get(_CHECKPOINT_METADATA)
190
+ ),
191
+ default=0,
192
+ )
193
+ turns: list[list[ModelMessage]] = []
194
+ for message in messages[start:]:
195
+ if _is_user_request(message):
196
+ turns.append([message])
197
+ elif turns:
198
+ turns[-1].append(message)
199
+ return turns
200
+
201
+
202
+ def _is_user_request(message: ModelMessage) -> bool:
203
+ """Return whether a request is an actual user turn rather than a host event."""
204
+ return (
205
+ isinstance(message, ModelRequest)
206
+ and not (message.metadata and message.metadata.get(HOST_EVENT_METADATA_KEY))
207
+ and any(isinstance(part, UserPromptPart) for part in message.parts)
208
+ )
209
+
210
+
211
+ def _project_execution(turn: list[ModelMessage]) -> list[ModelMessage]:
212
+ """Keep a turn's execution structure while replacing tool-result payloads."""
213
+ compacted: list[ModelMessage] = []
214
+ for message in turn:
215
+ if not isinstance(message, ModelRequest):
216
+ compacted.append(message)
217
+ continue
218
+ parts = [
219
+ replace(part, content=_tool_result_placeholder(part)) if isinstance(part, ToolReturnPart) else part
220
+ for part in message.parts
221
+ ]
222
+ compacted.append(replace(message, parts=parts))
223
+ return compacted
224
+
225
+
226
+ def _project_dialogue(turn: list[ModelMessage]) -> list[ModelMessage]:
227
+ """Project a turn to user prompts and final assistant text only.
228
+
229
+ Responses containing tool calls are execution steps, not final answers. Rebuilt
230
+ text responses intentionally drop provider IDs, signatures, and usage that no
231
+ longer describe the projected history.
232
+ """
233
+ dialogue: list[ModelMessage] = []
234
+ for message in turn:
235
+ if isinstance(message, ModelRequest):
236
+ if message.metadata and message.metadata.get(HOST_EVENT_METADATA_KEY):
237
+ continue
238
+ parts = [part for part in message.parts if isinstance(part, UserPromptPart)]
239
+ if parts:
240
+ dialogue.append(replace(message, parts=parts))
241
+ continue
242
+ if any(isinstance(part, ToolCallPart) for part in message.parts):
243
+ continue
244
+ text = "".join(part.content for part in message.parts if isinstance(part, TextPart))
245
+ if text:
246
+ dialogue.append(_text_response(message, text))
247
+ return dialogue
248
+
249
+
250
+ def _project_user(turn: list[ModelMessage]) -> list[ModelMessage]:
251
+ """Project a turn to its user request."""
252
+ for message in turn:
253
+ if isinstance(message, ModelRequest) and _is_user_request(message):
254
+ parts = [part for part in message.parts if isinstance(part, UserPromptPart)]
255
+ return [replace(message, parts=parts)]
256
+ return []
257
+
258
+
259
+ def _tool_result_placeholder(part: ToolReturnPart) -> str:
260
+ """Return an omission marker, retaining a stored-message pointer when present."""
261
+ if isinstance(part.metadata, dict) and (message_id := part.metadata.get("message_id")):
262
+ return f"{_TOOL_RESULT_PLACEHOLDER}; full result: {message_id}"
263
+ return _TOOL_RESULT_PLACEHOLDER
264
+
265
+
266
+ def _text_response(message: ModelResponse, content: str) -> ModelResponse:
267
+ """Build a clean text-only projection of an assistant response."""
268
+ return ModelResponse(
269
+ parts=[TextPart(content=content)],
270
+ model_name=message.model_name,
271
+ timestamp=message.timestamp,
272
+ run_id=message.run_id,
273
+ conversation_id=message.conversation_id,
274
+ )
275
+
276
+
277
+ def _estimate_turns(turns: list[list[ModelMessage]], checkpoint: list[ModelMessage]) -> int:
278
+ """Estimate flattened turns together with their protected checkpoint."""
279
+ return _estimate_messages_tokens([message for turn in turns for message in turn] + checkpoint)
280
+
281
+
282
+ def _estimate_messages_tokens(messages: list[ModelMessage]) -> int:
283
+ """Estimate model-visible content without counting internal bookkeeping."""
284
+ chars = 0
285
+ for message in messages:
286
+ chars += _MESSAGE_OVERHEAD_CHARS
287
+ if isinstance(message, ModelRequest) and message.instructions:
288
+ chars += len(message.instructions)
289
+ for part in message.parts:
290
+ chars += _PART_OVERHEAD_CHARS
291
+ if isinstance(part, UserPromptPart):
292
+ chars += len(describe_chat_input(cast(ChatInput, part.content)))
293
+ continue
294
+ for field in ("tool_name", "args", "content", "transcript", "signature", "tools_added"):
295
+ chars += _estimate_value_chars(getattr(part, field, None))
296
+ return math.ceil(chars / _CHARS_PER_TOKEN)
297
+
298
+
299
+ def _estimate_value_chars(value: Any) -> int:
300
+ if value is None:
301
+ return 0
302
+ if isinstance(value, str):
303
+ return len(value)
304
+ if isinstance(value, BinaryContent):
305
+ return len(describe_chat_input([value]))
306
+ return len(json.dumps(value, ensure_ascii=False, separators=(",", ":"), default=str))
307
+
308
+
309
+ def _estimate_text_tokens(text: str) -> int:
310
+ return math.ceil(len(text) / _CHARS_PER_TOKEN)
311
+
312
+
313
+ def _context_window(model: str) -> int | None:
314
+ """Resolve the model context window from genai-prices when known."""
315
+ model_ref = model.partition(":")[2] or model
316
+ try:
317
+ calculation = genai_prices.calc_price(
318
+ genai_prices.Usage(input_tokens=0, output_tokens=0),
319
+ model_ref,
320
+ )
321
+ except LookupError:
322
+ return None
323
+ return calculation.model.context_window
@@ -0,0 +1,161 @@
1
+ """The chat ⇄ frontend event contract.
2
+
3
+ ``ChatSession.run_stream()`` yields a stream of these events; any frontend (the TUI, a
4
+ browser client, a CLI logger, a test harness) consumes the stream and decides how
5
+ to render each one. Events are **semantic** — they carry the data of what the
6
+ agent did, never pre-rendered presentation — so a frontend renders / words /
7
+ truncates however it wants. Rendering is deliberately the frontend's job; this
8
+ module ships no summarizers.
9
+
10
+ They're pydantic models forming a **discriminated union** on ``kind`` (consistent
11
+ with the other serialized schema models), which gives a frontend free, robust, two-way
12
+ wire (de)serialization:
13
+
14
+ raw = event.model_dump_json() # produce (server)
15
+ event = TypeAdapter(ChatEvent).validate_json(raw) # consume (client)
16
+
17
+ The stream ends with exactly one ``TurnFinished`` (carrying the result) on normal
18
+ completion. Failures propagate as exceptions; an interrupted run raises
19
+ ``CancelledError`` and the agent's message history / usage reflect the partial run.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Annotated, Any, Literal, TypeAlias, Union
25
+
26
+ from pydantic import BaseModel, ConfigDict, Field
27
+
28
+ from tabulaflow.output.specs import OutputSpec
29
+ from tabulaflow.agents.trace import Usage
30
+ from tabulaflow.agents.tools.protocols import ToolCallOutcome as ToolCallOutcome
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Streaming events (emitted during a turn)
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ class _ChatEvent(BaseModel):
39
+ """Base for all chat events (immutable)."""
40
+
41
+ model_config = ConfigDict(frozen=True)
42
+
43
+
44
+ class AnswerDelta(_ChatEvent):
45
+ """A chunk of the assistant's streaming final answer (the user-facing reply)."""
46
+
47
+ kind: Literal["answer_delta"] = "answer_delta"
48
+ content: str
49
+
50
+
51
+ class NarrationDelta(_ChatEvent):
52
+ """A chunk of mid-turn narration — text the model emits while working, before its
53
+ final answer. Distinct from ``AnswerDelta`` so a frontend can drop or dim it
54
+ separately (the TUI drops it; a webapp might show it greyed)."""
55
+
56
+ kind: Literal["narration_delta"] = "narration_delta"
57
+ content: str
58
+
59
+
60
+ class ThinkingDelta(_ChatEvent):
61
+ """A chunk of the model's reasoning summary (reasoning models only). Distinct
62
+ from ``AnswerDelta`` so a frontend can show / collapse it separately from the answer."""
63
+
64
+ kind: Literal["thinking_delta"] = "thinking_delta"
65
+ content: str
66
+
67
+
68
+ class ToolStarted(_ChatEvent):
69
+ """The agent invoked a tool. ``args`` is the raw tool-call arguments (lossless,
70
+ so a frontend can show the full query / spec, or render its own compact line)."""
71
+
72
+ kind: Literal["tool_started"] = "tool_started"
73
+ tool_call_id: str
74
+ name: str
75
+ args: dict[str, Any]
76
+
77
+
78
+ class ToolFinished(_ChatEvent):
79
+ """A tool call returned. ``outcome`` is structured so a frontend can reword it;
80
+ ``None`` means plain completion with no suffix-worthy fact. The full result
81
+ (if any) arrives later in ``TurnFinished.result``."""
82
+
83
+ kind: Literal["tool_finished"] = "tool_finished"
84
+ tool_call_id: str
85
+ name: str
86
+ outcome: ToolCallOutcome | None = None
87
+
88
+
89
+ class ToolProgress(_ChatEvent):
90
+ """Progress within a long-running / fan-out tool (e.g. per-row subagents).
91
+
92
+ ``tool_call_id`` identifies which in-flight tool when several run at once;
93
+ ``None`` means "the current fan-out" for simple single-tool cases.
94
+ """
95
+
96
+ kind: Literal["tool_progress"] = "tool_progress"
97
+ completed: int
98
+ # ``None`` total means an open-ended running count with no known denominator
99
+ # (e.g. entities extracted so far) — rendered as a bare count, not a fraction.
100
+ total: int | None
101
+ # Optional noun for the open-ended count, e.g. ``"rows"`` -> ``"47 rows"``.
102
+ unit: str | None = None
103
+ stage: str | None = None
104
+ tool_call_id: str | None = None
105
+
106
+
107
+ class UsageUpdated(_ChatEvent):
108
+ """Cumulative token/cost usage so far this turn (for a live cost readout)."""
109
+
110
+ kind: Literal["usage_updated"] = "usage_updated"
111
+ usage: Usage
112
+
113
+
114
+ class CompactionStarted(_ChatEvent):
115
+ """The session started compacting context before the pending user turn."""
116
+
117
+ kind: Literal["compaction_started"] = "compaction_started"
118
+
119
+
120
+ class CompactionFinished(_ChatEvent):
121
+ """Context compaction ended and normal processing is resuming."""
122
+
123
+ kind: Literal["compaction_finished"] = "compaction_finished"
124
+
125
+
126
+ # ---------------------------------------------------------------------------
127
+ # Terminal event (ends the stream on normal completion)
128
+ # ---------------------------------------------------------------------------
129
+
130
+
131
+ class ChatResult(BaseModel):
132
+ """Logical result of one chat turn."""
133
+
134
+ text: str
135
+ output: OutputSpec = Field(default_factory=OutputSpec)
136
+ usage: Usage | None = None
137
+
138
+
139
+ class TurnFinished(_ChatEvent):
140
+ """The turn completed normally; carries the full result. The only terminal
141
+ event — failures and interrupts surface as exceptions on the iterator, not here."""
142
+
143
+ kind: Literal["turn_finished"] = "turn_finished"
144
+ result: ChatResult
145
+
146
+
147
+ ChatEvent: TypeAlias = Annotated[
148
+ Union[
149
+ AnswerDelta,
150
+ NarrationDelta,
151
+ ThinkingDelta,
152
+ ToolStarted,
153
+ ToolFinished,
154
+ ToolProgress,
155
+ UsageUpdated,
156
+ CompactionStarted,
157
+ CompactionFinished,
158
+ TurnFinished,
159
+ ],
160
+ Field(discriminator="kind"),
161
+ ]