wafer-core 0.1.24__py3-none-any.whl → 0.1.25__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.
@@ -34,7 +34,6 @@ from wafer_core.tools import (
34
34
  GLOB_TOOL,
35
35
  GREP_TOOL,
36
36
  READ_TOOL,
37
- SEARCH_DOCS_TOOL,
38
37
  SKILL_TOOL,
39
38
  WRITE_TOOL,
40
39
  ApprovalCallback,
@@ -43,7 +42,6 @@ from wafer_core.tools import (
43
42
  exec_glob,
44
43
  exec_grep,
45
44
  exec_read,
46
- exec_search_docs,
47
45
  exec_skill,
48
46
  exec_write,
49
47
  )
@@ -65,7 +63,6 @@ ALL_TOOLS = {
65
63
  "glob": GLOB_TOOL,
66
64
  "grep": GREP_TOOL,
67
65
  "bash": BASH_TOOL,
68
- "search_docs": SEARCH_DOCS_TOOL,
69
66
  "skill": SKILL_TOOL,
70
67
  # TODO(wafer-tool): "wafer": WAFER_TOOL,
71
68
  }
@@ -214,7 +211,6 @@ class CodingEnvironment:
214
211
  self.bash_approval_callback,
215
212
  self._sandbox_policy,
216
213
  ),
217
- "search_docs": lambda tc: exec_search_docs(tc),
218
214
  "skill": lambda tc: exec_skill(tc),
219
215
  # TODO(wafer-tool): "wafer": lambda tc: exec_wafer(
220
216
  # tc, self.working_dir, self.enabled_tools, self.allow_spawn, cancel_scope
@@ -7,10 +7,13 @@ import logging
7
7
  import os
8
8
  from pathlib import Path
9
9
 
10
+ import httpx
11
+
10
12
  logger = logging.getLogger(__name__)
11
13
 
12
14
  SUPABASE_URL = "https://hvlpthcnxlywlquiciqe.supabase.co"
13
15
  BUCKET_NAME = "traces"
16
+ API_BASE = os.environ.get("WAFER_API_URL", "https://api.wafer.ai")
14
17
 
15
18
 
16
19
  def upload_results_to_supabase(output_dir: Path, log: logging.Logger | None = None) -> bool:
@@ -95,6 +98,12 @@ def upload_results_to_supabase(output_dir: Path, log: logging.Logger | None = No
95
98
  )
96
99
 
97
100
  log.info(f"Uploaded {len(uploaded)} files to Supabase: {run_name}")
101
+
102
+ # Auto-index in database for trace viewer
103
+ # Fail if indexing fails - user can re-run (everything is idempotent)
104
+ if not _index_run_in_database(run_name, report_path, log):
105
+ return False
106
+
98
107
  return True
99
108
 
100
109
  except ImportError:
