diff --git a/agentmain.py b/agentmain.py index ffd8205..b2839ac 100644 --- a/agentmain.py +++ b/agentmain.py @@ -38,8 +38,7 @@ def get_system_prompt(): class GeneraticAgent: def __init__(self): script_dir = os.path.dirname(os.path.abspath(__file__)) - temp_dir = os.path.join(script_dir, 'temp') - if not os.path.exists(temp_dir): os.makedirs(temp_dir) + os.makedirs(os.path.join(script_dir, 'temp'), exist_ok=True) from llmcore import mykeys llm_sessions = [] for k, cfg in mykeys.items(): @@ -99,10 +98,11 @@ class GeneraticAgent: sys_prompt = get_system_prompt() script_dir = os.path.dirname(os.path.abspath(__file__)) handler = GenericAgentHandler(None, self.history, os.path.join(script_dir, 'temp')) - if self.handler and self.handler.key_info: - handler.key_info = self.handler.key_info - if '清除工作记忆' not in handler.key_info: - handler.key_info += '\n[SYSTEM] 若开始新任务,先更新或清除工作记忆\n' + if self.handler and 'key_info' in self.handler.working: + ki = re.sub(r'\n\[SYSTEM\] 此为.*?工作记忆[。\n]*', '', self.handler.working['key_info']) # 去旧 + handler.working['key_info'] = ki + handler.working['passed_sessions'] = ps = self.handler.working.get('passed_sessions', 0) + 1 + if ps > 0: handler.working['key_info'] += f'\n[SYSTEM] 此为 {ps} 个对话前设置的工作记忆。若已在新任务,先更新或清除工作记忆\n' self.handler = handler self.llmclient.backend = self.llmclient.backends[self.llm_no] user_input = raw_query @@ -154,7 +154,6 @@ if __name__ == '__main__': threading.Thread(target=agent.run, daemon=True).start() if args.task: - script_dir = os.path.dirname(os.path.abspath(__file__)) d = os.path.join(script_dir, f'temp/{args.task}'); rp = os.path.join(d, 'reply.txt'); nround = '' with open(os.path.join(d, 'input.txt'), encoding='utf-8') as f: raw = f.read() while True: @@ -193,8 +192,9 @@ if __name__ == '__main__': except Exception as e: if once: raise print(f'[Reflect] drain error: {e}'); result = f'[ERROR] {e}' - script_dir = os.path.dirname(os.path.abspath(__file__)) - open(os.path.join(script_dir, './temp/reflect.log'), 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}]\n{result}\n\n') + log_dir = os.path.join(script_dir, 'temp/reflect_logs'); os.makedirs(log_dir, exist_ok=True) + script_name = os.path.splitext(os.path.basename(args.reflect))[0] + open(os.path.join(log_dir, f'{script_name}_{datetime.now():%Y-%m-%d}.log'), 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}]\n{result}\n\n') if on_done: try: on_done(result) except Exception as e: print(f'[Reflect] on_done error: {e}') diff --git a/ga.py b/ga.py index ee1d731..e5b64c1 100644 --- a/ga.py +++ b/ga.py @@ -246,8 +246,7 @@ class GenericAgentHandler(BaseHandler): '''Generic Agent 工具库,包含多种工具的实现。工具函数自动加上了 do_ 前缀。实际工具名没有前缀。''' def __init__(self, parent, last_history=None, cwd='./'): self.parent = parent - self.key_info = "" - self.related_sop = "" + self.working = {} self.cwd = cwd; self.current_turn = 0 self.history_info = last_history if last_history else [] self.code_stop_signal = [] @@ -408,11 +407,12 @@ class GenericAgentHandler(BaseHandler): ''' key_info = args.get("key_info", "") related_sop = args.get("related_sop", "") - if "key_info" in args: self.key_info = key_info - if "related_sop" in args: self.related_sop = related_sop + if "key_info" in args: self.working['key_info'] = key_info + if "related_sop" in args: self.working['related_sop'] = related_sop + self.working['passed_sessions'] = 0 yield f"[Info] Updated key_info and related_sop.\n" - yield f"key_info:\n{self.key_info}\n\n" - yield f"related_sop:\n{self.related_sop}\n\n" + yield f"key_info:\n{self.working.get('key_info', '')}\n\n" + yield f"related_sop:\n{self.working.get('related_sop', '')}\n\n" next_prompt = self._get_anchor_prompt() #next_prompt += '\n[SYSTEM TIPS] 此函数一般在任务开始或中间时调用,如果任务已成功完成应该是start_long_term_update用于结算长期记忆。\n' return StepOutcome({"status": "success"}, next_prompt=next_prompt) @@ -477,14 +477,14 @@ class GenericAgentHandler(BaseHandler): h_str = "\n".join(self.history_info[-20:]) prompt = f"\n### [WORKING MEMORY]\n\n{h_str}\n" prompt += f"\nCurrent turn: {self.current_turn}\n" - if self.key_info: prompt += f"\n{self.key_info}" - if self.related_sop: prompt += f"\n有不清晰的地方请再次读取{self.related_sop}" + if self.working.get('key_info'): prompt += f"\n{self.working.get('key_info')}" + if self.working.get('related_sop'): prompt += f"\n有不清晰的地方请再次读取{self.working.get('related_sop')}" try: print(prompt) except: pass return prompt def next_prompt_patcher(self, next_prompt, outcome, turn): - if turn % 35 == 0 and 'plan' not in str(self.related_sop): + if turn % 35 == 0 and 'plan' not in str(self.working.get('related_sop')): next_prompt += f"\n\n[DANGER] 已连续执行第 {turn} 轮。你必须总结情况进行ask_user,不允许继续重试。" elif turn % 7 == 0: next_prompt += f"\n\n[DANGER] 已连续执行第 {turn} 轮。禁止无效重试。若无有效进展,必须切换策略:1. 探测物理边界 2. 请求用户协助。如有需要,可调用 update_working_checkpoint 保存关键上下文。" diff --git a/llmcore.py b/llmcore.py index c7011d1..479045a 100644 --- a/llmcore.py +++ b/llmcore.py @@ -413,23 +413,28 @@ class ToolClient: self.total_cd_tokens = 0 def chat(self, messages, tools=None): - if self._should_use_structured_messages(messages): - return (yield from self._chat_structured(messages, tools)) - full_prompt = self._build_protocol_prompt(messages, tools) - print("Full prompt length:", len(full_prompt), 'chars') script_dir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f: - f.write(f"=== Prompt === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{full_prompt}\n") - gen = self.backend.ask(full_prompt, stream=True) + log_path = os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt') + if self._should_use_structured_messages(messages): + backend_messages = self._build_backend_messages(messages, tools) + print("Structured prompt length:", sum(self._estimate_content_len(m.get("content")) for m in backend_messages), 'chars') + prompt_log = self._serialize_messages_for_log(backend_messages) + gen = self.backend.raw_ask(backend_messages) + else: + full_prompt = self._build_protocol_prompt(messages, tools) + print("Full prompt length:", len(full_prompt), 'chars') + prompt_log = full_prompt + gen = self.backend.ask(full_prompt, stream=True) + with open(log_path, 'a', encoding='utf-8', errors="replace") as f: + f.write(f"=== Prompt === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{prompt_log}\n") raw_text = ''; summarytag = '[NextWillSummary]' for chunk in gen: - raw_text += chunk; + raw_text += chunk if chunk != summarytag: yield chunk print('Complete response received.') if raw_text.endswith(summarytag): self.last_tools = ''; raw_text = raw_text[:-len(summarytag)] - script_dir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f: + with open(log_path, 'a', encoding='utf-8', errors="replace") as f: f.write(f"=== Response === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{raw_text}\n\n") return self._parse_mixed_response(raw_text) @@ -506,24 +511,6 @@ class ToolClient: logged.append(msg) return json.dumps(logged, ensure_ascii=False, indent=2) - def _chat_structured(self, messages, tools): - backend_messages = self._build_backend_messages(messages, tools) - print("Structured prompt length:", sum(self._estimate_content_len(m.get("content")) for m in backend_messages), 'chars') - script_dir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f: - f.write(f"=== Prompt === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{self._serialize_messages_for_log(backend_messages)}\n") - gen = self.backend.raw_ask(backend_messages) - raw_text = ''; summarytag = '[NextWillSummary]' - for chunk in gen: - raw_text += chunk - if chunk != summarytag: yield chunk - print('Complete response received.') - if raw_text.endswith(summarytag): - self.last_tools = ''; raw_text = raw_text[:-len(summarytag)] - with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f: - f.write(f"=== Response === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{raw_text}\n\n") - return self._parse_mixed_response(raw_text) - def _build_protocol_prompt(self, messages, tools): system_content = next((m['content'] for m in messages if m['role'].lower() == 'system'), "") history_msgs = [m for m in messages if m['role'].lower() != 'system'] diff --git a/memory/autonomous_operation_sop.md b/memory/autonomous_operation_sop.md index 324610f..3cb8bf8 100644 --- a/memory/autonomous_operation_sop.md +++ b/memory/autonomous_operation_sop.md @@ -15,7 +15,7 @@ ## 执行 **启动**: -- update_working_checkpoint: `自主行动|报告→./autonomous_reports/R{XX}_简短描述.md|≤30回合|收尾:重读sop,写报告+更新history+标记TODO` +- update_working_checkpoint: `自主行动|报告→./autonomous_reports/R{XX}_简短描述.md|≤30回合|收尾:重读sop,写报告+更新history+标记TODO | 报告严禁放错位置` - 读 `./autonomous_reports/history.txt` 推断下一编号RXX + 了解历史避免重复 **执行**: diff --git a/memory/skill_search/SKILL.md b/memory/skill_search/SKILL.md index 9844b3e..e89b34b 100644 --- a/memory/skill_search/SKILL.md +++ b/memory/skill_search/SKILL.md @@ -1,116 +1,64 @@ -# Skill Search — API 客户端 +# Skill Search — 105K 技能卡检索 -> 从 10 万+ 技能卡中智能检索最适合当前环境的 skill。 +> 从 105K+ 技能卡中语义搜索最匹配的 skill。零依赖,内置默认 API 地址,开箱即用。 -## 架构 - -``` -┌─────────────┐ HTTPS/JSON ┌──────────────────┐ -│ 客户端 CLI │ ──────────────────▶ │ Skill Search │ -│ (本项目) │ ◀────────────────── │ API Server │ -└─────────────┘ └──────────────────┘ - • 环境检测 • 105K+ 技能卡索引 - • 结果格式化 • 四层漏斗检索引擎 - • 零数据依赖 • 环境过滤 → 安全标注 - → 语义匹配 → 质量排序 -``` - -## 快速开始 - -```bash -# 设置 API 地址 -export SKILL_SEARCH_API="https://your-server.com/api" -export SKILL_SEARCH_KEY="your-api-key" # 可选 - -# 搜索 -python -m skill_search "python testing" - -# 限定类别 -python -m skill_search "docker deploy" --category devops - -# JSON 输出(适合程序集成) -python -m skill_search "git workflow" --json - -# 查看环境信息 -python -m skill_search --env - -# 查看索引统计 -python -m skill_search --stats -``` - -## 编程接口 +## 最简调用 ```python -from skill_search import search, detect_environment - -env = detect_environment() -results = search(query="python testing", env=env, top_k=5) +import sys; sys.path.append('../memory/skill_search') +from skill_search import search +results = search("python send email") # ⚠️ 必须用英文查询,中文匹配效果极差 for r in results: - print(f"{r.skill.name} (score: {r.final_score:.2f})") - print(f" {r.skill.one_line_summary}") + s = r.skill + print(f"[{r.final_score:.2f}] {s.name} — {s.one_line_summary}") + print(f" key: {s.key} category: {s.category} tags: {s.tags[:3]}") ``` -## 文件结构 +## API 签名 -``` -skill_search/ -├── SKILL.md # 本文档 -└── skill_search/ # Python 包 - ├── __init__.py # 公开 API - ├── __main__.py # CLI 入口 - ├── engine.py # HTTP 客户端(替代本地检索) - ├── index.py # SkillIndex 数据模型 - ├── env_detect.py # 本地环境检测 - └── formatter.py # 结果格式化输出 +```python +search(query, env=None, category=None, top_k=10) -> list[SearchResult] +# env: 自动检测,一般不传 +# category: 可选过滤,如 "devops" +# top_k: 返回数量,默认10 ``` -## 环境变量 +## 返回结构 -| 变量 | 说明 | 默认值 | -|------|------|--------| -| `SKILL_SEARCH_API` | API 服务地址 | `https://skill-search.example.com/api` | -| `SKILL_SEARCH_KEY` | API 密钥(可选) | 无 | +``` +SearchResult + .final_score float 综合评分 (0~1) + .relevance float 语义相关度 + .quality float 质量分 + .match_reasons list[str] 匹配原因 + .warnings list[str] 警告 + .skill SkillIndex ↓ -## API 协议 - -### POST /search - -请求: -```json -{ - "query": "python testing", - "env": { "os": "windows", "shell": "powershell", ... }, - "category": "coding", - "top_k": 10 -} +SkillIndex (常用字段) + .key str 唯一标识/路径 + .name str 名称 + .one_line_summary str 一句话摘要 + .description str 详细描述 + .category str 类别 + .tags list[str] 标签 + .form str 形式(sop/script/...) + .autonomous_safe bool 是否自主安全 ``` -响应: -```json -{ - "results": [ - { - "skill": { "key": "org/repo/skill", "name": "...", ... }, - "relevance": 0.85, - "quality": 7.2, - "final_score": 0.78, - "match_reasons": ["完整短语匹配", "标签匹配: python"], - "warnings": [] - } - ] -} +## CLI + +```bash +python -m skill_search "python testing" +python -m skill_search "docker deployment" --category devops --top 5 +python -m skill_search "git" --json +python -m skill_search --stats +python -m skill_search --env ``` -### POST /stats +## 配置 -请求: `{}` 或 `{"env": {...}}` - -响应: -```json -{ - "total": 105586, - "safe_count": 98234, - "categories": { "coding": 45000, "devops": 12000, ... } -} -``` \ No newline at end of file +| 项 | 默认值 | 说明 | +|---|---|---| +| API地址 | `http://www.fudankw.cn:58787` | 环境变量 `SKILL_SEARCH_API` 可覆盖 | +| API密钥 | 无(可选) | 环境变量 `SKILL_SEARCH_KEY` | \ No newline at end of file