DeepFabric 4.4.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 (71) hide show
  1. deepfabric/__init__.py +70 -0
  2. deepfabric/__main__.py +6 -0
  3. deepfabric/auth.py +382 -0
  4. deepfabric/builders.py +303 -0
  5. deepfabric/builders_agent.py +1304 -0
  6. deepfabric/cli.py +1288 -0
  7. deepfabric/config.py +899 -0
  8. deepfabric/config_manager.py +251 -0
  9. deepfabric/constants.py +94 -0
  10. deepfabric/dataset_manager.py +534 -0
  11. deepfabric/error_codes.py +581 -0
  12. deepfabric/evaluation/__init__.py +47 -0
  13. deepfabric/evaluation/backends/__init__.py +32 -0
  14. deepfabric/evaluation/backends/ollama_backend.py +137 -0
  15. deepfabric/evaluation/backends/tool_call_parsers.py +409 -0
  16. deepfabric/evaluation/backends/transformers_backend.py +326 -0
  17. deepfabric/evaluation/evaluator.py +845 -0
  18. deepfabric/evaluation/evaluators/__init__.py +13 -0
  19. deepfabric/evaluation/evaluators/base.py +104 -0
  20. deepfabric/evaluation/evaluators/builtin/__init__.py +5 -0
  21. deepfabric/evaluation/evaluators/builtin/tool_calling.py +93 -0
  22. deepfabric/evaluation/evaluators/registry.py +66 -0
  23. deepfabric/evaluation/inference.py +155 -0
  24. deepfabric/evaluation/metrics.py +397 -0
  25. deepfabric/evaluation/parser.py +304 -0
  26. deepfabric/evaluation/reporters/__init__.py +13 -0
  27. deepfabric/evaluation/reporters/base.py +56 -0
  28. deepfabric/evaluation/reporters/cloud_reporter.py +195 -0
  29. deepfabric/evaluation/reporters/file_reporter.py +61 -0
  30. deepfabric/evaluation/reporters/multi_reporter.py +56 -0
  31. deepfabric/exceptions.py +67 -0
  32. deepfabric/factory.py +26 -0
  33. deepfabric/generator.py +1084 -0
  34. deepfabric/graph.py +545 -0
  35. deepfabric/hf_hub.py +214 -0
  36. deepfabric/kaggle_hub.py +219 -0
  37. deepfabric/llm/__init__.py +41 -0
  38. deepfabric/llm/api_key_verifier.py +534 -0
  39. deepfabric/llm/client.py +1206 -0
  40. deepfabric/llm/errors.py +105 -0
  41. deepfabric/llm/rate_limit_config.py +262 -0
  42. deepfabric/llm/rate_limit_detector.py +278 -0
  43. deepfabric/llm/retry_handler.py +270 -0
  44. deepfabric/metrics.py +212 -0
  45. deepfabric/progress.py +262 -0
  46. deepfabric/prompts.py +290 -0
  47. deepfabric/schemas.py +1000 -0
  48. deepfabric/spin/__init__.py +6 -0
  49. deepfabric/spin/client.py +263 -0
  50. deepfabric/spin/models.py +26 -0
  51. deepfabric/stream_simulator.py +90 -0
  52. deepfabric/tools/__init__.py +5 -0
  53. deepfabric/tools/defaults.py +85 -0
  54. deepfabric/tools/loader.py +87 -0
  55. deepfabric/tools/mcp_client.py +677 -0
  56. deepfabric/topic_manager.py +303 -0
  57. deepfabric/topic_model.py +20 -0
  58. deepfabric/training/__init__.py +35 -0
  59. deepfabric/training/api_key_prompt.py +302 -0
  60. deepfabric/training/callback.py +363 -0
  61. deepfabric/training/metrics_sender.py +301 -0
  62. deepfabric/tree.py +438 -0
  63. deepfabric/tui.py +1267 -0
  64. deepfabric/update_checker.py +166 -0
  65. deepfabric/utils.py +150 -0
  66. deepfabric/validation.py +143 -0
  67. deepfabric-4.4.0.dist-info/METADATA +702 -0
  68. deepfabric-4.4.0.dist-info/RECORD +71 -0
  69. deepfabric-4.4.0.dist-info/WHEEL +4 -0
  70. deepfabric-4.4.0.dist-info/entry_points.txt +2 -0
  71. deepfabric-4.4.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,6 @@
