EvoScientist 0.0.1.dev4__py3-none-any.whl → 0.1.0rc2__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 (37) hide show
  1. EvoScientist/EvoScientist.py +25 -61
  2. EvoScientist/__init__.py +0 -19
  3. EvoScientist/backends.py +0 -26
  4. EvoScientist/cli.py +1365 -480
  5. EvoScientist/middleware.py +7 -56
  6. EvoScientist/skills/clip/SKILL.md +253 -0
  7. EvoScientist/skills/clip/references/applications.md +207 -0
  8. EvoScientist/skills/langgraph-docs/SKILL.md +36 -0
  9. EvoScientist/skills/tensorboard/SKILL.md +629 -0
  10. EvoScientist/skills/tensorboard/references/integrations.md +638 -0
  11. EvoScientist/skills/tensorboard/references/profiling.md +545 -0
  12. EvoScientist/skills/tensorboard/references/visualization.md +620 -0
  13. EvoScientist/skills/vllm/SKILL.md +364 -0
  14. EvoScientist/skills/vllm/references/optimization.md +226 -0
  15. EvoScientist/skills/vllm/references/quantization.md +284 -0
  16. EvoScientist/skills/vllm/references/server-deployment.md +255 -0
  17. EvoScientist/skills/vllm/references/troubleshooting.md +447 -0
  18. EvoScientist/stream/__init__.py +0 -25
  19. EvoScientist/stream/utils.py +16 -23
  20. EvoScientist/tools.py +2 -75
  21. {evoscientist-0.0.1.dev4.dist-info → evoscientist-0.1.0rc2.dist-info}/METADATA +8 -153
  22. {evoscientist-0.0.1.dev4.dist-info → evoscientist-0.1.0rc2.dist-info}/RECORD +26 -24
  23. evoscientist-0.1.0rc2.dist-info/entry_points.txt +2 -0
  24. EvoScientist/config.py +0 -274
  25. EvoScientist/llm/__init__.py +0 -21
  26. EvoScientist/llm/models.py +0 -99
  27. EvoScientist/memory.py +0 -715
  28. EvoScientist/onboard.py +0 -725
  29. EvoScientist/paths.py +0 -44
  30. EvoScientist/skills_manager.py +0 -391
  31. EvoScientist/stream/display.py +0 -604
  32. EvoScientist/stream/events.py +0 -415
  33. EvoScientist/stream/state.py +0 -343
  34. evoscientist-0.0.1.dev4.dist-info/entry_points.txt +0 -5
  35. {evoscientist-0.0.1.dev4.dist-info → evoscientist-0.1.0rc2.dist-info}/WHEEL +0 -0
  36. {evoscientist-0.0.1.dev4.dist-info → evoscientist-0.1.0rc2.dist-info}/licenses/LICENSE +0 -0
  37. {evoscientist-0.0.1.dev4.dist-info → evoscientist-0.1.0rc2.dist-info}/top_level.txt +0 -0
