open-data-sci 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 (85) hide show
  1. open_data_sci-0.1.0.dist-info/METADATA +629 -0
  2. open_data_sci-0.1.0.dist-info/RECORD +85 -0
  3. open_data_sci-0.1.0.dist-info/WHEEL +4 -0
  4. open_data_sci-0.1.0.dist-info/entry_points.txt +2 -0
  5. open_data_sci-0.1.0.dist-info/licenses/LICENSE +201 -0
  6. opendatasci/__init__.py +47 -0
  7. opendatasci/_tui/__init__.py +1 -0
  8. opendatasci/_tui/adapter.py +102 -0
  9. opendatasci/_tui/app.py +429 -0
  10. opendatasci/_tui/commands.py +95 -0
  11. opendatasci/_tui/completion.py +139 -0
  12. opendatasci/_tui/controller.py +644 -0
  13. opendatasci/_tui/file_refs.py +153 -0
  14. opendatasci/_tui/models.py +4 -0
  15. opendatasci/_tui/presenter.py +259 -0
  16. opendatasci/_tui/service.py +78 -0
  17. opendatasci/_tui/session.py +53 -0
  18. opendatasci/_tui/styles.tcss +248 -0
  19. opendatasci/_tui/styles_visible.tcss +245 -0
  20. opendatasci/_tui/theme.py +113 -0
  21. opendatasci/_tui/tools_display.py +86 -0
  22. opendatasci/_tui/widgets.py +1001 -0
  23. opendatasci/_utils/__init__.py +0 -0
  24. opendatasci/_utils/async_utils.py +11 -0
  25. opendatasci/_utils/data_formats.py +135 -0
  26. opendatasci/_utils/hash_utils.py +52 -0
  27. opendatasci/_utils/langchain_utils.py +155 -0
  28. opendatasci/_utils/streaming_utils.py +23 -0
  29. opendatasci/agents/__init__.py +12 -0
  30. opendatasci/agents/agents.py +515 -0
  31. opendatasci/agents/agents_factory.py +71 -0
  32. opendatasci/agents/chat_memory.py +397 -0
  33. opendatasci/agents/graphs.py +84 -0
  34. opendatasci/agents/nodes.py +74 -0
  35. opendatasci/agents/states.py +36 -0
  36. opendatasci/agents/turn_memory.py +124 -0
  37. opendatasci/configs.py +275 -0
  38. opendatasci/context/__init__.py +7 -0
  39. opendatasci/context/base.py +56 -0
  40. opendatasci/context/local.py +236 -0
  41. opendatasci/models/__init__.py +7 -0
  42. opendatasci/models/anthropic.py +40 -0
  43. opendatasci/models/aws.py +86 -0
  44. opendatasci/models/factory.py +179 -0
  45. opendatasci/models/google.py +79 -0
  46. opendatasci/models/local.py +79 -0
  47. opendatasci/models/microsoft.py +62 -0
  48. opendatasci/models/openai.py +49 -0
  49. opendatasci/models/providers.py +12 -0
  50. opendatasci/prompts/__init__.py +5 -0
  51. opendatasci/prompts/builders.py +85 -0
  52. opendatasci/prompts/caching.py +42 -0
  53. opendatasci/prompts/message_templates.py +7 -0
  54. opendatasci/prompts/prompt_templates.py +227 -0
  55. opendatasci/resources/skills/competitive_data_science.md +241 -0
  56. opendatasci/resources/skills/data_science.md +55 -0
  57. opendatasci/resources/skills/data_science_education.md +42 -0
  58. opendatasci/resources/skills/deep_learning.md +205 -0
  59. opendatasci/resources/skills/machine_learning.md +68 -0
  60. opendatasci/resources/skills/quantitative_analysis.md +45 -0
  61. opendatasci/sandbox/__init__.py +14 -0
  62. opendatasci/sandbox/_runner.py +114 -0
  63. opendatasci/sandbox/base.py +170 -0
  64. opendatasci/sandbox/srt.py +490 -0
  65. opendatasci/skills/__init__.py +9 -0
  66. opendatasci/skills/base.py +28 -0
  67. opendatasci/skills/local.py +131 -0
  68. opendatasci/streaming/__init__.py +37 -0
  69. opendatasci/streaming/events.py +159 -0
  70. opendatasci/streaming/processors.py +387 -0
  71. opendatasci/tools/__init__.py +58 -0
  72. opendatasci/tools/coding.py +261 -0
  73. opendatasci/tools/critic.py +136 -0
  74. opendatasci/tools/dataset_info.py +391 -0
  75. opendatasci/tools/factory.py +172 -0
  76. opendatasci/tools/mcp.py +179 -0
  77. opendatasci/tools/planning.py +88 -0
  78. opendatasci/tools/skills.py +90 -0
  79. opendatasci/tools/user_interaction.py +54 -0
  80. opendatasci/tools/web.py +236 -0
  81. opendatasci/tools/workers.py +237 -0
  82. opendatasci/tools/workspace.py +55 -0
  83. opendatasci/workspace/__init__.py +9 -0
  84. opendatasci/workspace/base.py +20 -0
  85. opendatasci/workspace/local.py +25 -0
