phronesisml 0.2.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 (78) hide show
  1. phronesisml/__init__.py +326 -0
  2. phronesisml/agents/__init__.py +0 -0
  3. phronesisml/agents/base.py +125 -0
  4. phronesisml/agents/eda/__init__.py +0 -0
  5. phronesisml/agents/eda/agent.py +113 -0
  6. phronesisml/agents/etl/__init__.py +0 -0
  7. phronesisml/agents/etl/agent.py +165 -0
  8. phronesisml/agents/evaluation/__init__.py +0 -0
  9. phronesisml/agents/evaluation/agent.py +169 -0
  10. phronesisml/agents/explainability/__init__.py +0 -0
  11. phronesisml/agents/explainability/agent.py +191 -0
  12. phronesisml/agents/feature_engineering/__init__.py +0 -0
  13. phronesisml/agents/feature_engineering/agent.py +148 -0
  14. phronesisml/agents/model_selection/__init__.py +0 -0
  15. phronesisml/agents/model_selection/agent.py +252 -0
  16. phronesisml/agents/reporting/__init__.py +0 -0
  17. phronesisml/agents/reporting/agent.py +90 -0
  18. phronesisml/agents/storage/__init__.py +0 -0
  19. phronesisml/agents/storage/agent.py +130 -0
  20. phronesisml/agents/target_detection/__init__.py +0 -0
  21. phronesisml/agents/target_detection/agent.py +127 -0
  22. phronesisml/agents/upload/__init__.py +0 -0
  23. phronesisml/agents/upload/agent.py +147 -0
  24. phronesisml/agents/validation/__init__.py +0 -0
  25. phronesisml/agents/validation/agent.py +111 -0
  26. phronesisml/configs/__init__.py +5 -0
  27. phronesisml/configs/settings.py +97 -0
  28. phronesisml/data/__init__.py +0 -0
  29. phronesisml/data/loaders/__init__.py +0 -0
  30. phronesisml/data/loaders/file_loader.py +223 -0
  31. phronesisml/data/profilers/__init__.py +9 -0
  32. phronesisml/data/profilers/stats.py +119 -0
  33. phronesisml/data/transformers/__init__.py +0 -0
  34. phronesisml/data/transformers/cleaning.py +175 -0
  35. phronesisml/data/validators/__init__.py +9 -0
  36. phronesisml/data/validators/checks.py +103 -0
  37. phronesisml/engines/__init__.py +0 -0
  38. phronesisml/engines/base_engine.py +210 -0
  39. phronesisml/engines/engine_selector.py +125 -0
  40. phronesisml/engines/pandas_engine.py +134 -0
  41. phronesisml/engines/polars_engine.py +139 -0
  42. phronesisml/engines/spark_engine.py +148 -0
  43. phronesisml/exceptions.py +84 -0
  44. phronesisml/interfaces/api/__init__.py +7 -0
  45. phronesisml/interfaces/api/app.py +137 -0
  46. phronesisml/interfaces/api/jobs.py +128 -0
  47. phronesisml/interfaces/api/models.py +132 -0
  48. phronesisml/interfaces/api/routes.py +486 -0
  49. phronesisml/interfaces/cli/__init__.py +0 -0
  50. phronesisml/interfaces/cli/app.py +114 -0
  51. phronesisml/ml/__init__.py +0 -0
  52. phronesisml/ml/automl/__init__.py +23 -0
  53. phronesisml/ml/automl/auto_selector.py +288 -0
  54. phronesisml/ml/automl/trainer.py +333 -0
  55. phronesisml/ml/evaluation/__init__.py +9 -0
  56. phronesisml/ml/evaluation/metrics.py +247 -0
  57. phronesisml/ml/explainability/__init__.py +16 -0
  58. phronesisml/ml/explainability/shap_explainer.py +214 -0
  59. phronesisml/ml/feature_engineering/__init__.py +9 -0
  60. phronesisml/ml/feature_engineering/engineer.py +358 -0
  61. phronesisml/ml/reports/__init__.py +9 -0
  62. phronesisml/ml/reports/builder.py +458 -0
  63. phronesisml/ml/reports/templates/full_report.md +64 -0
  64. phronesisml/ml/target_detection/__init__.py +12 -0
  65. phronesisml/ml/target_detection/detector.py +252 -0
  66. phronesisml/py.typed +0 -0
  67. phronesisml/sdk.py +786 -0
  68. phronesisml/simple.py +1039 -0
  69. phronesisml/workflow/__init__.py +0 -0
  70. phronesisml/workflow/graph.py +186 -0
  71. phronesisml/workflow/nodes.py +69 -0
  72. phronesisml/workflow/router.py +157 -0
  73. phronesisml/workflow/state.py +185 -0
  74. phronesisml-0.2.0.dist-info/METADATA +622 -0
  75. phronesisml-0.2.0.dist-info/RECORD +78 -0
  76. phronesisml-0.2.0.dist-info/WHEEL +4 -0
  77. phronesisml-0.2.0.dist-info/entry_points.txt +2 -0
  78. phronesisml-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,326 @@
