Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ SANDBOX=local # code execution backend: 'local' (default) or 'docke
# OpenAI
OPENAI_ENABLED=true
OPENAI_API_KEY=#your-openai-api-key
OPENAI_MODELS=gpt-5.4,gpt-4.1 # comma separated list of models
OPENAI_MODELS=gpt-5.5 # comma separated list of models

# Azure OpenAI
AZURE_ENABLED=true
Expand Down
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,24 @@ updates:
target-branch: "dev"
schedule:
interval: weekly

# Frontend JavaScript dependencies (package.json / yarn.lock)
- package-ecosystem: "npm"
directory: "/"
target-branch: "dev"
schedule:
interval: weekly

# Backend Python dependencies (pyproject.toml / uv.lock)
- package-ecosystem: "uv"
directory: "/"
target-branch: "dev"
schedule:
interval: weekly

# GitHub Actions workflow dependencies
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "dev"
schedule:
interval: weekly
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ https://github.com/user-attachments/assets/8e4f8a08-6423-4227-a1f7-559e0126ce31

## News 🔥🔥🔥

[07-13-2026] **Data Formulator 0.8 alpha 1** — a preview of a more connected, agent-driven data workflow:
[07-17-2026] **Data Formulator 0.8 alpha 2** — A more connected, conversational way to work with data:

- **Smarter agent handoffs.** Analysis can delegate directly to data loading while preserving the conversation and request context.
- **A redesigned connector experience.** Progressive setup, explicit authentication paths, database discovery, clearer scope controls, and improved credential handling make data sources easier to configure safely.
- **Better loading plans.** Recommended selections, compact previews, readable filters, provenance, and more reliable history and scrolling improve review before import.
- **Stronger enterprise foundations.** Unified connector metadata, session-scoped knowledge and distillation improvements, model routing, and additional isolation guardrails prepare Data Formulator for larger deployments.
- **Explore large datasets through conversation.** Connect a database, then ask the agent to find the right tables, filter the data, or update your selection as your question evolves. You can review the resulting filters, previews, and data sources before anything is loaded.
- **Keep the whole analysis in one conversation.** Agents can load data without losing track of what you asked. Questions, explanations, and results stay together in the Data Thread, so you can pick up from any step or branch into a new question, column, or chart.
- **Choose a chart that fits the question.** The gallery now includes bullet, connected scatter, ECDF, Gantt, range area, slope, sparkline, and violin charts, along with better recommendations. Files you attach also stay available to the analyst as the exploration continues.
- **Spend less time troubleshooting.** This release improves long-running sessions, model routing, data isolation, installation across platforms, dependency security, and MySQL data freshness. Persistent logs and an in-app log viewer make problems easier to track down.

> Preview with `pip install --pre data_formulator==0.8.0a1` or `uvx --from data_formulator==0.8.0a1 data_formulator`.
> Preview with `pip install --pre data_formulator==0.8.0a2` or `uvx --from data_formulator==0.8.0a2 data_formulator`.

> Install the latest stable release (0.7) with `pip install data_formulator` or run instantly with `uvx data_formulator`.

Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"resolutions": {
"lodash": "^4.18.1",
"vite": "^7.3.3",
"dompurify": "^3.4.2"
"dompurify": "^3.4.2",
"markdown-it": "^14.3.0",
"linkify-it": "^5.0.2",
"undici": "^7.28.0"
},
"dependencies": {
"@azure/msal-browser": "^5.6.3",
Expand Down Expand Up @@ -60,13 +63,14 @@
"react-katex": "^3.1.0",
"react-markdown": "^10.1.0",
"react-redux": "^8.0.4",
"react-router-dom": "^6.22.0",
"react-router-dom": "^6.30.4",
"react-selectable-fast": "^3.4.0",
"react-simple-code-editor": "^0.13.1",
"react-vega": "^7.6.0",
"react-virtuoso": "^4.3.10",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"remark-gfm": "^4",
"tiptap-markdown": "^0.9.0",
"typescript": "^4.9.5",
"validator": "^13.15.20",
Expand Down
7 changes: 6 additions & 1 deletion py-src/data_formulator/agents/agent_data_loading_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -1268,9 +1268,14 @@ def _tool_execute_python(self, args):

return response
else:
err = raw.get("error_message", raw.get("content", "Unknown error"))
logger.warning(
"execute_python code failed: %s\n--- code ---\n%s",
err, code[:2000],
)
return {
"stdout": "",
"error": raw.get("error_message", raw.get("content", "Unknown error")),
"error": err,
}

except Exception as e:
Expand Down
7 changes: 6 additions & 1 deletion py-src/data_formulator/agents/client_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def ping(self, timeout: int = 10):
params["timeout"] = timeout
litellm.completion(
model=self.model, messages=messages,
max_tokens=3, drop_params=True, **params,
max_tokens=3, drop_params=True, _skip_mcp_handler=True, **params,
)

def _dispatch(self, *, messages, stream, params, tools=None, extra=None):
Expand All @@ -359,6 +359,11 @@ def _dispatch(self, *, messages, stream, params, tools=None, extra=None):
effective_stream = stream and not is_ollama
call_kwargs = dict(model=self.model, messages=messages,
drop_params=True, stream=effective_stream,
# We never use litellm's built-in MCP gateway. Setting this
# skips litellm's proxy/MCP handler import path, which pulls
# in fastapi and is not a dependency of this project
# (litellm>=1.92 imports it whenever `tools` are passed).
_skip_mcp_handler=True,
**params, **(extra or {}))
if tools is not None:
call_kwargs["tools"] = tools
Expand Down
5 changes: 3 additions & 2 deletions py-src/data_formulator/agents/semantic_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
SEMANTIC TYPE SYSTEM (Python mirror of the TypeScript registry)
=============================================================================
The **source of truth** for semantic types lives in the TypeScript library:
src/lib/agents-chart/core/type-registry.ts
The **source of truth** for semantic types lives in the flint-chart library
(npm package `flint-chart`, repo microsoft/flint-chart):
packages/flint-js/src/core/type-registry.ts
This file mirrors the registered types and provides:
1. String constants for every type in the TS TYPE_REGISTRY
Expand Down
49 changes: 40 additions & 9 deletions py-src/data_formulator/analyst/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,14 @@ def _decode(self, args: str) -> str | None:
next message starts a fresh turn). Use it whenever you've done what was asked,
including answering a question you fully resolved.

