gitinstall 1.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.
- gitinstall/__init__.py +61 -0
- gitinstall/_sdk.py +541 -0
- gitinstall/academic.py +831 -0
- gitinstall/admin.html +327 -0
- gitinstall/auto_update.py +384 -0
- gitinstall/autopilot.py +349 -0
- gitinstall/badge.py +476 -0
- gitinstall/checkpoint.py +330 -0
- gitinstall/cicd.py +499 -0
- gitinstall/clawhub.html +718 -0
- gitinstall/config_schema.py +353 -0
- gitinstall/db.py +984 -0
- gitinstall/db_backend.py +445 -0
- gitinstall/dep_chain.py +337 -0
- gitinstall/dependency_audit.py +1153 -0
- gitinstall/detector.py +542 -0
- gitinstall/doctor.py +493 -0
- gitinstall/education.py +869 -0
- gitinstall/enterprise.py +802 -0
- gitinstall/error_fixer.py +953 -0
- gitinstall/event_bus.py +251 -0
- gitinstall/executor.py +577 -0
- gitinstall/feature_flags.py +138 -0
- gitinstall/fetcher.py +921 -0
- gitinstall/huggingface.py +922 -0
- gitinstall/hw_detect.py +988 -0
- gitinstall/i18n.py +664 -0
- gitinstall/installer_registry.py +362 -0
- gitinstall/knowledge_base.py +379 -0
- gitinstall/license_check.py +605 -0
- gitinstall/llm.py +569 -0
- gitinstall/log.py +236 -0
- gitinstall/main.py +1408 -0
- gitinstall/mcp_agent.py +841 -0
- gitinstall/mcp_server.py +386 -0
- gitinstall/monorepo.py +810 -0
- gitinstall/multi_source.py +425 -0
- gitinstall/onboard.py +276 -0
- gitinstall/planner.py +222 -0
- gitinstall/planner_helpers.py +323 -0
- gitinstall/planner_known_projects.py +1010 -0
- gitinstall/planner_templates.py +996 -0
- gitinstall/remote_gpu.py +633 -0
- gitinstall/resilience.py +608 -0
- gitinstall/run_tests.py +572 -0
- gitinstall/skills.py +476 -0
- gitinstall/tool_schemas.py +324 -0
- gitinstall/trending.py +279 -0
- gitinstall/uninstaller.py +415 -0
- gitinstall/validate_top100.py +607 -0
- gitinstall/watchdog.py +180 -0
- gitinstall/web.py +1277 -0
- gitinstall/web_ui.html +2277 -0
- gitinstall-1.1.0.dist-info/METADATA +275 -0
- gitinstall-1.1.0.dist-info/RECORD +59 -0
- gitinstall-1.1.0.dist-info/WHEEL +5 -0
- gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
- gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
- gitinstall-1.1.0.dist-info/top_level.txt +1 -0
gitinstall/mcp_agent.py
ADDED
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mcp_agent.py — MCP 生态 + AI Agent 增强引擎
|
|
3
|
+
=============================================
|
|
4
|
+
|
|
5
|
+
目标市场:MCP 生态 + AI Agent(新兴,★★★★☆)
|
|
6
|
+
|
|
7
|
+
功能:
|
|
8
|
+
1. Agent-Friendly API — 面向 AI Agent 的高层 API
|
|
9
|
+
2. 多步计划执行(Multi-step Plan Executor)
|
|
10
|
+
3. 会话上下文管理(Conversation Context)
|
|
11
|
+
4. Tool Schema 生成(OpenAI / Anthropic / MCP 格式)
|
|
12
|
+
5. Agent 协作协议(Agent-to-Agent)
|
|
13
|
+
6. 流式结果(Streaming Results)
|
|
14
|
+
7. Agent Memory — 安装经验记忆
|
|
15
|
+
8. 自然语言意图解析(Intent Parser)
|
|
16
|
+
|
|
17
|
+
零外部依赖,纯 Python 标准库。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import hashlib
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import time
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Callable, Generator, Optional
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ─────────────────────────────────────────────
|
|
34
|
+
# 数据结构
|
|
35
|
+
# ─────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class AgentAction:
|
|
39
|
+
"""Agent 可执行的原子操作"""
|
|
40
|
+
action_id: str = ""
|
|
41
|
+
name: str = "" # detect | fetch | plan | install | audit | doctor | ...
|
|
42
|
+
params: dict[str, Any] = field(default_factory=dict)
|
|
43
|
+
result: dict[str, Any] | None = None
|
|
44
|
+
status: str = "pending" # pending | running | success | failed | skipped
|
|
45
|
+
error: str = ""
|
|
46
|
+
duration_ms: int = 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class AgentPlan:
|
|
51
|
+
"""Agent 执行计划 — 多步骤"""
|
|
52
|
+
plan_id: str = ""
|
|
53
|
+
intent: str = "" # 用户原始意图
|
|
54
|
+
actions: list[AgentAction] = field(default_factory=list)
|
|
55
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
56
|
+
created_at: str = ""
|
|
57
|
+
status: str = "pending" # pending | executing | completed | failed
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class AgentSession:
|
|
62
|
+
"""Agent 会话 — 跨多轮对话保持上下文"""
|
|
63
|
+
session_id: str = ""
|
|
64
|
+
created_at: str = ""
|
|
65
|
+
last_active: str = ""
|
|
66
|
+
history: list[dict] = field(default_factory=list) # 对话历史
|
|
67
|
+
environment: dict[str, Any] = field(default_factory=dict) # 缓存的环境信息
|
|
68
|
+
installed: list[str] = field(default_factory=list) # 已安装的项目
|
|
69
|
+
preferences: dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class AgentMemory:
|
|
74
|
+
"""Agent 经验记忆"""
|
|
75
|
+
memory_id: str = ""
|
|
76
|
+
project: str = ""
|
|
77
|
+
outcome: str = "" # success | failed
|
|
78
|
+
environment_hash: str = ""
|
|
79
|
+
steps_taken: list[str] = field(default_factory=list)
|
|
80
|
+
errors_encountered: list[str] = field(default_factory=list)
|
|
81
|
+
resolution: str = ""
|
|
82
|
+
timestamp: str = ""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ─────────────────────────────────────────────
|
|
86
|
+
# 自然语言意图解析
|
|
87
|
+
# ─────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class ParsedIntent:
|
|
91
|
+
"""解析后的用户意图"""
|
|
92
|
+
action: str = "" # install | info | audit | diagnose | recommend | compare | ...
|
|
93
|
+
targets: list[str] = field(default_factory=list) # 目标项目/模型
|
|
94
|
+
constraints: dict[str, Any] = field(default_factory=dict) # 约束条件
|
|
95
|
+
confidence: float = 0.0
|
|
96
|
+
raw_input: str = ""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# 意图模式库 — 覆盖中英文
|
|
100
|
+
_INTENT_PATTERNS: list[tuple[str, str, float]] = [
|
|
101
|
+
# (正则, action, 基础置信度)
|
|
102
|
+
(r'(?:install|安装|装|setup|部署|deploy)\s+(.+)', "install", 0.9),
|
|
103
|
+
(r'(?:uninstall|卸载|删除|remove)\s+(.+)', "uninstall", 0.9),
|
|
104
|
+
(r'(?:audit|审计|检查安全|scan)\s+(.+)', "audit", 0.85),
|
|
105
|
+
(r'(?:diagnose|诊断|排错|debug|fix)\s*(.+)?', "diagnose", 0.8),
|
|
106
|
+
(r'(?:detect|检测环境|环境|system|系统)', "detect", 0.85),
|
|
107
|
+
(r'(?:doctor|体检|health)', "doctor", 0.85),
|
|
108
|
+
(r'(?:plan|计划|规划)\s+(.+)', "plan", 0.8),
|
|
109
|
+
(r'(?:fetch|获取|info|信息)\s+(.+)', "fetch", 0.8),
|
|
110
|
+
(r'(?:recommend|推荐|建议)\s*(.+)?', "recommend", 0.75),
|
|
111
|
+
(r'(?:compare|对比|比较)\s+(.+)\s+(?:and|和|vs|与)\s+(.+)', "compare", 0.85),
|
|
112
|
+
(r'(?:update|更新|升级)\s*(.+)?', "update", 0.8),
|
|
113
|
+
(r'(?:license|许可证|协议)\s+(.+)', "license", 0.85),
|
|
114
|
+
(r'(?:sbom|物料清单)\s+(.+)', "sbom", 0.85),
|
|
115
|
+
(r'(?:paper|论文|arxiv)\s+(.+)', "paper", 0.85),
|
|
116
|
+
(r'(?:learn|学习|教程|tutorial)\s*(.+)?', "learn", 0.75),
|
|
117
|
+
(r'(?:vram|显存|内存|memory)\s+(.+)', "vram", 0.8),
|
|
118
|
+
(r'(?:gpu|显卡)\s*(.+)?', "gpu_info", 0.8),
|
|
119
|
+
(r'(?:classroom|课堂|教室)\s*(.+)?', "classroom", 0.8),
|
|
120
|
+
(r'(?:badge|徽章|按钮)\s+(.+)', "badge", 0.8),
|
|
121
|
+
(r'(?:ci|cicd|pipeline)\s+(.+)', "cicd", 0.8),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_intent(text: str) -> ParsedIntent:
|
|
126
|
+
"""
|
|
127
|
+
解析自然语言意图。
|
|
128
|
+
|
|
129
|
+
支持:
|
|
130
|
+
- "安装 pytorch/pytorch"
|
|
131
|
+
- "install langchain with GPU support"
|
|
132
|
+
- "这个项目安全吗 huggingface/transformers"
|
|
133
|
+
- "推荐一个 NLP 框架"
|
|
134
|
+
- "对比 vllm 和 tgi"
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
ParsedIntent
|
|
138
|
+
"""
|
|
139
|
+
text = text.strip()
|
|
140
|
+
if not text:
|
|
141
|
+
return ParsedIntent(raw_input=text)
|
|
142
|
+
|
|
143
|
+
best_match = None
|
|
144
|
+
best_confidence = 0.0
|
|
145
|
+
|
|
146
|
+
for pattern, action, base_conf in _INTENT_PATTERNS:
|
|
147
|
+
m = re.search(pattern, text, re.IGNORECASE)
|
|
148
|
+
if m:
|
|
149
|
+
# 提取目标
|
|
150
|
+
targets = []
|
|
151
|
+
for g in m.groups():
|
|
152
|
+
if g:
|
|
153
|
+
targets.extend(_extract_targets(g.strip()))
|
|
154
|
+
|
|
155
|
+
# 提取约束
|
|
156
|
+
constraints = _extract_constraints(text)
|
|
157
|
+
|
|
158
|
+
conf = base_conf
|
|
159
|
+
if targets:
|
|
160
|
+
conf += 0.05
|
|
161
|
+
|
|
162
|
+
if conf > best_confidence:
|
|
163
|
+
best_confidence = conf
|
|
164
|
+
best_match = ParsedIntent(
|
|
165
|
+
action=action,
|
|
166
|
+
targets=targets,
|
|
167
|
+
constraints=constraints,
|
|
168
|
+
confidence=conf,
|
|
169
|
+
raw_input=text,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if best_match:
|
|
173
|
+
return best_match
|
|
174
|
+
|
|
175
|
+
# 如果没有明确意图,尝试识别项目名
|
|
176
|
+
targets = _extract_targets(text)
|
|
177
|
+
if targets:
|
|
178
|
+
return ParsedIntent(
|
|
179
|
+
action="install",
|
|
180
|
+
targets=targets,
|
|
181
|
+
confidence=0.5,
|
|
182
|
+
raw_input=text,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return ParsedIntent(action="unknown", raw_input=text, confidence=0.0)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _extract_targets(text: str) -> list[str]:
|
|
189
|
+
"""提取目标项目/模型名"""
|
|
190
|
+
targets = []
|
|
191
|
+
|
|
192
|
+
# GitHub owner/repo 格式
|
|
193
|
+
for m in re.finditer(r'(?:https?://github\.com/)?([\w.-]+/[\w.-]+)', text):
|
|
194
|
+
targets.append(m.group(1))
|
|
195
|
+
|
|
196
|
+
# 如果没有 owner/repo,尝试识别知名项目
|
|
197
|
+
if not targets:
|
|
198
|
+
known_aliases = {
|
|
199
|
+
"pytorch": "pytorch/pytorch",
|
|
200
|
+
"tensorflow": "tensorflow/tensorflow",
|
|
201
|
+
"react": "facebook/react",
|
|
202
|
+
"vue": "vuejs/core",
|
|
203
|
+
"langchain": "langchain-ai/langchain",
|
|
204
|
+
"transformers": "huggingface/transformers",
|
|
205
|
+
"vllm": "vllm-project/vllm",
|
|
206
|
+
"llama.cpp": "ggml-org/llama.cpp",
|
|
207
|
+
"llamacpp": "ggml-org/llama.cpp",
|
|
208
|
+
"whisper": "openai/whisper",
|
|
209
|
+
"yolo": "ultralytics/ultralytics",
|
|
210
|
+
"fastapi": "fastapi/fastapi",
|
|
211
|
+
"flask": "pallets/flask",
|
|
212
|
+
"django": "django/django",
|
|
213
|
+
"next.js": "vercel/next.js",
|
|
214
|
+
"nextjs": "vercel/next.js",
|
|
215
|
+
"deno": "denoland/deno",
|
|
216
|
+
"rust": "rust-lang/rust",
|
|
217
|
+
"go": "golang/go",
|
|
218
|
+
"node": "nodejs/node",
|
|
219
|
+
"numpy": "numpy/numpy",
|
|
220
|
+
"pandas": "pandas-dev/pandas",
|
|
221
|
+
"scikit-learn": "scikit-learn/scikit-learn",
|
|
222
|
+
"sklearn": "scikit-learn/scikit-learn",
|
|
223
|
+
"stable-diffusion": "CompVis/stable-diffusion",
|
|
224
|
+
"ollama": "ollama/ollama",
|
|
225
|
+
"comfyui": "comfyanonymous/ComfyUI",
|
|
226
|
+
}
|
|
227
|
+
text_lower = text.lower()
|
|
228
|
+
for alias, repo in known_aliases.items():
|
|
229
|
+
if alias in text_lower:
|
|
230
|
+
targets.append(repo)
|
|
231
|
+
break
|
|
232
|
+
|
|
233
|
+
return targets
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _extract_constraints(text: str) -> dict[str, Any]:
|
|
237
|
+
"""提取约束条件"""
|
|
238
|
+
constraints = {}
|
|
239
|
+
text_lower = text.lower()
|
|
240
|
+
|
|
241
|
+
if any(w in text_lower for w in ("gpu", "cuda", "显卡")):
|
|
242
|
+
constraints["gpu"] = True
|
|
243
|
+
if any(w in text_lower for w in ("cpu only", "无gpu", "无显卡", "no gpu")):
|
|
244
|
+
constraints["gpu"] = False
|
|
245
|
+
if any(w in text_lower for w in ("docker", "容器")):
|
|
246
|
+
constraints["docker"] = True
|
|
247
|
+
if any(w in text_lower for w in ("本地", "local", "离线", "offline")):
|
|
248
|
+
constraints["local"] = True
|
|
249
|
+
if any(w in text_lower for w in ("安全", "secure", "安全模式")):
|
|
250
|
+
constraints["security_first"] = True
|
|
251
|
+
|
|
252
|
+
# VRAM 约束
|
|
253
|
+
m = re.search(r'(\d+)\s*(?:gb|g)\s*(?:vram|显存|内存)', text_lower)
|
|
254
|
+
if m:
|
|
255
|
+
constraints["vram_gb"] = int(m.group(1))
|
|
256
|
+
|
|
257
|
+
return constraints
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ─────────────────────────────────────────────
|
|
261
|
+
# Agent 执行器
|
|
262
|
+
# ─────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
class AgentExecutor:
|
|
265
|
+
"""
|
|
266
|
+
AI Agent 高层执行引擎。
|
|
267
|
+
|
|
268
|
+
将自然语言意图转化为结构化的多步骤执行计划,
|
|
269
|
+
并执行每一步,支持流式输出和错误恢复。
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(self, session: AgentSession | None = None):
|
|
273
|
+
if session:
|
|
274
|
+
self.session = session
|
|
275
|
+
else:
|
|
276
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
277
|
+
sid = hashlib.sha256(now.encode()).hexdigest()[:12]
|
|
278
|
+
self.session = AgentSession(
|
|
279
|
+
session_id=sid,
|
|
280
|
+
created_at=now,
|
|
281
|
+
last_active=now,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def process(self, user_input: str) -> AgentPlan:
|
|
285
|
+
"""
|
|
286
|
+
处理用户输入,生成并执行计划。
|
|
287
|
+
|
|
288
|
+
这是 Agent 的主入口。传入自然语言,返回执行结果。
|
|
289
|
+
"""
|
|
290
|
+
intent = parse_intent(user_input)
|
|
291
|
+
plan = self._create_plan(intent)
|
|
292
|
+
self._execute_plan(plan)
|
|
293
|
+
|
|
294
|
+
# 记录到会话历史
|
|
295
|
+
self.session.history.append({
|
|
296
|
+
"role": "user",
|
|
297
|
+
"content": user_input,
|
|
298
|
+
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
299
|
+
})
|
|
300
|
+
self.session.history.append({
|
|
301
|
+
"role": "assistant",
|
|
302
|
+
"plan_id": plan.plan_id,
|
|
303
|
+
"status": plan.status,
|
|
304
|
+
"actions": len(plan.actions),
|
|
305
|
+
})
|
|
306
|
+
self.session.last_active = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
307
|
+
|
|
308
|
+
return plan
|
|
309
|
+
|
|
310
|
+
def _create_plan(self, intent: ParsedIntent) -> AgentPlan:
|
|
311
|
+
"""从意图创建执行计划"""
|
|
312
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
313
|
+
plan_id = hashlib.sha256(f"{now}-{intent.action}".encode()).hexdigest()[:10]
|
|
314
|
+
|
|
315
|
+
plan = AgentPlan(
|
|
316
|
+
plan_id=plan_id,
|
|
317
|
+
intent=intent.raw_input,
|
|
318
|
+
created_at=now,
|
|
319
|
+
context={"parsed_intent": intent.action, "targets": intent.targets},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
actions = _INTENT_TO_ACTIONS.get(intent.action, _default_actions)
|
|
323
|
+
plan.actions = actions(intent, self.session)
|
|
324
|
+
return plan
|
|
325
|
+
|
|
326
|
+
def _execute_plan(self, plan: AgentPlan) -> None:
|
|
327
|
+
"""执行计划中的每一步"""
|
|
328
|
+
plan.status = "executing"
|
|
329
|
+
|
|
330
|
+
for action in plan.actions:
|
|
331
|
+
if action.status == "skipped":
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
action.status = "running"
|
|
335
|
+
start = time.monotonic()
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
result = self._execute_action(action)
|
|
339
|
+
action.result = result
|
|
340
|
+
action.status = "success"
|
|
341
|
+
|
|
342
|
+
# 缓存环境信息
|
|
343
|
+
if action.name == "detect":
|
|
344
|
+
self.session.environment = result
|
|
345
|
+
elif action.name == "install" and result.get("success"):
|
|
346
|
+
target = action.params.get("identifier", "")
|
|
347
|
+
if target and target not in self.session.installed:
|
|
348
|
+
self.session.installed.append(target)
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
action.status = "failed"
|
|
352
|
+
action.error = str(e)
|
|
353
|
+
|
|
354
|
+
action.duration_ms = int((time.monotonic() - start) * 1000)
|
|
355
|
+
|
|
356
|
+
# 整体状态
|
|
357
|
+
if all(a.status in ("success", "skipped") for a in plan.actions):
|
|
358
|
+
plan.status = "completed"
|
|
359
|
+
elif any(a.status == "failed" for a in plan.actions):
|
|
360
|
+
plan.status = "failed"
|
|
361
|
+
else:
|
|
362
|
+
plan.status = "completed"
|
|
363
|
+
|
|
364
|
+
def _execute_action(self, action: AgentAction) -> dict:
|
|
365
|
+
"""执行单个 action"""
|
|
366
|
+
from _sdk import detect, plan, install, diagnose, fetch, doctor, audit
|
|
367
|
+
|
|
368
|
+
name = action.name
|
|
369
|
+
params = action.params
|
|
370
|
+
|
|
371
|
+
if name == "detect":
|
|
372
|
+
return detect()
|
|
373
|
+
elif name == "fetch":
|
|
374
|
+
return fetch(params.get("identifier", ""))
|
|
375
|
+
elif name == "plan":
|
|
376
|
+
return plan(params.get("identifier", ""))
|
|
377
|
+
elif name == "install":
|
|
378
|
+
return install(
|
|
379
|
+
params.get("identifier", ""),
|
|
380
|
+
install_dir=params.get("install_dir"),
|
|
381
|
+
)
|
|
382
|
+
elif name == "audit":
|
|
383
|
+
return audit(params.get("identifier", ""))
|
|
384
|
+
elif name == "doctor":
|
|
385
|
+
return doctor()
|
|
386
|
+
elif name == "diagnose":
|
|
387
|
+
return diagnose(params.get("identifier", ""))
|
|
388
|
+
elif name == "noop":
|
|
389
|
+
return {"message": params.get("message", "OK")}
|
|
390
|
+
else:
|
|
391
|
+
return {"error": f"Unknown action: {name}"}
|
|
392
|
+
|
|
393
|
+
def stream_process(self, user_input: str) -> Generator[dict, None, None]:
|
|
394
|
+
"""
|
|
395
|
+
流式处理 — 适合长时间任务的实时反馈。
|
|
396
|
+
|
|
397
|
+
Yields:
|
|
398
|
+
{"type": "intent", "data": {...}}
|
|
399
|
+
{"type": "action_start", "data": {...}}
|
|
400
|
+
{"type": "action_complete", "data": {...}}
|
|
401
|
+
{"type": "plan_complete", "data": {...}}
|
|
402
|
+
"""
|
|
403
|
+
intent = parse_intent(user_input)
|
|
404
|
+
yield {"type": "intent", "data": {
|
|
405
|
+
"action": intent.action,
|
|
406
|
+
"targets": intent.targets,
|
|
407
|
+
"confidence": intent.confidence,
|
|
408
|
+
}}
|
|
409
|
+
|
|
410
|
+
plan = self._create_plan(intent)
|
|
411
|
+
plan.status = "executing"
|
|
412
|
+
|
|
413
|
+
for action in plan.actions:
|
|
414
|
+
if action.status == "skipped":
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
yield {"type": "action_start", "data": {
|
|
418
|
+
"action_id": action.action_id,
|
|
419
|
+
"name": action.name,
|
|
420
|
+
"params": action.params,
|
|
421
|
+
}}
|
|
422
|
+
|
|
423
|
+
action.status = "running"
|
|
424
|
+
start = time.monotonic()
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
result = self._execute_action(action)
|
|
428
|
+
action.result = result
|
|
429
|
+
action.status = "success"
|
|
430
|
+
except Exception as e:
|
|
431
|
+
action.status = "failed"
|
|
432
|
+
action.error = str(e)
|
|
433
|
+
|
|
434
|
+
action.duration_ms = int((time.monotonic() - start) * 1000)
|
|
435
|
+
|
|
436
|
+
yield {"type": "action_complete", "data": {
|
|
437
|
+
"action_id": action.action_id,
|
|
438
|
+
"name": action.name,
|
|
439
|
+
"status": action.status,
|
|
440
|
+
"duration_ms": action.duration_ms,
|
|
441
|
+
"error": action.error,
|
|
442
|
+
}}
|
|
443
|
+
|
|
444
|
+
plan.status = "completed" if all(
|
|
445
|
+
a.status in ("success", "skipped") for a in plan.actions
|
|
446
|
+
) else "failed"
|
|
447
|
+
|
|
448
|
+
yield {"type": "plan_complete", "data": {
|
|
449
|
+
"plan_id": plan.plan_id,
|
|
450
|
+
"status": plan.status,
|
|
451
|
+
"total_actions": len(plan.actions),
|
|
452
|
+
}}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# ─────────────────────────────────────────────
|
|
456
|
+
# 意图→Actions 映射
|
|
457
|
+
# ─────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
def _make_action(name: str, params: dict | None = None, aid: str = "") -> AgentAction:
|
|
460
|
+
"""创建 action"""
|
|
461
|
+
return AgentAction(
|
|
462
|
+
action_id=aid or hashlib.sha256(f"{name}-{time.time()}".encode()).hexdigest()[:8],
|
|
463
|
+
name=name,
|
|
464
|
+
params=params or {},
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _install_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
469
|
+
"""安装流程: detect → plan → install"""
|
|
470
|
+
actions = []
|
|
471
|
+
|
|
472
|
+
# 如果会话中没有缓存环境信息,先检测
|
|
473
|
+
if not session.environment:
|
|
474
|
+
actions.append(_make_action("detect"))
|
|
475
|
+
|
|
476
|
+
for target in intent.targets:
|
|
477
|
+
actions.append(_make_action("plan", {"identifier": target}))
|
|
478
|
+
actions.append(_make_action("install", {"identifier": target}))
|
|
479
|
+
|
|
480
|
+
return actions or [_make_action("noop", {"message": "请指定要安装的项目"})]
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _audit_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
484
|
+
"""审计流程"""
|
|
485
|
+
actions = []
|
|
486
|
+
for target in intent.targets:
|
|
487
|
+
actions.append(_make_action("audit", {"identifier": target}))
|
|
488
|
+
return actions or [_make_action("noop", {"message": "请指定要审计的项目"})]
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _diagnose_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
492
|
+
"""诊断流程: doctor + diagnose"""
|
|
493
|
+
actions = [_make_action("doctor")]
|
|
494
|
+
for target in intent.targets:
|
|
495
|
+
actions.append(_make_action("diagnose", {"identifier": target}))
|
|
496
|
+
return actions
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _detect_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
500
|
+
return [_make_action("detect")]
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _doctor_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
504
|
+
return [_make_action("doctor")]
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _fetch_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
508
|
+
actions = []
|
|
509
|
+
for target in intent.targets:
|
|
510
|
+
actions.append(_make_action("fetch", {"identifier": target}))
|
|
511
|
+
return actions or [_make_action("noop", {"message": "请指定项目"})]
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _plan_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
515
|
+
actions = []
|
|
516
|
+
if not session.environment:
|
|
517
|
+
actions.append(_make_action("detect"))
|
|
518
|
+
for target in intent.targets:
|
|
519
|
+
actions.append(_make_action("plan", {"identifier": target}))
|
|
520
|
+
return actions or [_make_action("noop", {"message": "请指定项目"})]
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _default_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
|
|
524
|
+
return [_make_action("noop", {"message": f"未识别的操作: {intent.action}"})]
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
_INTENT_TO_ACTIONS: dict[str, Callable] = {
|
|
528
|
+
"install": _install_actions,
|
|
529
|
+
"uninstall": _default_actions, # TODO: 对接 uninstaller.py
|
|
530
|
+
"audit": _audit_actions,
|
|
531
|
+
"diagnose": _diagnose_actions,
|
|
532
|
+
"detect": _detect_actions,
|
|
533
|
+
"doctor": _doctor_actions,
|
|
534
|
+
"plan": _plan_actions,
|
|
535
|
+
"fetch": _fetch_actions,
|
|
536
|
+
"recommend": _default_actions,
|
|
537
|
+
"compare": _default_actions,
|
|
538
|
+
"update": _default_actions,
|
|
539
|
+
"license": _audit_actions,
|
|
540
|
+
"sbom": _audit_actions,
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
# ─────────────────────────────────────────────
|
|
545
|
+
# Tool Schema 生成(OpenAI / Anthropic / MCP)
|
|
546
|
+
# ─────────────────────────────────────────────
|
|
547
|
+
|
|
548
|
+
def generate_tool_schemas(format: str = "openai") -> list[dict]:
|
|
549
|
+
"""
|
|
550
|
+
生成适配不同 AI 平台的 tool schema。
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
format: "openai" | "anthropic" | "mcp"
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
Tool schema 列表
|
|
557
|
+
"""
|
|
558
|
+
base_tools = [
|
|
559
|
+
{
|
|
560
|
+
"name": "gitinstall_detect",
|
|
561
|
+
"description": "检测当前系统环境(OS、CPU、GPU、运行时、包管理器)",
|
|
562
|
+
"parameters": {},
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"name": "gitinstall_install",
|
|
566
|
+
"description": "一键安装 GitHub 项目。支持 owner/repo 格式或完整 URL",
|
|
567
|
+
"parameters": {
|
|
568
|
+
"identifier": {"type": "string", "description": "GitHub 项目标识(owner/repo 或 URL)", "required": True},
|
|
569
|
+
"install_dir": {"type": "string", "description": "安装目录(可选)"},
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
"name": "gitinstall_plan",
|
|
574
|
+
"description": "为 GitHub 项目生成安装计划,不执行安装",
|
|
575
|
+
"parameters": {
|
|
576
|
+
"identifier": {"type": "string", "description": "GitHub 项目标识", "required": True},
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
"name": "gitinstall_audit",
|
|
581
|
+
"description": "安全审计 — 检查依赖漏洞、许可证风险、typosquatting",
|
|
582
|
+
"parameters": {
|
|
583
|
+
"identifier": {"type": "string", "description": "GitHub 项目标识", "required": True},
|
|
584
|
+
"online": {"type": "boolean", "description": "是否查询在线 CVE 数据库"},
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
"name": "gitinstall_doctor",
|
|
589
|
+
"description": "系统健康检查 — Python、Git、包管理器、GPU、磁盘空间",
|
|
590
|
+
"parameters": {},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
"name": "gitinstall_fetch",
|
|
594
|
+
"description": "获取 GitHub 项目元数据(README、依赖、项目类型)",
|
|
595
|
+
"parameters": {
|
|
596
|
+
"identifier": {"type": "string", "description": "GitHub 项目标识", "required": True},
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
"name": "gitinstall_vram",
|
|
601
|
+
"description": "评估 AI 模型的 VRAM 需求和最佳量化方案",
|
|
602
|
+
"parameters": {
|
|
603
|
+
"model_id": {"type": "string", "description": "HuggingFace 模型 ID", "required": True},
|
|
604
|
+
"vram_gb": {"type": "number", "description": "可用 VRAM (GB)"},
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"name": "gitinstall_paper",
|
|
609
|
+
"description": "从 arXiv 论文 ID 查找代码仓库并安装",
|
|
610
|
+
"parameters": {
|
|
611
|
+
"paper_id": {"type": "string", "description": "arXiv ID (如 2301.13688)", "required": True},
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
"name": "gitinstall_classroom",
|
|
616
|
+
"description": "创建课堂环境 — 批量配置学生开发环境",
|
|
617
|
+
"parameters": {
|
|
618
|
+
"name": {"type": "string", "description": "课堂名称", "required": True},
|
|
619
|
+
"projects": {"type": "array", "items": {"type": "string"}, "description": "项目列表", "required": True},
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
"name": "gitinstall_natural",
|
|
624
|
+
"description": "自然语言安装 — 用中文或英文描述你想做什么,自动理解执行",
|
|
625
|
+
"parameters": {
|
|
626
|
+
"text": {"type": "string", "description": "自然语言描述", "required": True},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
]
|
|
630
|
+
|
|
631
|
+
if format == "openai":
|
|
632
|
+
return _to_openai_format(base_tools)
|
|
633
|
+
elif format == "anthropic":
|
|
634
|
+
return _to_anthropic_format(base_tools)
|
|
635
|
+
elif format == "mcp":
|
|
636
|
+
return _to_mcp_format(base_tools)
|
|
637
|
+
return base_tools
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _to_openai_format(tools: list[dict]) -> list[dict]:
|
|
641
|
+
"""转换为 OpenAI function calling 格式"""
|
|
642
|
+
result = []
|
|
643
|
+
for t in tools:
|
|
644
|
+
props = {}
|
|
645
|
+
required = []
|
|
646
|
+
for pname, pinfo in t.get("parameters", {}).items():
|
|
647
|
+
props[pname] = {
|
|
648
|
+
"type": pinfo.get("type", "string"),
|
|
649
|
+
"description": pinfo.get("description", ""),
|
|
650
|
+
}
|
|
651
|
+
if pinfo.get("items"):
|
|
652
|
+
props[pname]["items"] = pinfo["items"]
|
|
653
|
+
if pinfo.get("required"):
|
|
654
|
+
required.append(pname)
|
|
655
|
+
|
|
656
|
+
result.append({
|
|
657
|
+
"type": "function",
|
|
658
|
+
"function": {
|
|
659
|
+
"name": t["name"],
|
|
660
|
+
"description": t["description"],
|
|
661
|
+
"parameters": {
|
|
662
|
+
"type": "object",
|
|
663
|
+
"properties": props,
|
|
664
|
+
"required": required,
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
})
|
|
668
|
+
return result
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def _to_anthropic_format(tools: list[dict]) -> list[dict]:
|
|
672
|
+
"""转换为 Anthropic tool_use 格式"""
|
|
673
|
+
result = []
|
|
674
|
+
for t in tools:
|
|
675
|
+
props = {}
|
|
676
|
+
required = []
|
|
677
|
+
for pname, pinfo in t.get("parameters", {}).items():
|
|
678
|
+
props[pname] = {
|
|
679
|
+
"type": pinfo.get("type", "string"),
|
|
680
|
+
"description": pinfo.get("description", ""),
|
|
681
|
+
}
|
|
682
|
+
if pinfo.get("required"):
|
|
683
|
+
required.append(pname)
|
|
684
|
+
|
|
685
|
+
result.append({
|
|
686
|
+
"name": t["name"],
|
|
687
|
+
"description": t["description"],
|
|
688
|
+
"input_schema": {
|
|
689
|
+
"type": "object",
|
|
690
|
+
"properties": props,
|
|
691
|
+
"required": required,
|
|
692
|
+
},
|
|
693
|
+
})
|
|
694
|
+
return result
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _to_mcp_format(tools: list[dict]) -> list[dict]:
|
|
698
|
+
"""转换为 MCP 协议格式"""
|
|
699
|
+
result = []
|
|
700
|
+
for t in tools:
|
|
701
|
+
props = {}
|
|
702
|
+
required = []
|
|
703
|
+
for pname, pinfo in t.get("parameters", {}).items():
|
|
704
|
+
props[pname] = {
|
|
705
|
+
"type": pinfo.get("type", "string"),
|
|
706
|
+
"description": pinfo.get("description", ""),
|
|
707
|
+
}
|
|
708
|
+
if pinfo.get("required"):
|
|
709
|
+
required.append(pname)
|
|
710
|
+
|
|
711
|
+
result.append({
|
|
712
|
+
"name": t["name"],
|
|
713
|
+
"description": t["description"],
|
|
714
|
+
"inputSchema": {
|
|
715
|
+
"type": "object",
|
|
716
|
+
"properties": props,
|
|
717
|
+
"required": required,
|
|
718
|
+
},
|
|
719
|
+
})
|
|
720
|
+
return result
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
# ─────────────────────────────────────────────
|
|
724
|
+
# Agent 经验记忆
|
|
725
|
+
# ─────────────────────────────────────────────
|
|
726
|
+
|
|
727
|
+
_MEMORY_DIR = os.path.expanduser("~/.gitinstall/agent_memory")
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def remember_outcome(
|
|
731
|
+
project: str,
|
|
732
|
+
outcome: str,
|
|
733
|
+
steps: list[str] | None = None,
|
|
734
|
+
errors: list[str] | None = None,
|
|
735
|
+
resolution: str = "",
|
|
736
|
+
env_info: dict | None = None,
|
|
737
|
+
) -> AgentMemory:
|
|
738
|
+
"""记录安装结果到经验库"""
|
|
739
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
740
|
+
env_hash = hashlib.sha256(
|
|
741
|
+
json.dumps(env_info or {}, sort_keys=True).encode()
|
|
742
|
+
).hexdigest()[:12]
|
|
743
|
+
|
|
744
|
+
mem = AgentMemory(
|
|
745
|
+
memory_id=hashlib.sha256(f"{project}-{now}".encode()).hexdigest()[:10],
|
|
746
|
+
project=project,
|
|
747
|
+
outcome=outcome,
|
|
748
|
+
environment_hash=env_hash,
|
|
749
|
+
steps_taken=steps or [],
|
|
750
|
+
errors_encountered=errors or [],
|
|
751
|
+
resolution=resolution,
|
|
752
|
+
timestamp=now,
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
os.makedirs(_MEMORY_DIR, exist_ok=True)
|
|
756
|
+
path = os.path.join(_MEMORY_DIR, f"{mem.memory_id}.json")
|
|
757
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
758
|
+
json.dump({
|
|
759
|
+
"memory_id": mem.memory_id,
|
|
760
|
+
"project": mem.project,
|
|
761
|
+
"outcome": mem.outcome,
|
|
762
|
+
"environment_hash": mem.environment_hash,
|
|
763
|
+
"steps_taken": mem.steps_taken,
|
|
764
|
+
"errors_encountered": mem.errors_encountered,
|
|
765
|
+
"resolution": mem.resolution,
|
|
766
|
+
"timestamp": mem.timestamp,
|
|
767
|
+
}, f, indent=2, ensure_ascii=False)
|
|
768
|
+
|
|
769
|
+
return mem
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def recall_experience(project: str) -> list[AgentMemory]:
|
|
773
|
+
"""查找某项目的安装经验"""
|
|
774
|
+
if not os.path.isdir(_MEMORY_DIR):
|
|
775
|
+
return []
|
|
776
|
+
|
|
777
|
+
results = []
|
|
778
|
+
for fname in os.listdir(_MEMORY_DIR):
|
|
779
|
+
if not fname.endswith(".json"):
|
|
780
|
+
continue
|
|
781
|
+
path = os.path.join(_MEMORY_DIR, fname)
|
|
782
|
+
try:
|
|
783
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
784
|
+
data = json.load(f)
|
|
785
|
+
if data.get("project", "") == project:
|
|
786
|
+
results.append(AgentMemory(**data))
|
|
787
|
+
except (json.JSONDecodeError, OSError, TypeError):
|
|
788
|
+
pass
|
|
789
|
+
|
|
790
|
+
results.sort(key=lambda x: x.timestamp, reverse=True)
|
|
791
|
+
return results
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
# ─────────────────────────────────────────────
|
|
795
|
+
# Agent-to-Agent 协作协议
|
|
796
|
+
# ─────────────────────────────────────────────
|
|
797
|
+
|
|
798
|
+
def create_agent_handoff(
|
|
799
|
+
from_agent: str,
|
|
800
|
+
to_agent: str,
|
|
801
|
+
task: str,
|
|
802
|
+
context: dict[str, Any] | None = None,
|
|
803
|
+
) -> dict:
|
|
804
|
+
"""
|
|
805
|
+
创建 Agent 间的任务交接消息。
|
|
806
|
+
|
|
807
|
+
用于多 Agent 编排场景(如 AutoGen、CrewAI)。
|
|
808
|
+
"""
|
|
809
|
+
return {
|
|
810
|
+
"protocol": "gitinstall-agent-handoff/1.0",
|
|
811
|
+
"from": from_agent,
|
|
812
|
+
"to": to_agent,
|
|
813
|
+
"task": task,
|
|
814
|
+
"context": context or {},
|
|
815
|
+
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
816
|
+
"capabilities": [
|
|
817
|
+
"detect", "fetch", "plan", "install", "audit",
|
|
818
|
+
"doctor", "vram_estimate", "paper_install", "classroom",
|
|
819
|
+
],
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def format_plan_result(plan: AgentPlan) -> str:
|
|
824
|
+
"""格式化计划执行结果"""
|
|
825
|
+
status_icons = {"completed": "✅", "failed": "❌", "pending": "⏳", "executing": "🔄"}
|
|
826
|
+
|
|
827
|
+
lines = [
|
|
828
|
+
f"{status_icons.get(plan.status, '?')} 执行计划 [{plan.plan_id}]",
|
|
829
|
+
f" 意图: {plan.intent}",
|
|
830
|
+
f" 状态: {plan.status}",
|
|
831
|
+
"",
|
|
832
|
+
]
|
|
833
|
+
|
|
834
|
+
for a in plan.actions:
|
|
835
|
+
icon = {"success": "✅", "failed": "❌", "skipped": "⏭️", "pending": "○", "running": "🔄"}.get(a.status, "?")
|
|
836
|
+
duration = f" ({a.duration_ms}ms)" if a.duration_ms else ""
|
|
837
|
+
lines.append(f" {icon} {a.name}{duration}")
|
|
838
|
+
if a.error:
|
|
839
|
+
lines.append(f" ⚠️ {a.error}")
|
|
840
|
+
|
|
841
|
+
return "\n".join(lines)
|