1
+ """Phronesis — public SDK surface.
2
+
3
+ This is the canonical entry point for programmatic use of Phronesis.
4
+ All public API is exposed here; ``interfaces/cli/`` consumes this
5
+ surface.
6
+
7
+ Usage::
8
+
9
+ # Simple API (recommended for quickstart)
10
+ from phronesisml import analyze, train
11
+
12
+ profile = analyze("data.csv")
13
+ result = train("data.csv")
14
+
15
+ # OOP API
16
+ from phronesisml import Phronesis
17
+
18
+ ml = Phronesis("data.csv")
19
+ ml.run()
20
+ print(ml.report())
21
+
22
+ # Advanced API (full control)
23
+ import phronesisml
24
+
25
+ result = await phronesisml.run_pipeline(data_path="data.csv")
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import logging
31
+ from typing import Any
32
+
33
+ from phronesisml.configs.settings import PhronesisConfig
34
+ from phronesisml.exceptions import ConfigurationError, PhronesisError, WorkflowError
35
+ from phronesisml.sdk import (
36
+ DatasetSummary,
37
+ EDAReport,
38
+ EvaluationMetrics,
39
+ ExplanationReport,
40
+ FeatureReport,
41
+ ModelInfo,
42
+ Phronesis,
43
+ TargetInfo,
44
+ ValidationReport,
45
+ )
46
+ from phronesisml.simple import (
47
+ CleanResult,
48
+ DatasetProfile,
49
+ ExplainResult,
50
+ FeatureResult,
51
+ ModelResult,
52
+ TargetResult,
53
+ TrainResult,
54
+ ValidationResult,
55
+ analyze,
56
+ analyze_async,
57
+ clean,
58
+ clean_async,
59
+ detect_target,
60
+ detect_target_async,
61
+ engineer,
62
+ engineer_async,
63
+ explain,
64
+ explain_async,
65
+ report,
66
+ report_async,
67
+ select_model,
68
+ select_model_async,
69
+ train,
70
+ train_async,
71
+ validate,
72
+ validate_async,
73
+ )
74
+ from phronesisml.workflow.state import WorkflowState
75
+
76
+ logger = logging.getLogger(__name__)
77
+
78
+ __version__ = "0.2.0"
79
+
80
+ __all__ = [
81
+ # ── Simple API ──────────────────────────────────────
82
+ "analyze",
83
+ "analyze_async",
84
+ "clean",
85
+ "clean_async",
86
+ "validate",
87
+ "validate_async",
88
+ "detect_target",
89
+ "detect_target_async",
90
+ "engineer",
91
+ "engineer_async",
92
+ "select_model",
93
+ "select_model_async",
94
+ "explain",
95
+ "explain_async",
96
+ "report",
97
+ "report_async",
98
+ "train",
99
+ "train_async",
100
+ "CleanResult",
101
+ "DatasetProfile",
102
+ "ExplainResult",
103
+ "FeatureResult",
104
+ "ModelResult",
105
+ "TargetResult",
106
+ "TrainResult",
107
+ "ValidationResult",
108
+ # ── OOP API ─────────────────────────────────────────
109
+ "Phronesis",
110
+ "DatasetSummary",
111
+ "EDAReport",
112
+ "EvaluationMetrics",
113
+ "ExplanationReport",
114
+ "FeatureReport",
115
+ "ModelInfo",
116
+ "TargetInfo",
117
+ "ValidationReport",
118
+ # ── Advanced API ────────────────────────────────────
119
+ "PhronesisConfig",
120
+ "PhronesisError",
121
+ "ConfigurationError",
122
+ "WorkflowError",
123
+ "WorkflowState",
124
+ "run_pipeline",
125
+ "__version__",
126
+ ]
127
+
128
+
129
+ def _compose_agents(
130
+ config: PhronesisConfig,
131
+ data_path: str,
132
+ ) -> dict[str, Any]:
133
+ """Manually compose all agents via constructor injection.
134
+
135
+ This is the composition root — the only place where concrete agent
136
+ and engine classes are instantiated. The rest of the SDK depends
137
+ on abstractions (``BaseAgent``, ``BaseEngine``).
138
+
139
+ All agent imports are deferred to this function so that
140
+ ``import phronesisml`` does not pull in every dependency eagerly.
141
+ """
142
+ from phronesisml.agents.eda.agent import EDAAgent
143
+ from phronesisml.agents.etl.agent import ETLAgent, ETLConfig
144
+ from phronesisml.agents.evaluation.agent import EvaluationAgent
145
+ from phronesisml.agents.explainability.agent import ExplainabilityAgent
146
+ from phronesisml.agents.feature_engineering.agent import FeatureEngineeringAgent
147
+ from phronesisml.agents.model_selection.agent import ModelSelectionAgent
148
+ from phronesisml.agents.reporting.agent import ReportingAgent
149
+ from phronesisml.agents.storage.agent import StorageAgent
150
+ from phronesisml.agents.target_detection.agent import TargetDetectionAgent
151
+ from phronesisml.agents.upload.agent import UploadAgent
152
+ from phronesisml.agents.validation.agent import ValidationAgent
153
+ from phronesisml.engines.engine_selector import select_engine
154
+
155
+ # Select the computation engine
156
+ engine = select_engine(config=config, data_path=data_path)
157
+
158
+ # Instantiate agents with their dependencies
159
+ agents: dict[str, Any] = {
160
+ "upload": UploadAgent(engine=engine),
161
+ "validation": ValidationAgent(engine=engine),
162
+ "etl": ETLAgent(config=ETLConfig(null_strategy="drop")),
163
+ "eda": EDAAgent(engine=engine),
164
+ "target_detection": TargetDetectionAgent(engine=engine),
165
+ "feature_engineering": FeatureEngineeringAgent(
166
+ engine=engine,
167
+ feature_selection_config=config.feature_selection,
168
+ ),
169
+ "model_selection": ModelSelectionAgent(engine=engine),
170
+ "evaluation": EvaluationAgent(engine=engine),
171
+ "explainability": ExplainabilityAgent(engine=engine),
172
+ "reporting": ReportingAgent(),
173
+ "storage": StorageAgent(),
174
+ }
175
+ return agents
176
+
177
+
178
+ _FULL_PIPELINE_STAGES: list[str] = [
179
+ "upload",
180
+ "etl",
181
+ "validation",
182
+ "eda",
183
+ "target_detection",
184
+ "feature_engineering",
185
+ "model_selection",
186
+ "evaluation",
187
+ "explainability",
188
+ "reporting",
189
+ "storage",
190
+ ]
191
+
192
+
193
+ async def run_pipeline(
194
+ data_path: str,
195
+ engine_preference: str | None = None,
196
+ null_strategy: str = "drop",
197
+ stages: list[str] | None = None,
198
+ config: PhronesisConfig | None = None,
199
+ ) -> dict[str, Any]:
200
+ """Run the Phronesis pipeline on a dataset.
201
+
202
+ This is the primary public API. It:
203
+ 1. Builds configuration (from *config* or defaults).
204
+ 2. Composes agents via manual DI.
205
+ 3. Constructs the LangGraph workflow with the requested stages.
206
+ 4. Executes the graph with the initial state.
207
+
208
+ Args:
209
+ data_path: Path to the input dataset.
210
+ engine_preference: Force a specific engine (``"pandas"``, ``"polars"``,
211
+ ``"spark"``). ``None`` for auto-selection.
212
+ null_strategy: Null handling strategy (``"drop"``, ``"fill"``, ``"flag"``).
213
+ stages: Ordered list of pipeline stages to execute. If ``None``,
214
+ runs the full pipeline (all 11 stages).
215
+ config: Optional pre-built configuration. If ``None``, a config is
216
+ constructed from the other arguments.
217
+
218
+ Returns:
219
+ A dict summarising the pipeline results.
220
+
221
+ Raises:
222
+ WorkflowError: If the workflow graph execution fails.
223
+
224
+ """
225
+ if config is None:
226
+ config = PhronesisConfig()
227
+ if engine_preference is not None:
228
+ config.engine.preferred = engine_preference
229
+ if stages is None:
230
+ stages = list(_FULL_PIPELINE_STAGES)
231
+
232
+ logger.info("Phronesis pipeline starting — data_path=%s", data_path)
233
+
234
+ # 1–3. Compose agents, build graph, build initial state
235
+ try:
236
+ agents = _compose_agents(config, data_path)
237
+ from phronesisml.workflow.graph import build_graph
238
+
239
+ graph = build_graph(agents, stages=stages)
240
+ initial_state = WorkflowState(data_path=data_path)
241
+ except WorkflowError:
242
+ raise
243
+ except ConfigurationError as exc:
244
+ msg = f"Pipeline configuration failed: {exc}"
245
+ logger.exception(msg)
246
+ raise WorkflowError(msg) from exc
247
+ except Exception as exc:
248
+ msg = f"Pipeline setup failed: {exc}"
249
+ logger.exception(msg)
250
+ raise WorkflowError(msg) from exc
251
+
252
+ # 4. Execute
253
+ try:
254
+ final_state = await graph.ainvoke(initial_state)
255
+ except WorkflowError:
256
+ raise
257
+ except Exception as exc:
258
+ msg = f"Workflow execution failed: {exc}"
259
+ logger.exception(msg)
260
+ raise WorkflowError(msg) from exc
261
+
262
+ logger.info("Phronesis pipeline complete.")
263
+
264
+ # Extract summary from the final state
265
+ if isinstance(final_state, dict):
266
+ return _extract_summary(final_state)
267
+ return _extract_summary(final_state.model_dump())
268
+
269
+
270
+ def _extract_summary(state: dict[str, Any]) -> dict[str, Any]:
271
+ """Build a summary dict from the final workflow state."""
272
+ processed = state.get("processed_data")
273
+ validated = state.get("validated_data")
274
+ profile = state.get("data_profile")
275
+ best_pipeline = state.get("best_pipeline")
276
+ evaluation_report = state.get("evaluation_report")
277
+
278
+ return {
279
+ "row_count": state.get("row_count"),
280
+ "column_count": (
281
+ len(processed.columns)
282
+ if processed is not None
283
+ else (len(validated.columns) if validated is not None else None)
284
+ ),
285
+ "transformations": (
286
+ len(state["transform_log"]) if state.get("transform_log") is not None else 0
287
+ ),
288
+ "validation_passed": (
289
+ state["validation_report"].get("passed")
290
+ if state.get("validation_report") is not None
291
+ else None
292
+ ),
293
+ "numeric_columns": (profile.get("numeric_columns") if profile is not None else None),
294
+ "categorical_columns": (
295
+ profile.get("categorical_columns") if profile is not None else None
296
+ ),
297
+ "target_column": state.get("target_column"),
298
+ "task_type": state.get("task_type"),
299
+ "target_detection_confidence": state.get("target_detection_confidence"),
300
+ "ambiguity_reason": state.get("ambiguity_reason"),
301
+ "n_features": (
302
+ len(state["feature_names"]) if state.get("feature_names") is not None else None
303
+ ),
304
+ "best_model_type": (best_pipeline.get("model_type") if best_pipeline is not None else None),
305
+ "best_model_score": (best_pipeline.get("score") if best_pipeline is not None else None),
306
+ "hpo_truncated": (best_pipeline.get("truncated") if best_pipeline is not None else None),
307
+ "evaluation_metrics": (
308
+ evaluation_report.get("metrics") if evaluation_report is not None else None
309
+ ),
310
+ "evaluation_ambiguity_caveat": (
311
+ evaluation_report.get("ambiguity_caveat") if evaluation_report is not None else None
312
+ ),
313
+ "explanation_sampled": (
314
+ state["explanation_report"].get("sampled")
315
+ if state.get("explanation_report") is not None
316
+ else None
317
+ ),
318
+ "explanation_explainer_type": (
319
+ state["explanation_report"].get("explainer_type")
320
+ if state.get("explanation_report") is not None
321
+ else None
322
+ ),
323
+ "final_report_length": (
324
+ len(state["final_report"]) if state.get("final_report") is not None else None
325
+ ),
326
+ }
File without changes
@@ -0,0 +1,125 @@
1
+ """Base contracts for the Phronesis agent system.
2
+
3
+ Every agent in the framework must satisfy the ``BaseAgent`` protocol.
4
+ The protocol uses structural subtyping (``Protocol``) so agents do not
5
+ need to inherit from a base class — they only need to implement the
6
+ required attributes and methods with the correct signatures.
7
+
8
+ Design rationale:
9
+ - ``Protocol`` over ABC: avoids diamond-inheritance issues when agents
10
+ also need domain-specific base classes (e.g. domain-specific agents).
11
+ - ``async run``: agents may perform I/O (file reads, DB
12
+ queries) and async keeps the workflow non-blocking.
13
+ - ``AgentResult``: a lightweight return type that carries the agent's
14
+ output payload and optional metadata, keeping the contract uniform
15
+ across all agents regardless of what they produce.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Any, Protocol, runtime_checkable
21
+
22
+ __all__ = ["AgentResult", "BaseAgent", "Tool"]
23
+
24
+ from pydantic import BaseModel, Field
25
+
26
+ from phronesisml.exceptions import AgentNotImplementedError
27
+
28
+
29
+ class AgentResult(BaseModel):
30
+ """Standardised return value from every agent ``run()`` call.
31
+
32
+ Attributes:
33
+ success: Whether the agent completed without error.
34
+ data: Agent-specific output payload. The shape varies per agent
35
+ and is documented in each agent's own module docstring.
36
+ error: Human-readable error message when ``success`` is False.
37
+ error_type: Exception class name when ``success`` is False.
38
+ E.g. ``"DataLoadError"``, ``"ValueError"``. Survives JSON
39
+ serialization for non-Python consumers (FastAPI boundary).
40
+ error_message: Original exception message when ``success`` is False.
41
+ May differ from ``error`` (which is the agent-wrapped message).
42
+ error_context: Optional structured context dict for debugging.
43
+ Keys and values must be strings to survive serialization.
44
+ metadata: Arbitrary key-value metadata (e.g. row counts, timings).
45
+
46
+ """
47
+
48
+ success: bool = True
49
+ data: dict[str, Any] = Field(default_factory=dict)
50
+ error: str | None = None
51
+ error_type: str | None = None
52
+ error_message: str | None = None
53
+ error_context: dict[str, str] | None = None
54
+ metadata: dict[str, Any] = Field(default_factory=dict)
55
+
56
+
57
+ class Tool(BaseModel):
58
+ """Descriptor for a tool an agent exposes to the workflow.
59
+
60
+ This is a data descriptor, not a callable — the actual tool
61
+ implementation lives in the agent; the ``Tool`` object is a
62
+ serialisable declaration used for discovery and documentation.
63
+ """
64
+
65
+ name: str
66
+ description: str
67
+ parameters: dict[str, Any] = Field(default_factory=dict)
68
+
69
+
70
+ @runtime_checkable
71
+ class BaseAgent(Protocol):
72
+ """Protocol that every Phronesis agent must satisfy.
73
+
74
+ Agents are stateless with respect to the workflow — all mutable
75
+ state lives in ``WorkflowState`` and is passed in on every ``run()``
76
+ call. This makes agents independently testable and composable.
77
+ """
78
+
79
+ name: str
80
+ description: str
81
+
82
+ async def run(self, state: Any) -> AgentResult:
83
+ """Execute the agent's core logic against the current workflow state.
84
+
85
+ Agents MUST return an ``AgentResult`` — they MUST NOT raise
86
+ exceptions for expected/transient failures (bad data, missing
87
+ columns, model failures). Instead, return
88
+ ``AgentResult(success=False, error=..., error_type=..., error_message=...)``
89
+ so the workflow can decide whether to continue or abort.
90
+
91
+ Agents SHOULD raise only for programming errors (bugs, missing
92
+ dependencies) or truly unrecoverable failures (out of memory).
93
+
94
+ Args:
95
+ state: The shared ``WorkflowState`` instance.
96
+
97
+ Returns:
98
+ An ``AgentResult`` carrying the agent's output.
99
+
100
+ """
101
+ ...
102
+
103
+ def get_tools(self) -> list[Tool]:
104
+ """Return the list of tools this agent exposes."""
105
+ ...
106
+
107
+
108
+ class _StubAgent:
109
+ """Minimal stub that satisfies ``BaseAgent`` for agents not yet implemented.
110
+
111
+ This is used only during the initial skeleton pass to validate that
112
+ the protocol works structurally across all 15 agent directories.
113
+ It will be replaced by real implementations in subsequent passes.
114
+ """
115
+
116
+ def __init__(self, name: str, description: str) -> None:
117
+ self.name = name
118
+ self.description = description
119
+
120
+ async def run(self, state: Any) -> AgentResult:
121
+ msg = f"Agent '{self.name}' has not been implemented yet."
122
+ raise AgentNotImplementedError(msg)
123
+
124
+ def get_tools(self) -> list[Tool]:
125
+ return []
File without changes
@@ -0,0 +1,113 @@
1
+ """EDA agent — exploratory data analysis and statistical profiling.
2
+
3
+ This agent receives the validated DataFrame from the workflow state and
4
+ produces descriptive statistics and distribution analysis using the
5
+ active computation engine:
6
+
7
+ 1. Descriptive statistics per column (min/max/mean/std for numeric;
8
+ cardinality/top values for categorical).
9
+ 2. Dataset-level summary (shape, dtypes, memory usage).
10
+
11
+ Design:
12
+ - Stateless: all inputs come from ``WorkflowState``.
13
+ - Engine-mediated: all data operations go through ``BaseEngine`` —
14
+ no direct pandas/polars imports.
15
+ - Thin orchestrator: delegates to ``data.profilers.stats`` for real
16
+ computation logic.
17
+ - Returns: dict with ``data_profile`` and ``eda_report``
18
+ that LangGraph merges into the workflow state.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ from typing import Any
25
+
26
+ from phronesisml.agents.base import AgentResult, Tool
27
+ from phronesisml.data.profilers.stats import profile_dataset
28
+ from phronesisml.engines.base_engine import BaseEngine
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class EDAAgent:
34
+ """Agent responsible for exploratory data analysis and profiling.
35
+
36
+ Args:
37
+ engine: The active computation engine used for data operations.
38
+
39
+ """
40
+
41
+ name = "eda"
42
+ description = "Perform exploratory data analysis and statistical profiling."
43
+
44
+ def __init__(self, engine: BaseEngine) -> None:
45
+ self._engine = engine
46
+
47
+ async def run(self, state: Any) -> AgentResult:
48
+ """Profile ``state.validated_data`` (or ``state.processed_data``).
49
+
50
+ Reads from: ``state.validated_data`` (preferred) or
51
+ ``state.processed_data`` (fallback).
52
+ Returns: dict with ``data_profile`` and ``eda_report``
53
+ """
54
+ data = state.validated_data if state.validated_data is not None else state.processed_data
55
+ if data is None:
56
+ return AgentResult(
57
+ success=False,
58
+ error="No validated_data or processed_data in workflow state.",
59
+ )
60
+
61
+ try:
62
+ profile = profile_dataset(data, self._engine)
63
+
64
+ eda_report = {
65
+ "summary": f"Dataset has {profile['shape']['rows']} rows and "
66
+ f"{profile['shape']['columns']} columns.",
67
+ "numeric_columns": profile["numeric_columns"],
68
+ "categorical_columns": profile["categorical_columns"],
69
+ "memory_bytes": profile["memory_bytes"],
70
+ }
71
+
72
+ logger.info(
73
+ "EDA complete: %d rows, %d columns, %d numeric, %d categorical.",
74
+ profile["shape"]["rows"],
75
+ profile["shape"]["columns"],
76
+ len(profile["numeric_columns"]),
77
+ len(profile["categorical_columns"]),
78
+ )
79
+ return AgentResult(
80
+ success=True,
81
+ data={
82
+ "data_profile": profile,
83
+ "eda_report": eda_report,
84
+ },
85
+ metadata={
86
+ "rows": profile["shape"]["rows"],
87
+ "columns": profile["shape"]["columns"],
88
+ },
89
+ )
90
+ except Exception as exc:
91
+ msg = f"Unexpected error during EDA: {exc}"
92
+ logger.exception(msg)
93
+ return AgentResult(
94
+ success=False,
95
+ error=msg,
96
+ error_type=type(exc).__name__,
97
+ error_message=str(exc),
98
+ )
99
+
100
+ def get_tools(self) -> list[Tool]:
101
+ return [
102
+ Tool(
103
+ name="profile_data",
104
+ description="Compute descriptive statistics and distribution info for a DataFrame.",
105
+ parameters={
106
+ "columns": {
107
+ "type": "array",
108
+ "items": {"type": "string"},
109
+ "description": "Specific columns to profile (default: all).",
110
+ },
111
+ },
112
+ ),
113
+ ]
File without changes