@@ -1,343 +0,0 @@
1
- """Stream state tracking for CLI display.
2
-
3
- Contains SubAgentState, StreamState, and todo-item parsing helpers.
4
- No Rich dependencies — stdlib only.
5
- """
6
-
7
- import ast
8
- import json
9
-
10
-
11
- class SubAgentState:
12
- """Tracks a single sub-agent's activity."""
13
-
14
- def __init__(self, name: str, description: str = ""):
15
- self.name = name
16
- self.description = description
17
- self.tool_calls: list[dict] = []
18
- self.tool_results: list[dict] = []
19
- self._result_map: dict[str, dict] = {} # tool_call_id -> result
20
- self.is_active = True
21
-
22
- def add_tool_call(self, name: str, args: dict, tool_id: str = ""):
23
- # Skip empty-name calls without an id (incomplete streaming chunks)
24
- if not name and not tool_id:
25
- return
26
- tc_data = {"id": tool_id, "name": name, "args": args}
27
- if tool_id:
28
- for i, tc in enumerate(self.tool_calls):
29
- if tc.get("id") == tool_id:
30
- # Merge: keep the non-empty name/args
31
- if name:
32
- self.tool_calls[i]["name"] = name
33
- if args:
34
- self.tool_calls[i]["args"] = args
35
- return
36
- # Skip if name is empty and we can't deduplicate by id
37
- if not name:
38
- return
39
- self.tool_calls.append(tc_data)
40
-
41
- def add_tool_result(self, name: str, content: str, success: bool = True):
42
- result = {"name": name, "content": content, "success": success}
43
- self.tool_results.append(result)
44
- # Try to match result to the first unmatched tool call with same name
45
- for tc in self.tool_calls:
46
- tc_id = tc.get("id", "")
47
- tc_name = tc.get("name", "")
48
- if tc_id and tc_id not in self._result_map and tc_name == name:
49
- self._result_map[tc_id] = result
50
- return
51
- # Fallback: match first unmatched tool call
52
- for tc in self.tool_calls:
53
- tc_id = tc.get("id", "")
54
- if tc_id and tc_id not in self._result_map:
55
- self._result_map[tc_id] = result
56
- return
57
-
58
- def get_result_for(self, tc: dict) -> dict | None:
59
- """Get matched result for a tool call."""
60
- tc_id = tc.get("id", "")
61
- if tc_id:
62
- return self._result_map.get(tc_id)
63
- # Fallback: index-based matching
64
- try:
65
- idx = self.tool_calls.index(tc)
66
- if idx < len(self.tool_results):
67
- return self.tool_results[idx]
68
- except ValueError:
69
- pass
70
- return None
71
-
72
-
73
- class StreamState:
74
- """Accumulates stream state for display updates."""
75
-
76
- def __init__(self):
77
- self.thinking_text = ""
78
- self.response_text = ""
79
- self.tool_calls = []
80
- self.tool_results = []
81
- self.is_thinking = False
82
- self.is_responding = False
83
- self.is_processing = False
84
- # Sub-agent tracking
85
- self.subagents: list[SubAgentState] = []
86
- self._subagent_map: dict[str, SubAgentState] = {} # name -> state
87
- # Todo list tracking
88
- self.todo_items: list[dict] = []
89
- # Latest text segment (reset on each tool_call)
90
- self.latest_text = ""
91
-
92
- def _get_or_create_subagent(self, name: str, description: str = "") -> SubAgentState:
93
- if name not in self._subagent_map:
94
- # Case 1: real name arrives, "sub-agent" entry exists -> rename it
95
- if name != "sub-agent" and "sub-agent" in self._subagent_map:
96
- old_sa = self._subagent_map.pop("sub-agent")
97
- old_sa.name = name
98
- if description:
99
- old_sa.description = description
100
- self._subagent_map[name] = old_sa
101
- return old_sa
102
- # Case 2: "sub-agent" arrives but a pre-registered real-name entry
103
- # exists with no tool calls -> merge into it
104
- if name == "sub-agent":
105
- active_named = [
106
- sa for sa in self.subagents
107
- if sa.is_active and sa.name != "sub-agent"
108
- ]
109
- if len(active_named) == 1 and not active_named[0].tool_calls:
110
- self._subagent_map[name] = active_named[0]
111
- return active_named[0]
112
- sa = SubAgentState(name, description)
113
- self.subagents.append(sa)
114
- self._subagent_map[name] = sa
115
- else:
116
- existing = self._subagent_map[name]
117
- if description and not existing.description:
118
- existing.description = description
119
- # If this entry was created as "sub-agent" placeholder and the
120
- # actual name is different, update.
121
- if name != "sub-agent" and existing.name == "sub-agent":
122
- existing.name = name
123
- return self._subagent_map[name]
124
-
125
- def _resolve_subagent_name(self, name: str) -> str:
126
- """Resolve "sub-agent" to the single active named sub-agent when possible."""
127
- if name != "sub-agent":
128
- return name
129
- active_named = [
130
- sa.name for sa in self.subagents
131
- if sa.is_active and sa.name != "sub-agent"
132
- ]
133
- if len(active_named) == 1:
134
- return active_named[0]
135
- return name
136
-
137
- def handle_event(self, event: dict) -> str:
138
- """Process a single stream event, update internal state, return event type."""
139
- event_type: str = event.get("type", "")
140
-
141
- if event_type == "thinking":
142
- self.is_thinking = True
143
- self.is_responding = False
144
- self.is_processing = False
145
- self.thinking_text += event.get("content", "")
146
-
147
- elif event_type == "text":
148
- self.is_thinking = False
149
- self.is_responding = True
150
- self.is_processing = False
151
- text_content = event.get("content", "")
152
- self.response_text += text_content
153
- self.latest_text += text_content
154
-
155
- elif event_type == "tool_call":
156
- self.is_thinking = False
157
- self.is_responding = False
158
- self.is_processing = False
159
- self.latest_text = "" # Reset -- next text segment is a new message
160
-
161
- tool_id = event.get("id", "")
162
- tool_name = event.get("name", "unknown")
163
- tool_args = event.get("args", {})
164
- tc_data = {
165
- "id": tool_id,
166
- "name": tool_name,
167
- "args": tool_args,
168
- }
169
-
170
- if tool_id:
171
- updated = False
172
- for i, tc in enumerate(self.tool_calls):
173
- if tc.get("id") == tool_id:
174
- self.tool_calls[i] = tc_data
175
- updated = True
176
- break
177
- if not updated:
178
- self.tool_calls.append(tc_data)
179
- else:
180
- self.tool_calls.append(tc_data)
181
-
182
- # Capture todo items from write_todos args (most reliable source)
183
- if tool_name == "write_todos":
184
- todos = tool_args.get("todos", [])
185
- if isinstance(todos, list) and todos:
186
- self.todo_items = todos
187
-
188
- elif event_type == "tool_result":
189
- self.is_processing = True
190
- result_name = event.get("name", "unknown")
191
- result_content = event.get("content", "")
192
- self.tool_results.append({
193
- "name": result_name,
194
- "content": result_content,
195
- })
196
- # Update todo list from write_todos / read_todos results (fallback)
197
- if result_name in ("write_todos", "read_todos"):
198
- parsed = _parse_todo_items(result_content)
199
- if parsed:
200
- self.todo_items = parsed
201
-
202
- elif event_type == "subagent_start":
203
- name = event.get("name", "sub-agent")
204
- desc = event.get("description", "")
205
- sa = self._get_or_create_subagent(name, desc)
206
- sa.is_active = True
207
-
208
- elif event_type == "subagent_tool_call":
209
- sa_name = self._resolve_subagent_name(event.get("subagent", "sub-agent"))
210
- sa = self._get_or_create_subagent(sa_name)
211
- sa.add_tool_call(
212
- event.get("name", "unknown"),
213
- event.get("args", {}),
214
- event.get("id", ""),
215
- )
216
-
217
- elif event_type == "subagent_tool_result":
218
- sa_name = self._resolve_subagent_name(event.get("subagent", "sub-agent"))
219
- sa = self._get_or_create_subagent(sa_name)
220
- sa.add_tool_result(
221
- event.get("name", "unknown"),
222
- event.get("content", ""),
223
- event.get("success", True),
224
- )
225
-
226
- elif event_type == "subagent_end":
227
- name = self._resolve_subagent_name(event.get("name", "sub-agent"))
228
- if name in self._subagent_map:
229
- self._subagent_map[name].is_active = False
230
- elif name == "sub-agent":
231
- # Couldn't resolve -- deactivate the oldest active sub-agent
232
- for sa in self.subagents:
233
- if sa.is_active:
234
- sa.is_active = False
235
- break
236
-
237
- elif event_type == "done":
238
- self.is_processing = False
239
- if not self.response_text:
240
- self.response_text = event.get("response", "")
241
-
242
- elif event_type == "error":
243
- self.is_processing = False
244
- self.is_thinking = False
245
- self.is_responding = False
246
- error_msg = event.get("message", "Unknown error")
247
- self.response_text += f"\n\n[Error] {error_msg}"
248
-
249
- return event_type
250
-
251
- def get_display_args(self) -> dict:
252
- """Get kwargs for create_streaming_display()."""
253
- return {
254
- "thinking_text": self.thinking_text,
255
- "response_text": self.response_text,
256
- "latest_text": self.latest_text,
257
- "tool_calls": self.tool_calls,
258
- "tool_results": self.tool_results,
259
- "is_thinking": self.is_thinking,
260
- "is_responding": self.is_responding,
261
- "is_processing": self.is_processing,
262
- "subagents": self.subagents,
263
- "todo_items": self.todo_items,
264
- }
265
-
266
-
267
- def _parse_todo_items(content: str) -> list[dict] | None:
268
- """Parse todo items from write_todos output.
269
-
270
- Attempts to extract a list of dicts with 'status' and 'content' keys
271
- from the tool result string. Returns None if parsing fails.
272
-
273
- Handles formats like:
274
- - Raw JSON/Python list: [{"content": "...", "status": "..."}]
275
- - Prefixed: "Updated todo list to [{'content': '...', ...}]"
276
- """
277
- content = content.strip()
278
-
279
- def _try_parse(text: str) -> list[dict] | None:
280
- """Try JSON then Python literal parsing."""
281
- text = text.strip()
282
- try:
283
- data = json.loads(text)
284
- if isinstance(data, list) and data and isinstance(data[0], dict):
285
- return data
286
- except (json.JSONDecodeError, ValueError):
287
- pass
288
- try:
289
- data = ast.literal_eval(text)
290
- if isinstance(data, list) and data and isinstance(data[0], dict):
291
- return data
292
- except (ValueError, SyntaxError):
293
- pass
294
- return None
295
-
296
- # Try the full content directly
297
- result = _try_parse(content)
298
- if result:
299
- return result
300
-
301
- # Extract embedded [...] from content (e.g. "Updated todo list to [{...}]")
302
- bracket_start = content.find("[")
303
- if bracket_start != -1:
304
- bracket_end = content.rfind("]")
305
- if bracket_end > bracket_start:
306
- embedded = content[bracket_start:bracket_end + 1]
307
- result = _try_parse(embedded)
308
- if result:
309
- return result
310
-
311
- # Try line-by-line scan
312
- for line in content.split("\n"):
313
- line = line.strip()
314
- if "[" in line:
315
- start = line.find("[")
316
- end = line.rfind("]")
317
- if end > start:
318
- result = _try_parse(line[start:end + 1])
319
- if result:
320
- return result
321
-
322
- return None
323
-
324
-
325
- def _build_todo_stats(items: list[dict]) -> str:
326
- """Build stats string like '2 active | 1 pending | 3 done'."""
327
- counts: dict[str, int] = {}
328
- for item in items:
329
- status = str(item.get("status", "todo")).lower()
330
- # Normalize status names
331
- if status in ("done", "completed", "complete"):
332
- status = "done"
333
- elif status in ("active", "in_progress", "in-progress", "working"):
334
- status = "active"
335
- else:
336
- status = "pending"
337
- counts[status] = counts.get(status, 0) + 1
338
-
339
- parts = []
340
- for key in ("active", "pending", "done"):
341
- if counts.get(key, 0) > 0:
342
- parts.append(f"{counts[key]} {key}")
343
- return " | ".join(parts) if parts else f"{len(items)} items"
@@ -1,5 +0,0 @@
1
- [console_scripts]
2
- EvoSci = EvoScientist.cli:main
3
- EvoScientist = EvoScientist.cli:main
4
- evosci = EvoScientist.cli:main
5
- evoscientist = EvoScientist.cli:main