@@ -0,0 +1,261 @@
1
+ """Execution-related tools: Python, TUI, and library listing."""
2
+
3
+ import ast
4
+ import re
5
+ import tomllib
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Literal
8
+
9
+ from langchain_core.messages import HumanMessage, SystemMessage
10
+ from langchain_core.tools import BaseTool, tool
11
+ from pydantic import BaseModel, Field
12
+
13
+ from opendatasci.models.factory import create_model
14
+ from opendatasci.sandbox.base import BaseSandbox, SandboxExecResult
15
+
16
+ if TYPE_CHECKING:
17
+ from opendatasci.configs import OpenDataSciConfig
18
+
19
+ PYPROJECT_TOML: Path = Path(__file__).parent.parent / "pyproject.toml"
20
+
21
+
22
+ @tool
23
+ def list_python_libs() -> str:
24
+ """Check which Python libraries are available before writing code that imports them.
25
+
26
+ Stdlib modules are always present; only non-standard imports need checking.
27
+ """
28
+ with PYPROJECT_TOML.open("rb") as fh:
29
+ data = tomllib.load(fh)
30
+ libs = data.get("tool", {}).get("opendatasci", {}).get("opendatasci_agent_libs", [])
31
+ if not libs:
32
+ return "No agent libraries configured."
33
+ return ",".join(libs)
34
+
35
+
36
+ class _CodeReview(BaseModel):
37
+ verdict: Literal["LGTM", "NEEDS CHANGES"] = Field(
38
+ description="Overall verdict: LGTM if the code is correct and optimal, NEEDS CHANGES otherwise."
39
+ )
40
+ correctness: str = Field(
41
+ description=(
42
+ "Concise findings on correctness: bugs, logical errors, off-by-one errors, "
43
+ "incorrect API usage, unhandled edge cases, type mismatches. "
44
+ 'Use "No issues found." if none.'
45
+ )
46
+ )
47
+ optimality: str = Field(
48
+ description=(
49
+ "Concise findings on optimality: unnecessary latency, excessive memory allocation, "
50
+ "redundant computation, missed vectorisation, suboptimal data structures. "
51
+ 'Use "No issues found." if none.'
52
+ )
53
+ )
54
+
55
+
56
+ _REVIEW_SYSTEM_PROMPT = """\
57
+ You are an expert Python code reviewer. Your role is to critically evaluate code \
58
+ before it runs in an expensive or high-latency pipeline stage, where a bug or \
59
+ inefficiency could be very costly to recover from.
60
+
61
+ Review the provided code on exactly two dimensions:
62
+
63
+ **Correctness** — bugs, logical errors, off-by-one errors, incorrect API usage, \
64
+ unhandled edge cases, wrong variable names, type mismatches, or any issue that would \
65
+ cause the code to raise an exception or produce incorrect results at runtime.
66
+
67
+ **Optimality** — unnecessary latency (e.g. redundant passes over large datasets, \
68
+ serial loops that should be vectorised, blocking I/O inside loops), excessive memory \
69
+ allocation, redundant computation, or suboptimal algorithm/data-structure choices \
70
+ that inflate wall-clock time or peak memory usage.
71
+
72
+ Be terse. Reference specific lines or variable names. Do not explain what the code does.\
73
+ """
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Helpers
78
+ # ---------------------------------------------------------------------------
79
+
80
+
81
+ def _format_exec_error(code: str, error: str) -> str:
82
+ """Format a Python execution error as a structured message for the agent.
83
+
84
+ Parses the traceback to extract the error type, the failing line number,
85
+ and the relevant code snippet so the agent addresses the specific problem
86
+ rather than retrying blindly.
87
+ """
88
+ lines = error.splitlines()
89
+
90
+ error_type = "Error"
91
+ error_msg = ""
92
+ for line in reversed(lines):
93
+ line = line.strip()
94
+ if not line:
95
+ continue
96
+ if ": " in line:
97
+ error_type, error_msg = line.split(": ", 1)
98
+ else:
99
+ error_type = line
100
+ break
101
+
102
+ failing_line: int | None = None
103
+ for line in lines:
104
+ m = re.search(r'File "<opendatasci>", line (\d+)', line)
105
+ if m:
106
+ failing_line = int(m.group(1))
107
+
108
+ snippet = ""
109
+ if failing_line is not None:
110
+ code_lines = code.splitlines()
111
+ if 1 <= failing_line <= len(code_lines):
112
+ snippet = code_lines[failing_line - 1].strip()
113
+
114
+ header = f"Error [{error_type}]"
115
+ if failing_line is not None:
116
+ header += f" on line {failing_line}"
117
+
118
+ parts = [header]
119
+ if snippet:
120
+ parts.append(f"Code: {snippet}")
121
+ if error_msg:
122
+ parts.append(f"Message: {error_msg}")
123
+ parts.append("")
124
+ parts.append("Address this specific error before retrying.")
125
+ return "\n".join(parts)
126
+
127
+
128
+ def _format_cli_result(result: SandboxExecResult) -> str:
129
+ """Format a TUI SandboxExecResult as a string for the agent."""
130
+ if result.success:
131
+ return result.stdout or "Command succeeded (no output)."
132
+ parts = []
133
+ if result.stdout:
134
+ parts.append(f"stdout:\n{result.stdout}")
135
+ if result.error:
136
+ parts.append(result.error)
137
+ return "\n".join(parts) if parts else "Command failed."
138
+
139
+
140
+ def create_code_verification_tools(datasci_config: "OpenDataSciConfig") -> list[BaseTool]:
141
+ """Return the ``verify_python_code`` tool pre-wired to *datasci_config*'s LLM."""
142
+ _llm = create_model(datasci_config).with_structured_output(_CodeReview)
143
+
144
+ @tool
145
+ async def verify_python_code(code: str, context: str = "") -> str:
146
+ """Gate-check Python code for correctness and optimality before a costly execution.
147
+
148
+ Returns a LGTM / NEEDS CHANGES verdict with per-dimension findings.
149
+
150
+ # When to use this tool
151
+ - Before executing code whose failure mid-pipeline would be expensive to recover from:
152
+ model training, distributed jobs, multi-step preprocessing pipelines.
153
+ - When the code is non-trivial and bugs would be hard to diagnose post-hoc.
154
+
155
+ # When NOT to use this tool
156
+ - When the code is cheap to run — just execute it and fix errors from the output.
157
+ - As a substitute for running code: verification reduces obvious risk but does not
158
+ prove correctness.
159
+
160
+ Args:
161
+ code: Python code to review.
162
+ context: Optional description of what the code does and any relevant
163
+ constraints (e.g. "Trains a gradient-boosting classifier on a
164
+ 10 M-row DataFrame; must finish in under 30 s and use < 8 GB RAM").
165
+ """
166
+ try:
167
+ ast.parse(code)
168
+ except SyntaxError as exc:
169
+ return (
170
+ f"Static check failed [SyntaxError] on line {exc.lineno}: {exc.msg}\n"
171
+ "Fix the syntax error and try again."
172
+ )
173
+
174
+ user_content = f"```python\n{code}\n```"
175
+ if context:
176
+ user_content = f"Context: {context}\n\n{user_content}"
177
+
178
+ messages = [
179
+ SystemMessage(content=_REVIEW_SYSTEM_PROMPT),
180
+ HumanMessage(content=user_content),
181
+ ]
182
+ review: _CodeReview = await _llm.ainvoke(messages) # type: ignore[assignment]
183
+
184
+ return (
185
+ f"VERDICT: {review.verdict}\n\n"
186
+ f"### Correctness\n{review.correctness}\n\n"
187
+ f"### Optimality\n{review.optimality}"
188
+ )
189
+
190
+ return [verify_python_code]
191
+
192
+
193
+ def create_coding_tools(sandbox: BaseSandbox) -> list[BaseTool]:
194
+ """Return execution tools bound to *sandbox*: execute_python_code."""
195
+
196
+ @tool
197
+ async def execute_python_code(code: str, summary: str, communication: str) -> str:
198
+ """Execute Python code in the active workspace environment.
199
+
200
+ # Pre-bound variables
201
+ - ``wb``: workspace data files.
202
+ - ``sheets``: ``{"sheet_name": DataFrame, ...}``
203
+ - ``text_files``: ``{"filename": content, ...}``
204
+ - ``opendatasci_directory``: ``Path`` for saving output files to the workspace.
205
+ - ``save_result(name, value)``: persist a named result for export.
206
+
207
+ # How to use this tool
208
+ - Assign ``result = ...`` to return a value.
209
+ - Any library can be imported; check ``list_python_libs`` first for non-standard ones.
210
+ - Prefer vectorised operations over row-wise loops on large DataFrames.
211
+
212
+ # How NOT to use this tool
213
+ - Don't retry the same failing code verbatim — address the structured error before retrying.
214
+
215
+ Args:
216
+ code: Python code to execute.
217
+ summary: 3-4 word status label (e.g. "Calculating monthly totals").
218
+ communication: Brief message to the user about what you're doing
219
+ (e.g. "Let me load the sales data and check for missing values.").
220
+ """
221
+ exec_result = await sandbox.execute(code)
222
+ if exec_result.success:
223
+ parts = []
224
+ if exec_result.stdout:
225
+ parts.append(f"stdout:\n{exec_result.stdout}")
226
+ if exec_result.output is not None:
227
+ parts.append(f"result:\n{exec_result.output}")
228
+ return "\n".join(parts) if parts else "Code executed successfully (no output)"
229
+ return _format_exec_error(code, exec_result.error or "")
230
+
231
+ return [execute_python_code, list_python_libs]
232
+
233
+
234
+ def create_cli_tools(sandbox: BaseSandbox) -> list[BaseTool]:
235
+ """Return the execute_cli_command tool bound to *sandbox* (main agent only)."""
236
+
237
+ @tool
238
+ async def execute_cli_command(command: str, summary: str, communication: str) -> str:
239
+ """Run a read-oriented TUI command inside the active workspace directory.
240
+
241
+ Useful for inspecting the workspace without Python: listing files,
242
+ searching for patterns, counting lines, or diffing outputs.
243
+
244
+ # Permitted commands
245
+ ``ls``, ``cat``, ``grep``, ``wc``, ``find``, ``head``, ``tail``, ``cut``,
246
+ ``diff``, and others in the safe set. ``|`` and ``&&`` are allowed.
247
+
248
+ # When NOT to use this tool
249
+ - For write operations (file creation, deletion, or modification) — not permitted.
250
+ - When ``list_workspace_files`` already covers the need.
251
+
252
+ Args:
253
+ command: TUI command to run (e.g. ``"ls -la"``, ``"grep -r 'keyword' ."``).
254
+ summary: 3-4 word status label (e.g. "Listing workspace files").
255
+ communication: Brief message to the user about what you're doing
256
+ (e.g. "Let me see what files are available.").
257
+ """
258
+ cli_result = await sandbox.execute_cli(command)
259
+ return _format_cli_result(cli_result)
260
+
261
+ return [execute_cli_command]
@@ -0,0 +1,136 @@
1
+ """Self-review mode tools: enter_self_review_mode and exit_self_review_mode."""
2
+
3
+ import logging
4
+ from typing import Annotated, Any
5
+
6
+ from langchain_core.messages import ToolMessage
7
+ from langchain_core.tools import BaseTool, tool
8
+ from langchain_core.tools.base import InjectedToolCallId
9
+ from langgraph.prebuilt import InjectedState
10
+ from langgraph.types import Command
11
+
12
+ from opendatasci.agents.states import AgentState
13
+ from opendatasci.skills.base import BaseSkillStore
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def create_critic_tools(
19
+ store: BaseSkillStore,
20
+ ) -> list[BaseTool]:
21
+ """Return ``enter_self_review_mode`` and ``exit_self_review_mode``.
22
+
23
+ Args:
24
+ store: Skill store used to resolve the optional skill argument.
25
+ """
26
+ return [
27
+ _create_enter_tool(store),
28
+ _create_exit_tool(),
29
+ ]
30
+
31
+
32
+ def _create_enter_tool(store: BaseSkillStore) -> BaseTool:
33
+ @tool
34
+ def enter_self_review_mode(
35
+ state: Annotated[AgentState, InjectedState],
36
+ tool_call_id: Annotated[str, InjectedToolCallId],
37
+ skill: str | None = None,
38
+ ) -> Command[AgentState]:
39
+ """Enter Self-Review Mode to critically audit your work before continuing.
40
+
41
+ In Self-Review Mode only read-only tools are available. Call
42
+ ``exit_self_review_mode`` with your full review to return to execution.
43
+
44
+ # When to use this tool
45
+ - After a complex multi-step analysis to verify that your methodology is sound and your key results were obtained correctly.
46
+ - When results look surprising or inconsistent with expectations.
47
+ - Before a consequential decision that depends heavily on prior work.
48
+
49
+ # When NOT to use this tool
50
+ - While Plan Mode is active — exit plan mode first.
51
+ - For routine single-step work where there is nothing meaningful to review.
52
+
53
+ Args:
54
+ skill: Optional skill profile to load before reviewing
55
+ (e.g. ``"data_science"``). Omit to keep the current skill.
56
+ """
57
+ if state.is_plan_mode:
58
+ return Command(
59
+ update={
60
+ "messages": [
61
+ ToolMessage(
62
+ content=(
63
+ "Cannot enter self-review mode while plan mode is active. "
64
+ "Exit plan mode first, then call enter_self_review_mode."
65
+ ),
66
+ tool_call_id=tool_call_id,
67
+ )
68
+ ]
69
+ }
70
+ )
71
+
72
+ state_update: dict[str, Any] = {"is_self_review_mode": True}
73
+ if skill is not None:
74
+ loaded = store.load(skill)
75
+ if loaded is None:
76
+ available = ", ".join(sorted(store.list()))
77
+ return Command(
78
+ update={
79
+ "messages": [
80
+ ToolMessage(
81
+ content=f"Unknown skill '{skill}'. Available: {available}",
82
+ tool_call_id=tool_call_id,
83
+ )
84
+ ]
85
+ }
86
+ )
87
+ state_update["active_skills"] = [loaded]
88
+
89
+ state_update["messages"] = [
90
+ ToolMessage(
91
+ content=(
92
+ "Self-review mode active. Review the entire conversation, all results, "
93
+ "plans, dataset notes, and artefacts produced so far, then assess whether "
94
+ "the analysis is on the right track. "
95
+ "Call exit_self_review_mode once your review is complete."
96
+ ),
97
+ tool_call_id=tool_call_id,
98
+ )
99
+ ]
100
+ return Command(update=state_update)
101
+
102
+ return enter_self_review_mode
103
+
104
+
105
+ def _create_exit_tool() -> BaseTool:
106
+ @tool
107
+ def exit_self_review_mode(
108
+ review: str,
109
+ tool_call_id: Annotated[str, InjectedToolCallId],
110
+ ) -> Command[AgentState]:
111
+ """Exit Self-Review Mode and record the review findings.
112
+
113
+ Returns to execution mode. If missteps were identified, correct course before proceeding.
114
+
115
+ # How to use this tool
116
+ - Reference concrete results, tool calls, or decisions from the conversation.
117
+ - Be specific: name what is wrong (or confirm what is sound) — vague assessments are useless.
118
+
119
+ Args:
120
+ review: A clear assessment of whether your work is on the right track.
121
+ Describe any missteps, incorrect assumptions, or missed steps — or
122
+ confirm that your progress is sound.
123
+ """
124
+ content = (
125
+ f"Self-review complete. Review recorded:\n\n{review}\n\n"
126
+ "You are back in execution mode. "
127
+ "If missteps were identified, correct course before proceeding."
128
+ )
129
+ return Command(
130
+ update={
131
+ "is_self_review_mode": False,
132
+ "messages": [ToolMessage(content=content, tool_call_id=tool_call_id)],
133
+ }
134
+ )
135
+
136
+ return exit_self_review_mode