**Whenever you expect the user to reply — a question, a clarification, an
explanation you want them to react to, or a set of choices — use the `ask_user`
action instead.** It renders a question widget and pauses the run for their
reply, so the conversation resumes in the same turn. `ask_user` accepts
free-text questions (no clickable options required), so reach for it for *any*
followup-seeking turn, not only structured choices. Plain text never asks for
input; `ask_user` always does. There is no separate "stop" or "summary" action:
you stop by simply not acting.
**Whenever you expect the user to reply — a question, a clarification, or a set
of choices — use the `ask_user` action instead.** It renders a question widget
and pauses the run for their reply, so the conversation resumes in the same
turn. `ask_user` accepts free-text questions (no clickable options required), so
reach for it for *any* followup-seeking turn, not only structured choices. Keep
your reasoning and explanations in your reply text, not inside `ask_user`. Plain
text never asks for input; `ask_user` always does. There is no separate "stop"
or "summary" action: you stop by simply not acting.

The concrete actions available to you — and how to use each well — are
described in the capability sections below.
Expand Down Expand Up @@ -371,6 +371,7 @@ def run(
primary_tables: list[str] | None = None,
attached_images: list[str] | None = None,
charts: list[dict[str, Any]] | None = None,
scratch_files: list[str] | None = None,
) -> Generator[dict[str, Any], None, None]:
"""Run the unified analyst loop.

Expand Down Expand Up @@ -434,6 +435,7 @@ def run(
primary_tables=primary_tables,
attached_images=attached_images,
charts=charts,
scratch_files=scratch_files,
)
rlog.log(
"context_built",
Expand Down Expand Up @@ -973,9 +975,14 @@ def _run_explore_code(
stdout = stdout[:8000] + "\n... (truncated)"
return {"status": "ok", "stdout": stdout}
else:
err = raw.get("error_message", raw.get("content", "Unknown error"))
logger.warning(
"[AnalystAgent] explore code failed: %s\n--- code ---\n%s",
err, code[:2000],
)
return {
"status": "error",
"error": raw.get("error_message", raw.get("content", "Unknown error")),
"error": err,
"stdout": "",
}
except Exception as e:
Expand Down Expand Up @@ -1019,6 +1026,10 @@ def _run_visualize_code(

if execution_result['status'] != 'ok':
error_message = execution_result.get('content', 'Unknown error')
logger.warning(
"[AnalystAgent] visualize code failed: %s\n--- code ---\n%s",
error_message, code[:2000],
)
return {"status": "error", "error_message": str(error_message)}

full_df = execution_result['content']
Expand Down Expand Up @@ -1237,6 +1248,7 @@ def _build_initial_messages(
primary_tables: list[str] | None = None,
attached_images: list[str] | None = None,
charts: list[dict[str, Any]] | None = None,
scratch_files: list[str] | None = None,
) -> list[dict]:
"""Build the initial messages with 3-tier context."""
table_summaries = self._build_lightweight_table_context(input_tables, primary_tables=primary_tables)
Expand Down Expand Up @@ -1273,6 +1285,25 @@ def _build_initial_messages(
rules_text = "\n\n".join([f"### {r['title']}\n{r['body']}" for r in always_apply_rules])
user_content += f"[USER RULES - MUST FOLLOW]\n\n{rules_text}\n\n"

# Non-image attachments were uploaded to the workspace scratch/ folder
# (raw bytes). Surface them and the two natural uses: read as context
# for analysis, or bring into the workspace via data loading (which
# shares scratch/ access). Keep it neutral — the agent picks.
if scratch_files:
file_list = "\n".join(f"- {p}" for p in scratch_files)
user_content += (
"[ATTACHED FILES]\n\n"
"The user attached file(s), saved in the workspace scratch/ "
"folder:\n"
f"{file_list}\n\n"
"You can use them two ways: read a file with execute_python_script "
"(e.g. pd.read_excel('scratch/<name>') or "
"pd.read_csv('scratch/<name>')) to use as context for your "
"analysis, or \u2014 if it's data to bring into the workspace \u2014 "
"delegate to data_loading, which can read the same scratch/ "
"files.\n\n"
)

user_content += f"[USER QUESTION]\n\n{user_question}"

system_prompt = self._build_system_prompt(
Expand Down
2 changes: 2 additions & 0 deletions py-src/data_formulator/analyst/mini_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ def run(
primary_tables: list[str] | None = None,
attached_images: list[str] | None = None,
charts: list[dict[str, Any]] | None = None,
scratch_files: list[str] | None = None,
) -> Generator[dict[str, Any], None, None]:
"""Make a single analytic decision and stop.

Expand Down Expand Up @@ -486,6 +487,7 @@ def run(
input_tables, user_question, focused_thread, other_threads,
primary_tables=primary_tables,
attached_images=attached_images, charts=charts,
scratch_files=scratch_files,
)
else:
messages = trajectory
Expand Down
Loading
Loading