@@ -103,3 +112,39 @@ def upload_results_to_supabase(output_dir: Path, log: logging.Logger | None = No
103
112
  except Exception as e:
104
113
  log.error(f"Failed to upload to Supabase: {e}")
105
114
  return False
115
+
116
+
117
+ def _index_run_in_database(run_name: str, report_path: Path, log: logging.Logger) -> bool:
118
+ """Index a run in the trace_runs database table for fast querying.
119
+
120
+ Calls POST /v1/eval-traces/runs to upsert the run metadata.
121
+ This enables the trace viewer to show the run immediately without manual sync.
122
+
123
+ Args:
124
+ run_name: Name of the run (folder name)
125
+ report_path: Path to the report.json file
126
+ log: Logger instance
127
+
128
+ Returns:
129
+ True if indexing succeeded, False otherwise
130
+ """
131
+ try:
132
+ with open(report_path) as f:
133
+ report = json.load(f)
134
+
135
+ response = httpx.post(
136
+ f"{API_BASE}/v1/eval-traces/runs",
137
+ json={"name": run_name, "report": report},
138
+ timeout=30.0,
139
+ )
140
+
141
+ if response.status_code == 200:
142
+ log.info(f"Indexed run in database: {run_name}")
143
+ return True
144
+ else:
145
+ log.error(f"Failed to index run {run_name}: {response.status_code} {response.text}")
146
+ return False
147
+
148
+ except Exception as e:
149
+ log.error(f"Failed to index run {run_name} in database: {e}")
150
+ return False
@@ -72,10 +72,6 @@ from wafer_core.tools.write_kernel_tool import (
72
72
  KernelSubmission,
73
73
  exec_write_kernel,
74
74
  )
75
- from wafer_core.tools.search_docs_tool import (
76
- SEARCH_DOCS_TOOL,
77
- exec_search_docs,
78
- )
79
75
 
80
76
  __all__ = [
81
77
  # File tools
@@ -137,7 +133,4 @@ __all__ = [
137
133
  "exec_tracelens_report",
138
134
  "exec_tracelens_compare",
139
135
  "exec_tracelens_collective",
140
- # Search docs tool
141
- "SEARCH_DOCS_TOOL",
142
- "exec_search_docs",
143
136
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wafer-core
3
- Version: 0.1.24
3
+ Version: 0.1.25
4
4
  Summary: Core utilities and environments for Wafer GPU kernel optimization
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: aiohttp>=3.9.0
@@ -12,7 +12,7 @@ wafer_core/config/__init__.py,sha256=hKywfjA4YXd4lBeBFEcBoMwFoflPHJTiBnkTq7_JYOQ
12
12
  wafer_core/config/loader.py,sha256=k7JnILmO13TWUzIv9Lm8fvmj3UfYHZDgaFurjQ-GXpY,6623
13
13
  wafer_core/config/schema.py,sha256=2WhFlnG0VYYX4T-70BLeJK8Janvi4KEa8KKGZA7331w,3898
14
14
  wafer_core/environments/__init__.py,sha256=SIsResVtm22tr_d-oHPeeSxrkhFdmPOFico3DqtRqK8,238
15
- wafer_core/environments/coding.py,sha256=N-ELZwJu5vKLCVtwO25c6JSty6fmqf85VR2d3WJ4RXw,8559
15
+ wafer_core/environments/coding.py,sha256=T-_JFU-n5OxPR8xAWp8qar4Y5xyC-TWTIBjRy4PDel8,8418
16
16
  wafer_core/environments/gpumode.py,sha256=8Da08nltvN_YloNyYI6-omN2D4n5C7aptKDCtUgT2bQ,17191
17
17
  wafer_core/lib/__init__.py,sha256=4-4p3mhwlquejWGglYXU8_nHdA0LoPaa_jGzcm13USA,1325
18
18
  wafer_core/lib/kernel_scope/__init__.py,sha256=WW2vu8jUlqOu-MCpgO40lIYacCA9N2u-uuECIs_JO2w,2817
@@ -359,7 +359,7 @@ wafer_core/rollouts/skills.py,sha256=ATYoG02Cc6_VrtE415TnseBFJrKOMq27z-5YgBgPpZQ
359
359
  wafer_core/rollouts/slice.py,sha256=darOZO53BuSPfvv_KjOSzulGVSWbL4OuoE3k6xXpBFg,20195
360
360
  wafer_core/rollouts/store.py,sha256=UDP9idDOEVs_0Pslx0K_Y8E1i-BeoqVSaxdQiaqtz1E,18051
361
361
  wafer_core/rollouts/transform_messages.py,sha256=yldzdLgugNYb5Zxju7myFBel1tmrHXx9M399ImqPLGI,20891
362
- wafer_core/rollouts/upload.py,sha256=dR0W6QREQWIn7F0j95K7ut6uQ5phS95ijnYJK0J24bo,3432
362
+ wafer_core/rollouts/upload.py,sha256=hEqZfwgb0b4GYbrwRSA3fuqF70pqo6hyaQU59j3vM7E,4890
363
363
  wafer_core/rollouts/_logging/__init__.py,sha256=rCXeAssQ3gIrduuMzvKPD-ikt6rXejVL9h5XtDRyIQg,498
364
364
  wafer_core/rollouts/_logging/color_formatter.py,sha256=x3qRKwHsUCFkgcIl8x_Ajjw82X2EedbTe14sCxMU4Kc,2267
365
365
  wafer_core/rollouts/_logging/json_formatter.py,sha256=jJIa2IZCsu2C_Y1HXQi7hbI33x6L6shN_dqu-hmhxp4,2380
@@ -586,11 +586,10 @@ wafer_core/sessions/hooks.py,sha256=A-txm6ufnRGQCdtP3vwh7oEOdlLN9Tv0XsjORMihuAI,
586
586
  wafer_core/targets/__init__.py,sha256=sHndC7AAOaHXlrmDXFLB53a5Y8DBjuyqS6nwsO2nj-Y,1728
587
587
  wafer_core/targets/digitalocean.py,sha256=cvoYpYjtSyy5t2lQAPi7ERruuuibronah_ivOiduAHQ,16550
588
588
  wafer_core/targets/runpod.py,sha256=LrVmNvA6qjzL5nbGSWvtw7CHrK6bDu7_o3vKIek00Tc,20286
589
- wafer_core/tools/__init__.py,sha256=deGQQlcdSD6zQx8JHizfSXgF5-EntdBOF_ngtob1-VU,3506
589
+ wafer_core/tools/__init__.py,sha256=wBQD45GdSfkxcT6NHzIv0IMeXCc0enwwkpm3T_9j1X8,3341
590
590
  wafer_core/tools/bash_tool.py,sha256=daoKOVGSgL0x9X_3l8Apd6-wFH4VMXMGJwVemw2FIfc,16828
591
591
  wafer_core/tools/glob_tool.py,sha256=9X5PdOjQJj7kiVNqqCZC0-1LmnE6wHx3Zc9zfMjtXdc,3533
592
592
  wafer_core/tools/grep_tool.py,sha256=cStyDz-J47oDLLZCL83yOvYo8Ijv4qu3D372JKT_ptM,4580
593
- wafer_core/tools/search_docs_tool.py,sha256=WY4hY83sseX8Fpxvw6DZxiG-F95F2t3-4PyfMD1Lpkg,6809
594
593
  wafer_core/tools/skill_tool.py,sha256=JXsT5hBTUH5U4tmzHEywU7eHHt5xCEF79tL2tsuk4-c,2067
595
594
  wafer_core/tools/wafer_tool.py,sha256=-dgPTHbWXq3I3wFj0mP7-lj5iZqGRoFvFf9IEEo3plQ,6345
596
595
  wafer_core/tools/write_kernel_tool.py,sha256=dJjhr-WBhVNe06hcJQVmBZTbS8mid64KF1MwlE2s2R4,21547
@@ -674,6 +673,6 @@ wafer_core/utils/modal_execution/modal_app.py,sha256=VfS2cX8gHtnlPXemmMcEwDPeQdh
674
673
  wafer_core/utils/modal_execution/modal_config.py,sha256=7cGX9TGqilQ3qxI3OFGXV5orjtyRU-PEDOJ4vP2oxno,4421
675
674
  wafer_core/utils/modal_execution/modal_execution.py,sha256=gChjnV6jqA3A7IRP3DfvV5cSfm_MN0X4f7JZufXgdZE,24594
676
675
  wafer_core/utils/modal_execution/test_modal.py,sha256=_jqou_hrLs1Daf1590Pnb0a_lXMMa2rczAPpW9HpoNQ,8153
677
- wafer_core-0.1.24.dist-info/METADATA,sha256=h2zO5zgoRFyd1aZbWSugm8JWl8RzYYd9w5h0CDQ2pa4,1420
678
- wafer_core-0.1.24.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
679
- wafer_core-0.1.24.dist-info/RECORD,,
676
+ wafer_core-0.1.25.dist-info/METADATA,sha256=hNv2xTrSbhdaA1G8mBmLxe1HbYRE0NLX3C6w89sht3k,1420
677
+ wafer_core-0.1.25.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
678
+ wafer_core-0.1.25.dist-info/RECORD,,
@@ -1,196 +0,0 @@
1
- """Search documentation tool for GPU programming corpora.
2
-
3
- Provides semantic and keyword search over documentation for CuTeDSL, CUDA, etc.
4
-
5
- Corpora are downloaded via `wafer corpus download <name>` and stored in ~/.cache/wafer/corpora/.
6
- """
7
-
8
- import re
9
- from pathlib import Path
10
-
11
- from wafer_core.rollouts.dtypes import Tool, ToolCall, ToolFunction, ToolFunctionParameter, ToolResult
12
-
13
- # Cache directory where wafer corpus download stores files
14
- CACHE_DIR = Path.home() / ".cache" / "wafer" / "corpora"
15
-
16
- # Available corpora (names match wafer corpus download)
17
- AVAILABLE_CORPORA = ["cutlass", "cutedsl", "cuda", "hip", "amd"]
18
-
19
- SEARCH_DOCS_TOOL = Tool(
20
- type="function",
21
- function=ToolFunction(
22
- name="search_docs",
23
- description="""Search GPU programming documentation for relevant information.
24
-
25
- Use this tool to find documentation about:
26
- - CUTLASS C++ (cute:: namespace, gemm tutorials, tensor cores, TMA, Blackwell)
27
- - CuTeDSL Python API (@cute.kernel, @cute.jit, cute.arch functions)
28
- - CUDA programming concepts
29
- - GPU kernel optimization techniques
30
- - Code examples and patterns
31
-
32
- Available corpora:
33
- - 'cutlass' - NVIDIA CUTLASS C++ docs + GitHub examples (gemm, hopper, blackwell)
34
- - 'cutedsl' - CuTeDSL Python documentation
35
- - 'cuda' - General CUDA programming docs
36
- - 'hip' - AMD HIP programming docs
37
- - 'amd' - AMD GPU kernel development (rocWMMA, CK, etc.)
38
-
39
- Note: Corpora must be downloaded first with `wafer corpus download <name>`.
40
- Returns relevant documentation snippets with file paths.""",
41
- parameters=ToolFunctionParameter(
42
- type="object",
43
- properties={
44
- "query": {
45
- "type": "string",
46
- "description": "Search query - describe what you're looking for",
47
- },
48
- "corpus": {
49
- "type": "string",
50
- "description": "Which docs to search: 'cutlass', 'cutedsl', 'cuda', 'hip', 'amd' (default: cutlass)",
51
- },
52
- "max_results": {
53
- "type": "integer",
54
- "description": "Maximum number of results to return (default: 5)",
55
- },
56
- },
57
- ),
58
- required=["query"],
59
- )
60
- )
61
-
62
-
63
- def _get_corpus_path(corpus_name: str) -> Path | None:
64
- """Get the path to a corpus in the cache directory.
65
-
66
- Corpora are stored at ~/.cache/wafer/corpora/<corpus_name>/
67
- """
68
- if corpus_name not in AVAILABLE_CORPORA:
69
- return None
70
-
71
- corpus_path = CACHE_DIR / corpus_name
72
- if corpus_path.exists():
73
- return corpus_path
74
-
75
- return None
76
-
77
-
78
- def _search_files(corpus_path: Path, query: str, max_results: int = 5) -> list[dict]:
79
- """Simple keyword search through documentation files."""
80
- results = []
81
- query_terms = query.lower().split()
82
-
83
- # Search .md, .py, .cu, .hpp, and .h files (for CUTLASS examples)
84
- for pattern in ["**/*.md", "**/*.py", "**/*.cu", "**/*.hpp", "**/*.h", "**/*.cuh"]:
85
- for file_path in corpus_path.glob(pattern):
86
- if file_path.is_file():
87
- try:
88
- content = file_path.read_text(encoding="utf-8", errors="ignore")
89
- content_lower = content.lower()
90
-
91
- # Score based on term matches
92
- score = sum(content_lower.count(term) for term in query_terms)
93
-
94
- if score > 0:
95
- # Extract relevant snippets
96
- snippets = _extract_snippets(content, query_terms)
97
- results.append({
98
- "file": str(file_path), # Return absolute path so read tool can access it
99
- "score": score,
100
- "snippets": snippets[:3], # Top 3 snippets
101
- })
102
- except Exception:
103
- continue
104
-
105
- # Sort by score and return top results
106
- results.sort(key=lambda x: x["score"], reverse=True)
107
- return results[:max_results]
108
-
109
-
110
- def _extract_snippets(content: str, terms: list[str], context_lines: int = 5) -> list[str]:
111
- """Extract snippets containing search terms."""
112
- snippets = []
113
- lines = content.split("\n")
114
-
115
- for i, line in enumerate(lines):
116
- line_lower = line.lower()
117
- if any(term in line_lower for term in terms):
118
- # Get context around the match
119
- start = max(0, i - context_lines)
120
- end = min(len(lines), i + context_lines + 1)
121
- snippet = "\n".join(lines[start:end])
122
-
123
- # Skip very short snippets
124
- if len(snippet.strip()) > 50:
125
- snippets.append(snippet)
126
-
127
- return snippets
128
-
129
-
130
- async def exec_search_docs(
131
- tool_call: ToolCall,
132
- corpus_override: str | None = None,
133
- ) -> ToolResult:
134
- """Execute search_docs tool.
135
-
136
- Args:
137
- tool_call: The tool call with query and optional corpus
138
- corpus_override: Override corpus path (for testing)
139
- """
140
- query = tool_call.args.get("query", "")
141
- corpus_name = tool_call.args.get("corpus", "cutlass")
142
- max_results = tool_call.args.get("max_results", 5)
143
-
144
- if not query:
145
- return ToolResult(
146
- tool_call_id=tool_call.id,
147
- content="",
148
- error="query parameter is required",
149
- )
150
-
151
- # Find corpus path
152
- if corpus_override:
153
- corpus_path = Path(corpus_override)
154
- else:
155
- corpus_path = _get_corpus_path(corpus_name)
156
- if corpus_path is None:
157
- return ToolResult(
158
- tool_call_id=tool_call.id,
159
- content="",
160
- error=f"Unknown corpus: {corpus_name}. Available: {AVAILABLE_CORPORA}",
161
- )
162
-
163
- if not corpus_path.exists():
164
- return ToolResult(
165
- tool_call_id=tool_call.id,
166
- content="",
167
- error=f"Corpus '{corpus_name}' not downloaded. Run: wafer corpus download {corpus_name}",
168
- )
169
-
170
- # Search
171
- results = _search_files(corpus_path, query, max_results)
172
-
173
- if not results:
174
- return ToolResult(
175
- tool_call_id=tool_call.id,
176
- content=f"No results found for query: {query}",
177
- error=None,
178
- )
179
-
180
- # Format output
181
- output_parts = [f"Found {len(results)} results for: {query}\n"]
182
-
183
- for i, result in enumerate(results, 1):
184
- output_parts.append(f"\n{'='*60}")
185
- output_parts.append(f"[{i}] {result['file']} (score: {result['score']})")
186
- output_parts.append("=" * 60)
187
-
188
- for snippet in result["snippets"]:
189
- output_parts.append(snippet)
190
- output_parts.append("-" * 40)
191
-
192
- return ToolResult(
193
- tool_call_id=tool_call.id,
194
- content="\n".join(output_parts),
195
- error=None,
196
- )