1
+ """Spin Framework integration for tool execution."""
2
+
3
+ from .client import SpinClient, SpinSession
4
+ from .models import SpinExecutionResult
5
+
6
+ __all__ = ["SpinClient", "SpinSession", "SpinExecutionResult"]
@@ -0,0 +1,263 @@
1
+ """Spin Framework HTTP client for tool execution."""
2
+
3
+ import json
4
+ import logging
5
+ import uuid
6
+
7
+ from http import HTTPStatus
8
+ from typing import Any
9
+
10
+ import httpx
11
+
12
+ from .models import SpinComponentsResponse, SpinExecutionResult, SpinHealthResponse
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class SpinClient:
18
+ """HTTP client for communicating with Spin service."""
19
+
20
+ def __init__(
21
+ self,
22
+ endpoint: str,
23
+ timeout: float = 30.0,
24
+ tool_execute_path: str | None = None,
25
+ ):
26
+ """Initialize Spin client.
27
+
28
+ Args:
29
+ endpoint: Base URL of Spin service (e.g., "http://localhost:3000")
30
+ timeout: Request timeout in seconds
31
+ tool_execute_path: Custom path for tool execution (e.g., "/mock/execute").
32
+ When set, uses MCP-style payload format.
33
+ """
34
+ self.endpoint = endpoint.rstrip("/")
35
+ self.timeout = timeout
36
+ self.tool_execute_path = tool_execute_path
37
+ self._client: httpx.AsyncClient | None = None
38
+
39
+ async def _ensure_client(self) -> httpx.AsyncClient:
40
+ """Ensure async client is initialized."""
41
+ if self._client is None:
42
+ self._client = httpx.AsyncClient(timeout=self.timeout)
43
+ return self._client
44
+
45
+ async def close(self) -> None:
46
+ """Close the HTTP client."""
47
+ if self._client is not None:
48
+ await self._client.aclose()
49
+ self._client = None
50
+
51
+ async def health_check(self) -> SpinHealthResponse:
52
+ """Check if Spin service is healthy.
53
+
54
+ Returns:
55
+ SpinHealthResponse with status and available components
56
+
57
+ Raises:
58
+ httpx.HTTPError: If service is unreachable
59
+ """
60
+ client = await self._ensure_client()
61
+ response = await client.get(f"{self.endpoint}/health")
62
+ response.raise_for_status()
63
+ return SpinHealthResponse.model_validate(response.json())
64
+
65
+ async def get_components(self) -> list[str]:
66
+ """Get list of available tool components.
67
+
68
+ Returns:
69
+ List of component names
70
+
71
+ Raises:
72
+ httpx.HTTPError: If request fails
73
+ """
74
+ client = await self._ensure_client()
75
+ response = await client.get(f"{self.endpoint}/components")
76
+ response.raise_for_status()
77
+ data = SpinComponentsResponse.model_validate(response.json())
78
+ return data.components
79
+
80
+ async def execute_tool(
81
+ self,
82
+ session_id: str,
83
+ tool_name: str,
84
+ arguments: dict[str, Any],
85
+ component: str | None = None,
86
+ ) -> SpinExecutionResult:
87
+ """Execute a tool via Spin.
88
+
89
+ Args:
90
+ session_id: Session identifier for state isolation
91
+ tool_name: Name of the tool to execute
92
+ arguments: Tool arguments
93
+ component: Spin component that implements this tool (e.g., 'vfs', 'github')
94
+
95
+ Returns:
96
+ SpinExecutionResult with success status and result/error
97
+ """
98
+ client = await self._ensure_client()
99
+
100
+ # Use custom tool_execute_path if configured (MCP/mock style)
101
+ if self.tool_execute_path:
102
+ execute_url = f"{self.endpoint}{self.tool_execute_path}"
103
+ # MCP-style payload format
104
+ payload = {
105
+ "name": tool_name,
106
+ "arguments": arguments,
107
+ }
108
+ else:
109
+ # Standard component-based routing
110
+ if component:
111
+ execute_url = f"{self.endpoint}/{component}/execute"
112
+ else:
113
+ execute_url = f"{self.endpoint}/execute"
114
+ # Standard payload format
115
+ payload = {
116
+ "session_id": session_id,
117
+ "tool": tool_name,
118
+ "args": arguments,
119
+ }
120
+
121
+ try:
122
+ response = await client.post(
123
+ execute_url,
124
+ json=payload,
125
+ )
126
+
127
+ if response.status_code == HTTPStatus.OK:
128
+ data = response.json()
129
+
130
+ # Handle MCP/mock response format (has "result" key with nested data)
131
+ if self.tool_execute_path and "result" in data:
132
+ # Mock component returns {"result": {...}}
133
+
134
+ return SpinExecutionResult(
135
+ success=True,
136
+ result=json.dumps(data["result"]),
137
+ error_type=None,
138
+ )
139
+
140
+ # Standard Spin component response format
141
+ return SpinExecutionResult(
142
+ success=data.get("success", False),
143
+ result=data.get("result", ""),
144
+ error_type=data.get("error_type"),
145
+ )
146
+
147
+ error_data = response.json() if response.content else {}
148
+ return SpinExecutionResult(
149
+ success=False,
150
+ result=error_data.get("error", f"HTTP {response.status_code}"),
151
+ error_type="HTTPError",
152
+ )
153
+
154
+ except httpx.TimeoutException:
155
+ return SpinExecutionResult(
156
+ success=False,
157
+ result="Tool execution timed out",
158
+ error_type="Timeout",
159
+ )
160
+ except httpx.HTTPError as e:
161
+ return SpinExecutionResult(
162
+ success=False,
163
+ result=f"HTTP error: {e!s}",
164
+ error_type="HTTPError",
165
+ )
166
+
167
+ async def cleanup_session(self, session_id: str) -> bool:
168
+ """Clean up all state for a session.
169
+
170
+ Args:
171
+ session_id: Session to clean up
172
+
173
+ Returns:
174
+ True if cleanup succeeded
175
+ """
176
+ client = await self._ensure_client()
177
+
178
+ try:
179
+ response = await client.delete(f"{self.endpoint}/session/{session_id}")
180
+ except httpx.HTTPError as e:
181
+ logger.warning("Failed to cleanup session %s: %s", session_id, e)
182
+ return False
183
+ else:
184
+ return response.status_code == HTTPStatus.OK
185
+
186
+
187
+ class SpinSession:
188
+ """Manages a Spin session with state persistence across tool calls."""
189
+
190
+ def __init__(self, client: SpinClient, session_id: str | None = None):
191
+ """Initialize a Spin session.
192
+
193
+ Args:
194
+ client: SpinClient instance
195
+ session_id: Optional session ID (generates UUID if not provided)
196
+ """
197
+ self.client = client
198
+ self.session_id = session_id or str(uuid.uuid4())
199
+ self._initialized = False
200
+
201
+ async def execute_tool(
202
+ self,
203
+ tool_name: str,
204
+ arguments: dict[str, Any],
205
+ component: str | None = None,
206
+ ) -> SpinExecutionResult:
207
+ """Execute a tool in this session.
208
+
209
+ Args:
210
+ tool_name: Name of the tool
211
+ arguments: Tool arguments
212
+ component: Spin component that implements this tool (e.g., 'vfs', 'github')
213
+
214
+ Returns:
215
+ SpinExecutionResult with execution outcome
216
+ """
217
+ return await self.client.execute_tool(
218
+ session_id=self.session_id,
219
+ tool_name=tool_name,
220
+ arguments=arguments,
221
+ component=component,
222
+ )
223
+
224
+ async def seed_files(self, files: dict[str, str]) -> bool:
225
+ """Seed initial files into the session's virtual filesystem.
226
+
227
+ Args:
228
+ files: Dictionary of file_path -> content
229
+
230
+ Returns:
231
+ True if all files were seeded successfully
232
+ """
233
+ for file_path, content in files.items():
234
+ result = await self.execute_tool(
235
+ tool_name="write_file",
236
+ arguments={"file_path": file_path, "content": content},
237
+ component="vfs", # Builtin VFS tool
238
+ )
239
+ if not result.success:
240
+ logger.error("Failed to seed file %s: %s", file_path, result.result)
241
+ return False
242
+
243
+ self._initialized = True
244
+ logger.debug("Seeded %d files for session %s", len(files), self.session_id)
245
+ return True
246
+
247
+ async def cleanup(self) -> bool:
248
+ """Clean up this session's state.
249
+
250
+ Returns:
251
+ True if cleanup succeeded
252
+ """
253
+ result = await self.client.cleanup_session(self.session_id)
254
+ self._initialized = False
255
+ return result
256
+
257
+ async def __aenter__(self) -> "SpinSession":
258
+ """Async context manager entry."""
259
+ return self
260
+
261
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
262
+ """Async context manager exit - cleanup session."""
263
+ await self.cleanup()
@@ -0,0 +1,26 @@
1
+ """Data models for Spin integration."""
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class SpinExecutionResult(BaseModel):
7
+ """Result from Spin tool execution."""
8
+
9
+ success: bool = Field(description="Whether execution succeeded")
10
+ result: str = Field(description="Tool output or error message")
11
+ error_type: str | None = Field(
12
+ default=None, description="Error type if failed (e.g., 'FileNotFound', 'Timeout')"
13
+ )
14
+
15
+
16
+ class SpinHealthResponse(BaseModel):
17
+ """Response from Spin health check."""
18
+
19
+ status: str = Field(description="Service status")
20
+ components: list[str] = Field(default_factory=list, description="Available components")
21
+
22
+
23
+ class SpinComponentsResponse(BaseModel):
24
+ """Response from Spin components listing."""
25
+
26
+ components: list[str] = Field(default_factory=list, description="Available component names")
@@ -0,0 +1,90 @@
1
+ """Buffered stream simulation for TUI preview.
2
+
3
+ This module provides a fire-and-forget streaming simulation that emits chunks
4
+ to the TUI preview without impacting generation performance. The simulation
5
+ runs in background while generation continues immediately.
6
+ """
7
+
8
+ import asyncio
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+ from .constants import STREAM_SIM_CHUNK_DELAY_MS, STREAM_SIM_CHUNK_SIZE
15
+
16
+ if TYPE_CHECKING:
17
+ from .progress import ProgressReporter
18
+
19
+
20
+ # Track current stream task to prevent interleaving
21
+ class _StreamState:
22
+ current_task: asyncio.Task | None = None
23
+
24
+
25
+ _stream_state = _StreamState()
26
+
27
+
28
+ class StreamSimulatorConfig(BaseModel):
29
+ """Configuration for buffered stream simulation."""
30
+
31
+ chunk_size: int = Field(default=STREAM_SIM_CHUNK_SIZE, ge=1, le=100)
32
+ chunk_delay_ms: float = Field(default=STREAM_SIM_CHUNK_DELAY_MS, ge=0.0, le=100.0)
33
+ enabled: bool = Field(default=True)
34
+
35
+
36
+ def simulate_stream(
37
+ progress_reporter: "ProgressReporter | None",
38
+ content: str,
39
+ source: str,
40
+ config: StreamSimulatorConfig | None = None,
41
+ **metadata,
42
+ ) -> asyncio.Task | None:
43
+ """Fire-and-forget stream simulation (non-blocking).
44
+
45
+ Starts simulation in background and returns immediately. This is the
46
+ primary interface for stream simulation throughout the codebase.
47
+
48
+ Cancels any in-flight stream task before starting a new one to prevent
49
+ interleaved chunks from multiple generations appearing scrambled in the TUI.
50
+
51
+ Args:
52
+ progress_reporter: ProgressReporter instance or None
53
+ content: Text to simulate streaming
54
+ source: Source identifier for TUI routing
55
+ config: Optional StreamSimulatorConfig override
56
+ **metadata: Additional metadata passed to emit_chunk
57
+
58
+ Returns:
59
+ Task if started, None if no-op (no reporter or disabled)
60
+ """
61
+ _config = config or StreamSimulatorConfig()
62
+ if not progress_reporter or not _config.enabled:
63
+ return None
64
+
65
+ # Cancel any in-flight stream to prevent interleaving
66
+ if _stream_state.current_task is not None and not _stream_state.current_task.done():
67
+ _stream_state.current_task.cancel()
68
+
69
+ async def _simulate_impl() -> None:
70
+ """Internal implementation of chunk emission."""
71
+ if not content:
72
+ return
73
+
74
+ delay = _config.chunk_delay_ms / 1000.0
75
+ chunk_size = _config.chunk_size
76
+
77
+ try:
78
+ for i in range(0, len(content), chunk_size):
79
+ # Await before emitting to ensure cancellation is processed
80
+ # and event loop is not blocked when delay is 0
81
+ if i > 0:
82
+ await asyncio.sleep(delay)
83
+ chunk = content[i : i + chunk_size]
84
+ progress_reporter.emit_chunk(source, chunk, **metadata)
85
+ except asyncio.CancelledError:
86
+ # Gracefully handle cancellation
87
+ pass
88
+
89
+ _stream_state.current_task = asyncio.create_task(_simulate_impl())
90
+ return _stream_state.current_task
@@ -0,0 +1,5 @@
1
+ """Tool definitions and utilities for agent-based dataset generation."""
2
+
3
+ from .defaults import BUILTIN_TOOL_REGISTRY
4
+
5
+ __all__ = ["BUILTIN_TOOL_REGISTRY"]
@@ -0,0 +1,85 @@
1
+ """Default tool definitions for common use cases."""
2
+
3
+ from ..schemas import ToolDefinition, ToolParameter, ToolRegistry
4
+
5
+ # =============================================================================
6
+ # Builtin Tools - Execute via Spin VFS component
7
+ # These are the only hardcoded tools. Custom components (github, slack, etc.)
8
+ # should be configured via YAML config with tools loaded from endpoints.
9
+ # =============================================================================
10
+
11
+ # Component mapping: "builtin" in config routes to "/vfs/execute" in Spin
12
+ BUILTIN_COMPONENT = "vfs"
13
+
14
+ READ_FILE_TOOL = ToolDefinition(
15
+ name="read_file",
16
+ description="Read content from a file",
17
+ parameters=[
18
+ ToolParameter(
19
+ name="file_path",
20
+ type="str",
21
+ description="Path to the file to read",
22
+ required=True,
23
+ ),
24
+ ],
25
+ returns="File content as a string",
26
+ category="filesystem",
27
+ component=BUILTIN_COMPONENT,
28
+ )
29
+
30
+ WRITE_FILE_TOOL = ToolDefinition(
31
+ name="write_file",
32
+ description="Write content to a file",
33
+ parameters=[
34
+ ToolParameter(
35
+ name="file_path",
36
+ type="str",
37
+ description="Path to the file to write",
38
+ required=True,
39
+ ),
40
+ ToolParameter(
41
+ name="content",
42
+ type="str",
43
+ description="Content to write to the file",
44
+ required=True,
45
+ ),
46
+ ],
47
+ returns="Confirmation message with bytes written",
48
+ category="filesystem",
49
+ component=BUILTIN_COMPONENT,
50
+ )
51
+
52
+ LIST_FILES_TOOL = ToolDefinition(
53
+ name="list_files",
54
+ description="List all files in the current session",
55
+ parameters=[],
56
+ returns="JSON array of file paths",
57
+ category="filesystem",
58
+ component=BUILTIN_COMPONENT,
59
+ )
60
+
61
+ DELETE_FILE_TOOL = ToolDefinition(
62
+ name="delete_file",
63
+ description="Delete a file",
64
+ parameters=[
65
+ ToolParameter(
66
+ name="file_path",
67
+ type="str",
68
+ description="Path to the file to delete",
69
+ required=True,
70
+ ),
71
+ ],
72
+ returns="Confirmation that file was deleted",
73
+ category="filesystem",
74
+ component=BUILTIN_COMPONENT,
75
+ )
76
+
77
+ # Builtin tools registry
78
+ BUILTIN_TOOL_REGISTRY = ToolRegistry(
79
+ tools=[
80
+ READ_FILE_TOOL,
81
+ WRITE_FILE_TOOL,
82
+ LIST_FILES_TOOL,
83
+ DELETE_FILE_TOOL,
84
+ ]
85
+ )
@@ -0,0 +1,87 @@
1
+ """Tool loading and management utilities."""
2
+
3
+ import json
4
+ import logging
5
+
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from pydantic import ValidationError
11
+
12
+ from ..exceptions import ConfigurationError
13
+ from ..schemas import MCPToolDefinition, ToolDefinition, ToolRegistry
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def load_tools_from_endpoint(endpoint_url: str, timeout: float = 30.0) -> ToolRegistry:
19
+ """Load tool definitions from an HTTP endpoint in MCP format.
20
+
21
+ Fetches tools from an endpoint like /mock/list-tools that returns
22
+ MCP-format tool definitions with inputSchema.
23
+
24
+ Args:
25
+ endpoint_url: Full URL to fetch tools from (e.g., 'http://localhost:3000/mock/list-tools')
26
+ timeout: Request timeout in seconds
27
+
28
+ Returns:
29
+ ToolRegistry with loaded tools converted from MCP format
30
+
31
+ Raises:
32
+ ConfigurationError: If endpoint cannot be reached or returns invalid data
33
+ """
34
+ try:
35
+ response = httpx.get(endpoint_url, timeout=timeout)
36
+ response.raise_for_status()
37
+ data = response.json()
38
+ except httpx.RequestError as e:
39
+ raise ConfigurationError(f"Failed to connect to tools endpoint {endpoint_url}: {e}") from e
40
+ except httpx.HTTPStatusError as e:
41
+ raise ConfigurationError(
42
+ f"Tools endpoint returned error {e.response.status_code}: {e.response.text}"
43
+ ) from e
44
+ except json.JSONDecodeError as e:
45
+ raise ConfigurationError(f"Invalid JSON from tools endpoint: {e}") from e
46
+
47
+ # Extract tools array - handle both {"tools": [...]} and direct array
48
+ if isinstance(data, dict) and "tools" in data:
49
+ tools_data = data["tools"]
50
+ elif isinstance(data, list):
51
+ tools_data = data
52
+ else:
53
+ raise ConfigurationError(
54
+ f"Invalid response from {endpoint_url}: expected 'tools' key or array"
55
+ )
56
+
57
+ # Convert MCP tools to ToolDefinition
58
+ try:
59
+ tools = []
60
+ for tool_dict in tools_data:
61
+ mcp_tool = MCPToolDefinition.model_validate(tool_dict)
62
+ tools.append(ToolDefinition.from_mcp(mcp_tool))
63
+
64
+ logger.info("Loaded %d tools from endpoint %s", len(tools), endpoint_url)
65
+ return ToolRegistry(tools=tools)
66
+
67
+ except ValidationError as e:
68
+ raise ConfigurationError(f"Invalid MCP tool schema from {endpoint_url}: {e}") from e
69
+
70
+
71
+ def load_tools_from_dict(tool_dicts: list[dict[str, Any]]) -> ToolRegistry:
72
+ """Load tool definitions from a list of dictionaries.
73
+
74
+ Args:
75
+ tool_dicts: List of tool definition dictionaries
76
+
77
+ Returns:
78
+ ToolRegistry with loaded tools
79
+
80
+ Raises:
81
+ ConfigurationError: If tool definitions are invalid
82
+ """
83
+ try:
84
+ tools = [ToolDefinition.model_validate(tool_dict) for tool_dict in tool_dicts]
85
+ return ToolRegistry(tools=tools)
86
+ except Exception as e:
87
+ raise ConfigurationError(f"Invalid tool definitions: {str(e)}") from e