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.
- tabulaflow/__init__.py +3 -0
- tabulaflow/_paths.py +5 -0
- tabulaflow/agents/__init__.py +42 -0
- tabulaflow/agents/_cache.py +73 -0
- tabulaflow/agents/chat/__init__.py +80 -0
- tabulaflow/agents/chat/compaction.py +323 -0
- tabulaflow/agents/chat/events.py +161 -0
- tabulaflow/agents/chat/input.py +30 -0
- tabulaflow/agents/chat/session.py +926 -0
- tabulaflow/agents/chat/system_prompt.md +254 -0
- tabulaflow/agents/chat/tools.py +57 -0
- tabulaflow/agents/chat/turn.py +325 -0
- tabulaflow/agents/config.py +48 -0
- tabulaflow/agents/enrichment.py +411 -0
- tabulaflow/agents/extraction/__init__.py +26 -0
- tabulaflow/agents/extraction/extractor.py +237 -0
- tabulaflow/agents/extraction/markdown.py +439 -0
- tabulaflow/agents/llm.py +326 -0
- tabulaflow/agents/media.py +258 -0
- tabulaflow/agents/message_store.py +254 -0
- tabulaflow/agents/runtime.py +146 -0
- tabulaflow/agents/summarization.py +157 -0
- tabulaflow/agents/tools/__init__.py +151 -0
- tabulaflow/agents/tools/_sql.py +158 -0
- tabulaflow/agents/tools/add_canonical_name.py +915 -0
- tabulaflow/agents/tools/browser/__init__.py +1 -0
- tabulaflow/agents/tools/browser/aria.py +1312 -0
- tabulaflow/agents/tools/browser/manager.py +154 -0
- tabulaflow/agents/tools/browser/tool.py +1438 -0
- tabulaflow/agents/tools/connect_data_source.py +102 -0
- tabulaflow/agents/tools/create_parameterized_source.py +375 -0
- tabulaflow/agents/tools/extract_rows_from_documents.py +387 -0
- tabulaflow/agents/tools/filesystem/__init__.py +1 -0
- tabulaflow/agents/tools/filesystem/access.py +95 -0
- tabulaflow/agents/tools/filesystem/edit.py +173 -0
- tabulaflow/agents/tools/filesystem/patch.py +174 -0
- tabulaflow/agents/tools/filesystem/patch_engine.py +526 -0
- tabulaflow/agents/tools/filesystem/view.py +297 -0
- tabulaflow/agents/tools/get_column_json_schema.py +253 -0
- tabulaflow/agents/tools/get_table_schema.py +204 -0
- tabulaflow/agents/tools/protocols.py +120 -0
- tabulaflow/agents/tools/registry/__init__.py +1 -0
- tabulaflow/agents/tools/registry/get_column_json_schema.py +104 -0
- tabulaflow/agents/tools/registry/get_data_source_document.py +176 -0
- tabulaflow/agents/tools/registry/get_schema.py +118 -0
- tabulaflow/agents/tools/registry/get_table_schema.py +125 -0
- tabulaflow/agents/tools/registry/run_query.py +174 -0
- tabulaflow/agents/tools/registry/write_result_table.py +112 -0
- tabulaflow/agents/tools/render_chart.py +402 -0
- tabulaflow/agents/tools/render_graph.py +137 -0
- tabulaflow/agents/tools/render_map.py +145 -0
- tabulaflow/agents/tools/run_query.py +406 -0
- tabulaflow/agents/tools/run_subagent_for_each_row.py +583 -0
- tabulaflow/agents/tools/shell/__init__.py +1 -0
- tabulaflow/agents/tools/shell/guard.py +61 -0
- tabulaflow/agents/tools/shell/tool.py +442 -0
- tabulaflow/agents/tools/show_artifacts.py +88 -0
- tabulaflow/agents/trace.py +321 -0
- tabulaflow/app/__init__.py +0 -0
- tabulaflow/app/assets/__init__.py +0 -0
- tabulaflow/app/assets/samples/__init__.py +0 -0
- tabulaflow/app/assets/samples/sample.sqlite +0 -0
- tabulaflow/app/config.py +331 -0
- tabulaflow/app/main.py +80 -0
- tabulaflow/app/pane/__init__.py +5 -0
- tabulaflow/app/pane/assets/__init__.py +1 -0
- tabulaflow/app/pane/assets/fonts/figtree/Figtree-Variable.woff2 +0 -0
- tabulaflow/app/pane/assets/fonts/figtree/OFL.txt +93 -0
- tabulaflow/app/pane/assets/ui/__init__.py +1 -0
- tabulaflow/app/pane/assets/ui/contract.d.ts +180 -0
- tabulaflow/app/pane/assets/ui/index.html +40 -0
- tabulaflow/app/pane/assets/ui/pane.css +911 -0
- tabulaflow/app/pane/assets/ui/pane.js +1567 -0
- tabulaflow/app/pane/assets/ui/render/chart.js +201 -0
- tabulaflow/app/pane/assets/ui/render/code.js +35 -0
- tabulaflow/app/pane/assets/ui/render/color-domains.js +27 -0
- tabulaflow/app/pane/assets/ui/render/graph.js +756 -0
- tabulaflow/app/pane/assets/ui/render/map.js +1100 -0
- tabulaflow/app/pane/assets/ui/render/markdown.js +95 -0
- tabulaflow/app/pane/assets/ui/render/query.js +15 -0
- tabulaflow/app/pane/assets/ui/render/shared.js +258 -0
- tabulaflow/app/pane/assets/ui/render/table.js +411 -0
- tabulaflow/app/pane/assets/vendor/cytoscape/LICENSE-cytoscape-dagre.txt +21 -0
- tabulaflow/app/pane/assets/vendor/cytoscape/LICENSE-cytoscape.txt +19 -0
- tabulaflow/app/pane/assets/vendor/cytoscape/LICENSE-dagre.txt +19 -0
- tabulaflow/app/pane/assets/vendor/cytoscape/cytoscape-dagre.min.js +397 -0
- tabulaflow/app/pane/assets/vendor/cytoscape/cytoscape.min.js +32 -0
- tabulaflow/app/pane/assets/vendor/cytoscape/dagre.min.js +3809 -0
- tabulaflow/app/pane/assets/vendor/katex/LICENSE.txt +21 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- tabulaflow/app/pane/assets/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- tabulaflow/app/pane/assets/vendor/katex/katex.min.css +1 -0
- tabulaflow/app/pane/assets/vendor/katex/katex.min.js +1 -0
- tabulaflow/app/pane/assets/vendor/maplibre/LICENSE.txt +116 -0
- tabulaflow/app/pane/assets/vendor/maplibre/__init__.py +1 -0
- tabulaflow/app/pane/assets/vendor/maplibre/airport-labels.geojson +1 -0
- tabulaflow/app/pane/assets/vendor/maplibre/continent-labels.geojson +12 -0
- tabulaflow/app/pane/assets/vendor/maplibre/maplibre-gl.css +1 -0
- tabulaflow/app/pane/assets/vendor/maplibre/maplibre-gl.js +59 -0
- tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-admin0-boundaries.geojson +1 -0
- tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-admin1-boundaries.geojson +1 -0
- tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-airports-source.txt +8 -0
- tabulaflow/app/pane/assets/vendor/maplibre/natural-earth-boundaries-source.txt +10 -0
- tabulaflow/app/pane/assets/vendor/maplibre/ocean-labels.geojson +10 -0
- tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite-source.txt +26 -0
- tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite.json +709 -0
- tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite.png +0 -0
- tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite@2x.json +709 -0
- tabulaflow/app/pane/assets/vendor/maplibre/osm-bright-sprite@2x.png +0 -0
- tabulaflow/app/pane/assets/vendor/maplibre/shortbread-light.json +2535 -0
- tabulaflow/app/pane/assets/vendor/maplibre/tf-airport-icon-draft.svg +36 -0
- tabulaflow/app/pane/assets/vendor/maplibre/tf-interstate-shield-draft.svg +88 -0
- tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite-source.txt +20 -0
- tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite.json +30 -0
- tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite.png +0 -0
- tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite@2x.json +30 -0
- tabulaflow/app/pane/assets/vendor/maplibre/tf-route-sprite@2x.png +0 -0
- tabulaflow/app/pane/assets/vendor/markdown-it/LICENSE.txt +22 -0
- tabulaflow/app/pane/assets/vendor/markdown-it/markdown-it.min.js +3 -0
- tabulaflow/app/pane/assets/vendor/markdown-it-texmath/LICENSE.txt +21 -0
- tabulaflow/app/pane/assets/vendor/markdown-it-texmath/texmath.css +19 -0
- tabulaflow/app/pane/assets/vendor/markdown-it-texmath/texmath.js +368 -0
- tabulaflow/app/pane/assets/vendor/tabulator/LICENSE.txt +21 -0
- tabulaflow/app/pane/assets/vendor/tabulator/__init__.py +0 -0
- tabulaflow/app/pane/assets/vendor/tabulator/tabulator.min.css +2 -0
- tabulaflow/app/pane/assets/vendor/tabulator/tabulator.min.js +3 -0
- tabulaflow/app/pane/assets/vendor/vega/LICENSE-vega-embed.txt +27 -0
- tabulaflow/app/pane/assets/vendor/vega/LICENSE-vega-lite.txt +27 -0
- tabulaflow/app/pane/assets/vendor/vega/LICENSE-vega.txt +27 -0
- tabulaflow/app/pane/assets/vendor/vega/__init__.py +0 -0
- tabulaflow/app/pane/assets/vendor/vega/vega-embed.min.js +7 -0
- tabulaflow/app/pane/assets/vendor/vega/vega-lite.min.js +2 -0
- tabulaflow/app/pane/assets/vendor/vega/vega.min.js +2 -0
- tabulaflow/app/pane/cards.py +301 -0
- tabulaflow/app/pane/charts.py +179 -0
- tabulaflow/app/pane/contract.py +237 -0
- tabulaflow/app/pane/graphs.py +91 -0
- tabulaflow/app/pane/maps.py +215 -0
- tabulaflow/app/pane/server.py +661 -0
- tabulaflow/app/pane/tables.py +293 -0
- tabulaflow/app/runtime_paths.py +109 -0
- tabulaflow/app/sample_data.py +104 -0
- tabulaflow/app/session.py +289 -0
- tabulaflow/app/theme.py +87 -0
- tabulaflow/app/tui/__init__.py +5 -0
- tabulaflow/app/tui/app.py +1362 -0
- tabulaflow/app/tui/banner.py +185 -0
- tabulaflow/app/tui/cells.py +60 -0
- tabulaflow/app/tui/clipboard.py +30 -0
- tabulaflow/app/tui/commands.py +367 -0
- tabulaflow/app/tui/rendering.py +447 -0
- tabulaflow/app/tui/screens/__init__.py +1 -0
- tabulaflow/app/tui/screens/config.py +346 -0
- tabulaflow/app/tui/screens/results.py +827 -0
- tabulaflow/app/tui/screens/schema.py +839 -0
- tabulaflow/app/tui/spinner.py +15 -0
- tabulaflow/app/tui/theme.py +215 -0
- tabulaflow/app/tui/tui.tcss +140 -0
- tabulaflow/app/tui/widgets/__init__.py +1 -0
- tabulaflow/app/tui/widgets/chat.py +120 -0
- tabulaflow/app/tui/widgets/chat_log.py +27 -0
- tabulaflow/app/tui/widgets/choice.py +176 -0
- tabulaflow/app/tui/widgets/input.py +596 -0
- tabulaflow/app/tui/widgets/markdown.py +372 -0
- tabulaflow/app/tui/widgets/progress.py +760 -0
- tabulaflow/app/tui/widgets/result.py +908 -0
- tabulaflow/app/tui/widgets/suggestions.py +143 -0
- tabulaflow/app/turn.py +22 -0
- tabulaflow/cli.py +95 -0
- tabulaflow/core/__init__.py +111 -0
- tabulaflow/core/_cache.py +65 -0
- tabulaflow/core/dataframe.py +487 -0
- tabulaflow/core/media.py +156 -0
- tabulaflow/core/registry.py +34 -0
- tabulaflow/core/results.py +78 -0
- tabulaflow/core/schema.py +318 -0
- tabulaflow/core/serialization.py +44 -0
- tabulaflow/data/__init__.py +70 -0
- tabulaflow/data/_cache.py +66 -0
- tabulaflow/data/catalog.py +72 -0
- tabulaflow/data/config.py +109 -0
- tabulaflow/data/connect.py +420 -0
- tabulaflow/data/json_schema.py +160 -0
- tabulaflow/data/loaders/__init__.py +53 -0
- tabulaflow/data/loaders/_runner.py +75 -0
- tabulaflow/data/loaders/files.py +254 -0
- tabulaflow/data/loaders/huggingface.py +781 -0
- tabulaflow/data/neo4j.py +647 -0
- tabulaflow/data/protocols.py +87 -0
- tabulaflow/data/registry.py +104 -0
- tabulaflow/data/sparql.py +452 -0
- tabulaflow/data/sql.py +2896 -0
- tabulaflow/examples/__init__.py +1 -0
- tabulaflow/examples/ambiguity_aware_queries.py +65 -0
- tabulaflow/examples/chat_sessions.py +75 -0
- tabulaflow/examples/cli.py +58 -0
- tabulaflow/examples/compare_research_agents.py +64 -0
- tabulaflow/examples/custom_agents.py +80 -0
- tabulaflow/examples/data_enrichment.py +62 -0
- tabulaflow/examples/document_extraction.py +41 -0
- tabulaflow/examples/quick_start.py +70 -0
- tabulaflow/examples/research_quick_start.py +46 -0
- tabulaflow/examples/structured_outputs.py +116 -0
- tabulaflow/examples/support/__init__.py +1 -0
- tabulaflow/examples/support/dock-guide.pdf +0 -0
- tabulaflow/examples/support/faq.txt +16 -0
- tabulaflow/examples/support/travel_guide.txt +116 -0
- tabulaflow/examples/table_linking_agent.py +116 -0
- tabulaflow/examples/working_with_data.py +70 -0
- tabulaflow/output/__init__.py +14 -0
- tabulaflow/output/charts.py +167 -0
- tabulaflow/output/formatting/__init__.py +77 -0
- tabulaflow/output/formatting/_core.py +369 -0
- tabulaflow/output/formatting/_sql.py +124 -0
- tabulaflow/output/formatting/_table_grouping.py +242 -0
- tabulaflow/output/formatting/cypher.py +82 -0
- tabulaflow/output/formatting/resolution.py +60 -0
- tabulaflow/output/formatting/schema.py +54 -0
- tabulaflow/output/formatting/sparql.py +20 -0
- tabulaflow/output/formatting/sql_compact.py +191 -0
- tabulaflow/output/formatting/sql_ddl.py +288 -0
- tabulaflow/output/graphs.py +446 -0
- tabulaflow/output/maps.py +465 -0
- tabulaflow/output/resolver.py +316 -0
- tabulaflow/output/specs.py +291 -0
- tabulaflow/output/store.py +523 -0
- tabulaflow/research/__init__.py +1 -0
- tabulaflow/research/agents/__init__.py +33 -0
- tabulaflow/research/agents/ambig_flat.py +340 -0
- tabulaflow/research/agents/ambig_simple.py +123 -0
- tabulaflow/research/agents/ambig_structured.py +369 -0
- tabulaflow/research/agents/dbt.py +262 -0
- tabulaflow/research/agents/direct_prompt.py +91 -0
- tabulaflow/research/agents/ensemblers/__init__.py +11 -0
- tabulaflow/research/agents/ensemblers/agent.py +283 -0
- tabulaflow/research/agents/ensemblers/dbt.py +214 -0
- tabulaflow/research/agents/ensemblers/llm.py +211 -0
- tabulaflow/research/agents/ensemblers/majority.py +74 -0
- tabulaflow/research/agents/ensemblers/utils.py +31 -0
- tabulaflow/research/agents/full_schema.py +117 -0
- tabulaflow/research/agents/registry.py +71 -0
- tabulaflow/research/agents/schema_discovery.py +143 -0
- tabulaflow/research/agents/schema_linking.py +515 -0
- tabulaflow/research/agents/user_simulator.py +274 -0
- tabulaflow/research/agents/utils.py +119 -0
- tabulaflow/research/ambiguity.py +65 -0
- tabulaflow/research/benchmarks/__init__.py +56 -0
- tabulaflow/research/benchmarks/ambrosia_s.py +284 -0
- tabulaflow/research/benchmarks/arcs.py +246 -0
- tabulaflow/research/benchmarks/beaver.py +239 -0
- tabulaflow/research/benchmarks/bird_sql.py +357 -0
- tabulaflow/research/benchmarks/cypherbench.py +322 -0
- tabulaflow/research/benchmarks/installation.py +218 -0
- tabulaflow/research/benchmarks/registry.py +146 -0
- tabulaflow/research/benchmarks/runtime.py +110 -0
- tabulaflow/research/benchmarks/spider2_dbt.py +376 -0
- tabulaflow/research/benchmarks/spider2_lite.py +507 -0
- tabulaflow/research/benchmarks/spider2_snow.py +348 -0
- tabulaflow/research/cli.py +123 -0
- tabulaflow/research/metrics/__init__.py +59 -0
- tabulaflow/research/metrics/aggregators.py +250 -0
- tabulaflow/research/metrics/ambig_point_stats.py +398 -0
- tabulaflow/research/metrics/bird_sql_ex.py +31 -0
- tabulaflow/research/metrics/bird_sql_ex_soft.py +64 -0
- tabulaflow/research/metrics/cypherbench_ex.py +181 -0
- tabulaflow/research/metrics/executable.py +19 -0
- tabulaflow/research/metrics/found_one.py +65 -0
- tabulaflow/research/metrics/gold_ambig_point_stats.py +29 -0
- tabulaflow/research/metrics/gold_executable.py +16 -0
- tabulaflow/research/metrics/gold_result_not_empty.py +17 -0
- tabulaflow/research/metrics/pred_success.py +15 -0
- tabulaflow/research/metrics/psjs.py +183 -0
- tabulaflow/research/metrics/raw_pred_bird_sql_ex.py +19 -0
- tabulaflow/research/metrics/raw_pred_simple_ex.py +23 -0
- tabulaflow/research/metrics/registry.py +28 -0
- tabulaflow/research/metrics/schema_linking_stats.py +66 -0
- tabulaflow/research/metrics/simple_ex.py +136 -0
- tabulaflow/research/metrics/spider2_duckdb_match.py +106 -0
- tabulaflow/research/metrics/spider2_ex.py +123 -0
- tabulaflow/research/metrics/utils.py +84 -0
- tabulaflow/research/observability.py +96 -0
- tabulaflow/research/pipelines/__init__.py +42 -0
- tabulaflow/research/pipelines/ensemble.py +290 -0
- tabulaflow/research/pipelines/evaluate.py +149 -0
- tabulaflow/research/pipelines/execute.py +89 -0
- tabulaflow/research/pipelines/predict.py +387 -0
- tabulaflow/research/pipelines/preprocess.py +106 -0
- tabulaflow/research/pipelines/utils.py +75 -0
- tabulaflow/research/preprocessing/__init__.py +25 -0
- tabulaflow/research/preprocessing/column_profiler.py +76 -0
- tabulaflow/research/preprocessing/erd.py +322 -0
- tabulaflow/research/preprocessing/fk_predictor.py +98 -0
- tabulaflow/research/preprocessing/question_embedding.py +162 -0
- tabulaflow/research/preprocessing/registry.py +53 -0
- tabulaflow/research/preprocessing/schema.py +83 -0
- tabulaflow/research/query_analysis.py +59 -0
- tabulaflow/research/query_execution.py +71 -0
- tabulaflow/research/reporting.py +374 -0
- tabulaflow/research/tools/__init__.py +34 -0
- tabulaflow/research/tools/ask_user.py +43 -0
- tabulaflow/research/tools/finish.py +52 -0
- tabulaflow/research/tools/get_column_description.py +60 -0
- tabulaflow/research/tools/get_schema.py +45 -0
- tabulaflow/research/tools/run_dbt.py +176 -0
- tabulaflow/research/tools/search_keywords.py +90 -0
- tabulaflow/research/types.py +737 -0
- tabulaflow-0.1.0.dist-info/METADATA +496 -0
- tabulaflow-0.1.0.dist-info/RECORD +370 -0
- tabulaflow-0.1.0.dist-info/WHEEL +5 -0
- tabulaflow-0.1.0.dist-info/entry_points.txt +2 -0
- tabulaflow-0.1.0.dist-info/licenses/LICENSE +29 -0
- tabulaflow-0.1.0.dist-info/top_level.txt +1 -0
tabulaflow/__init__.py
ADDED
tabulaflow/_paths.py
ADDED
|
@@ -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
|
+
]
|