chore: auto update by agent

This commit is contained in:
Jiaqing Liang
2026-01-29 15:34:22 +08:00
parent 424ddc4ab1
commit 32a5af2608
5 changed files with 126 additions and 223 deletions

3
.gitignore vendored
View File

@@ -34,3 +34,6 @@ auth.json
model_responses.txt model_responses.txt
*.zip *.zip
# 存储敏感信息的记忆文件夹
memory/

View File

@@ -1,4 +1,4 @@
import json import json, re
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Optional from typing import Any, Optional
@dataclass @dataclass
@@ -49,7 +49,10 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
if response.thinking: yield '<thinking>' + response.thinking + '</thinking>\n\n' if response.thinking: yield '<thinking>' + response.thinking + '</thinking>\n\n'
if '</summary>```' in response.content: response.content = response.content.replace('</summary>```', '</summary> \n```') if '</summary>```' in response.content: response.content = response.content.replace('</summary>```', '</summary> \n```')
yield response.content + '\n\n' showcontent = response.content
if '</file_content>' in showcontent:
showcontent = re.sub(r'<file_content>\s*(.*?)\s*</file_content>', r'\n````<file_content>\n\1\n</file_content>````', showcontent, flags=re.DOTALL)
yield showcontent + '\n\n'
if not response.tool_calls: if not response.tool_calls:
tool_name, args = 'no_tool', {} tool_name, args = 'no_tool', {}

View File

@@ -18,6 +18,7 @@ from agent_loop import agent_runner_loop, StepOutcome, BaseHandler
@st.cache_resource @st.cache_resource
def init(): def init():
if not os.path.exists('temp'): os.makedirs('temp')
mainllm = SiderLLMSession(multiturns=6) mainllm = SiderLLMSession(multiturns=6)
llmclient = ToolClient(mainllm.ask, auto_save_tokens=True) llmclient = ToolClient(mainllm.ask, auto_save_tokens=True)
return llmclient return llmclient
@@ -27,8 +28,20 @@ llmclient = init()
from ga import GenericAgentHandler, smart_format from ga import GenericAgentHandler, smart_format
def get_system_prompt(): def get_system_prompt():
if not os.path.exists('memory'): os.makedirs('memory')
if not os.path.exists('memory/global_mem.txt'):
with open('memory/global_mem.txt', 'w', encoding='utf-8') as f: f.write('')
if not os.path.exists('memory/global_mem_insight.txt'):
with open('memory/global_mem_insight.txt', 'w', encoding='utf-8') as f:
f.write('PATHS: ../memory/global_mem.txt (Facts), ../memory/global_mem_insight.txt (Logic), ../ (Code Root).')
with open('sys_prompt.txt', 'r', encoding='utf-8') as f: with open('sys_prompt.txt', 'r', encoding='utf-8') as f:
return f.read() prompt = f.read()
try:
with open('memory/global_mem_insight.txt', 'r', encoding='utf-8') as f:
insight = f.read()
prompt += f"\n\n[Global Memory Insight]\n{insight}"
except FileNotFoundError: pass
return prompt
if "last_goal" not in st.session_state: if "last_goal" not in st.session_state:
st.session_state.last_goal = "" st.session_state.last_goal = ""
@@ -60,8 +73,7 @@ def agent_backend_stream(raw_query):
#if final_goal != raw_query: yield f"[Goal Refined] {final_goal}\n" #if final_goal != raw_query: yield f"[Goal Refined] {final_goal}\n"
history = st.session_state.get("last_history", []) history = st.session_state.get("last_history", [])
hquery = smart_format(raw_query.replace('\n', ' '), max_str_len=100) history.append(f"[USER]: {smart_format(raw_query.replace('\n', ' '))}")
history.append(f"[USER]: {hquery}")
sys_prompt = get_system_prompt() sys_prompt = get_system_prompt()
handler = GenericAgentHandler(None, history, './temp') handler = GenericAgentHandler(None, history, './temp')

47
ga.py
View File

@@ -1,6 +1,6 @@
import sys, os, re, json, time, pyperclip, threading import sys, os, re, json, time, pyperclip, threading
from pathlib import Path from pathlib import Path
import tempfile, traceback, subprocess import tempfile, traceback, subprocess, itertools, collections
if sys.stdout is None: sys.stdout = open(os.devnull, "w") if sys.stdout is None: sys.stdout = open(os.devnull, "w")
if sys.stderr is None: sys.stderr = open(os.devnull, "w") if sys.stderr is None: sys.stderr = open(os.devnull, "w")
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
@@ -193,14 +193,25 @@ def file_patch(path: str, old_content: str, new_content: str):
except Exception as e: except Exception as e:
return {"status": "error", "msg": str(e)} return {"status": "error", "msg": str(e)}
def file_read(path, start=1, count=100, show_linenos=True): def file_read(path, start=1, keyword=None, count=100, show_linenos=True):
L_MAX = max(100, 1024000//count); TAG = " ... [TRUNCATED]"
try: try:
with open(path, 'r', encoding='utf-8', errors='replace') as f: with open(path, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines() stream = (
chunk = lines[start-1 : start-1+count] (i, (l[:L_MAX].rstrip() + TAG if len(l) > L_MAX else l.rstrip()))
if show_linenos: res = [f"{i+start}|{l[:200]}" for i, l in enumerate(chunk)] for i, l in enumerate(f, 1)
else: res = [l for l in chunk] )
return f"Total:{len(lines)} lines\n" + "".join(res) stream = itertools.dropwhile(lambda x: x[0] < start, stream)
if keyword:
before = collections.deque(maxlen=count//3)
for i, l in stream:
if keyword.lower() in l.lower():
res = list(before) + [(i, l)] + list(itertools.islice(stream, count - len(before) - 1))
break
before.append((i, l))
else: return f"Keyword '{keyword}' not found after line {start}."
else: res = itertools.islice(stream, count)
return "\n".join(f"{i}|{l}" if show_linenos else l for i, l in res)
except Exception as e: except Exception as e:
return f"Error: {str(e)}" return f"Error: {str(e)}"
@@ -323,7 +334,7 @@ class GenericAgentHandler(BaseHandler):
blocks = extract_robust_content(response.content) blocks = extract_robust_content(response.content)
if not blocks: if not blocks:
yield f"[Status] ❌ 失败: 未在回复中找到代码块内容\n" yield f"[Status] ❌ 失败: 未在回复中找到代码块内容\n"
return StepOutcome({"status": "error", "msg": "No code block found in response"}, next_prompt="\n") return StepOutcome({"status": "error", "msg": "No content found, if you want a blank, you should use code_run"}, next_prompt="\n")
new_content = blocks new_content = blocks
try: try:
write_mode = 'a' if mode == "append" else 'w' write_mode = 'a' if mode == "append" else 'w'
@@ -339,12 +350,15 @@ class GenericAgentHandler(BaseHandler):
return StepOutcome({"status": "error", "msg": str(e)}, next_prompt="\n") return StepOutcome({"status": "error", "msg": str(e)}, next_prompt="\n")
def do_file_read(self, args, response): def do_file_read(self, args, response):
'''读取文件内容。从第start行开始读取。如有keyword则返回第一个keyword(忽略大小写)周边内容'''
path = self._get_abs_path(args.get("path", "")) path = self._get_abs_path(args.get("path", ""))
yield f"\n[Action] Reading file: {path}\n" yield f"\n[Action] Reading file: {path}\n"
start = args.get("start", 1) start = args.get("start", 1)
count = args.get("count", 100) count = args.get("count", 100)
keyword = args.get("keyword")
show_linenos = args.get("show_linenos", True) show_linenos = args.get("show_linenos", True)
result = file_read(path, start, count, show_linenos) result = file_read(path, start=start, keyword=keyword,
count=count, show_linenos=show_linenos)
next_prompt = self._get_anchor_prompt() next_prompt = self._get_anchor_prompt()
return StepOutcome(result, next_prompt=next_prompt) return StepOutcome(result, next_prompt=next_prompt)
@@ -370,9 +384,24 @@ class GenericAgentHandler(BaseHandler):
def do_no_tool(self, args, response): def do_no_tool(self, args, response):
'''这是一个特殊工具由引擎自主调用不要包含在TOOLS_SCHEMA里。 '''这是一个特殊工具由引擎自主调用不要包含在TOOLS_SCHEMA里。
''' '''
if not response or not getattr(response, 'content', '').strip():
yield "[Warn] LLM returned an empty response. Retrying...\n"
next_prompt = "[System] 检测到空回复,请重新生成内容或调用工具。"
return StepOutcome({}, next_prompt=next_prompt, should_exit=False)
yield "[Info] No tool called. Final response to user.\n" yield "[Info] No tool called. Final response to user.\n"
return StepOutcome(response, next_prompt=None, should_exit=True) return StepOutcome(response, next_prompt=None, should_exit=True)
def do_distill_good_memory(self, args, response):
'''Agent觉得当前任务完成后有重要信息需要记忆时调用此工具。
目前只支持全局记忆,暂不处理过程记忆或特定任务经验。
'''
next_prompt = '''### [总结提炼经验] 既然你觉得当前任务有重要信息需要记忆,请提取最近一次任务中【事实验证成功且长期有效】的环境事实与用户偏好,更新至全局记忆。
1. 严禁记录任何任务特定中间执行过程或临时变量经验,那是过程记忆不是全局记忆。
2. 若无高价值新事实,那就不更新任何内容。
3. 尽量先查看现有全局记忆形式仿照形式且避免冗余insight也要添加对全局记忆的短印象来提醒存在性。'''
yield "[Info] Start distilling good memory for long-term storage.\n"
return StepOutcome({"status": "success"}, next_prompt=next_prompt)
def _get_anchor_prompt(self): def _get_anchor_prompt(self):
h_str = "\n".join(self.history_info[-20:]) h_str = "\n".join(self.history_info[-20:])
prompt = f"\n### [WORKING MEMORY]\n<history>\n{h_str}\n</history>" prompt = f"\n### [WORKING MEMORY]\n<history>\n{h_str}\n</history>"

View File

@@ -1,212 +1,68 @@
[ [
{ {"type": "function", "function": {
"type": "function", "name": "code_run",
"function": { "description": "针对 Windows 优化的双模态代码执行器。优先使用 python 运行复杂逻辑,仅在必要系统操作时使用 powershell。注意执行的代码必须以 ```python 或 ```powershell 代码块的形式包含在回复正文中。严禁在代码中硬编码大量数据,如有需要应通过文件读取。执行时间限制为 60s。",
"name": "code_run", "parameters": {"type": "object", "properties": {
"description": "针对 Windows 优化的双模态代码执行器。优先使用 python 运行复杂逻辑,仅在必要系统操作时使用 powershell。注意执行的代码必须以 ```python 或 ```powershell 代码块的形式包含在回复正文中。严禁在代码中硬编码大量数据,如有需要应通过文件读取。执行时间限制为 60s。", "type": {"type": "string", "enum": ["python", "powershell"], "description": "执行环境类型,默认为 python。", "default": "python"},
"parameters": { "timeout": {"type": "integer", "description": "执行超时时间(秒),默认 60。", "default": 60},
"type": "object", "cwd": {"type": "string", "description": "工作目录,默认为当前工作目录。"}}}
"properties": { }},
"type": { {"type": "function", "function": {
"type": "string", "name": "file_read",
"enum": [ "description": "读取文件内容。建议在修改文件前先读取,以确保获取最新的上下文和行号。支持分页读取或关键字搜索。",
"python", "parameters": {"type": "object", "properties": {
"powershell" "path": {"type": "string", "description": "文件相对或绝对路径。"},
], "start": {"type": "integer", "description": "起始行号(从 1 开始)。", "default": 1},
"description": "执行环境类型,默认为 python。", "count": {"type": "integer", "description": "读取的行数。", "default": 100},
"default": "python" "keyword": {"type": "string", "description": "可选搜索关键字。如果提供,将返回第一个匹配项(忽略大小写)及其周边的内容。"},
}, "show_linenos": {"type": "boolean", "description": "是否显示行号,建议开启以辅助 file_patch 定位。", "default": true}}, "required": ["path"]}
"timeout": { }},
"type": "integer", {"type": "function", "function": {
"description": "执行超时时间(秒),默认 60。", "name": "file_patch",
"default": 60 "description": "精细化局部文件修改。在文件中寻找唯一的 old_content 块并替换为 new_content。要求 old_content 必须在文件中唯一存在,且空格、缩进、换行必须与原文件完全一致。如果匹配失败,请使用 file_read 重新确认文件内容。",
}, "parameters": {"type": "object", "properties": {
"cwd": { "path": {"type": "string", "description": "文件路径。"},
"type": "string", "old_content": {"type": "string", "description": "文件中需要被替换的原始文本块(需确保唯一性)。"},
"description": "工作目录,默认为当前工作目录。" "new_content": {"type": "string", "description": "替换后的新文本内容。"}}, "required": ["path", "old_content", "new_content"]}
} }},
} {"type": "function", "function": {
} "name": "file_write",
} "description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意要写入的内容必须放在回复正文的 <file_content> 标签或代码块中。",
}, "parameters": {"type": "object", "properties": {
{ "path": {"type": "string", "description": "文件路径。"},
"type": "function", "mode": {"type": "string", "enum": ["overwrite", "append"], "description": "写入模式overwrite覆盖默认或 append追加。", "default": "overwrite"}}, "required": ["path"]}
"function": { }},
"name": "file_read", {"type": "function", "function": {
"description": "读取文件内容。建议在修改文件前先读取,以确保获取最新的上下文和行号。支持分页读取,默认每次读取 100 行。", "name": "web_scan",
"parameters": { "description": "获取当前网页的清洗后内容,并列出所有已打开的标签页。支持切换标签页。在长页面中,可以使用 focus_item 进行语义过滤以提取关键信息。",
"type": "object", "parameters": {"type": "object", "properties": {
"properties": { "focus_item": {"type": "string", "description": "语义过滤指令,用于在长列表中优先保留与该关键词相关的项。"},
"path": { "switch_tab_id": {"type": "string", "description": "可选的标签页 ID。如果提供系统将在扫描前切换到该标签页。"}}}
"type": "string", }},
"description": "文件相对或绝对路径。" {"type": "function", "function": {
}, "name": "web_execute_js",
"start": { "description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制(如点击、滚动、提取特定数据)。这是 Web 场景下的首选工具。执行结果可选择保存到本地文件进行后续分析。",
"type": "integer", "parameters": {"type": "object", "properties": {
"description": "起始行号(从 1 开始)。", "script": {"type": "string", "description": "要执行的 JavaScript 代码。"},
"default": 1 "save_to_file": {"type": "string", "description": "可选。将 JS 执行结果js_return保存到的文件路径。注意该功能不支持 await 等异步结果。"}}, "required": ["script"]}
}, }},
"count": { {"type": "function", "function": {
"type": "integer", "name": "update_plan",
"description": "读取的行数。", "description": "更新任务的宏观计划和当前战略重心。仅在初始拆解多步任务或发生重大方案调整时使用。禁止用于记录细微调试步骤或纠错。",
"default": 100 "parameters": {"type": "object", "properties": {
}, "plan": {"type": "string", "description": "完整的宏观任务路线图。"},
"show_linenos": { "focus": {"type": "string", "description": "当前阶段的工作重点。"}}}
"type": "boolean", }},
"description": "是否显示行号,建议开启以辅助 file_patch 定位。", {"type": "function", "function": {
"default": true "name": "ask_user",
} "description": "当需要用户决策、提供额外信息或遇到无法自动解决的阻碍时,调用此工具中断任务并提问。",
}, "parameters": {"type": "object", "properties": {
"required": [ "question": {"type": "string", "description": "向用户提出的明确问题。"},
"path" "candidates": {"type": "array", "items": {"type": "string"}, "description": "提供给用户的可选快捷选项列表。"}}, "required": ["question"]}
] }},
} {"type": "function", "function": {
} "name": "distill_good_memory",
}, "description": "当模型认为当前任务执行完美,且有具有长期价值的环境事实或用户偏好需要提炼并存入全局记忆时,调用此工具。注意:此工具无参数,调用即代表触发记忆提炼流程。",
{ "parameters": {"type": "object", "properties": {}}}
"type": "function",
"function": {
"name": "file_patch",
"description": "精细化局部文件修改。在文件中寻找唯一的 old_content 块并替换为 new_content。要求 old_content 必须在文件中唯一存在,且空格、缩进、换行必须与原文件完全一致。如果匹配失败,请使用 file_read 重新确认文件内容。",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "文件路径。"
},
"old_content": {
"type": "string",
"description": "文件中需要被替换的原始文本块(需确保唯一性)。"
},
"new_content": {
"type": "string",
"description": "替换后的新文本内容。"
}
},
"required": [
"path",
"old_content",
"new_content"
]
}
}
},
{
"type": "function",
"function": {
"name": "file_write",
"description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意要写入的内容必须放在回复正文的 <file_content> 标签或代码块中。",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "文件路径。"
},
"mode": {
"type": "string",
"enum": [
"overwrite",
"append"
],
"description": "写入模式overwrite覆盖默认或 append追加。",
"default": "overwrite"
}
},
"required": [
"path"
]
}
}
},
{
"type": "function",
"function": {
"name": "web_scan",
"description": "获取当前网页的清洗后内容,并列出所有已打开的标签页。支持切换标签页。在长页面中,可以使用 focus_item 进行语义过滤以提取关键信息。",
"parameters": {
"type": "object",
"properties": {
"focus_item": {
"type": "string",
"description": "语义过滤指令,用于在长列表中优先保留与该关键词相关的项。"
},
"switch_tab_id": {
"type": "string",
"description": "可选的标签页 ID。如果提供系统将在扫描前切换到该标签页。"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "web_execute_js",
"description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制(如点击、滚动、提取特定数据)。这是 Web 场景下的首选工具。执行结果可选择保存到本地文件进行后续分析。",
"parameters": {
"type": "object",
"properties": {
"script": {
"type": "string",
"description": "要执行的 JavaScript 代码。"
},
"save_to_file": {
"type": "string",
"description": "可选。将 JS 执行结果js_return保存到的文件路径。注意该功能不支持 await 等异步结果。"
}
},
"required": [
"script"
]
}
}
},
{
"type": "function",
"function": {
"name": "update_plan",
"description": "更新任务的宏观计划和当前战略重心。仅在初始拆解多步任务或发生重大方案调整时使用。禁止用于记录细微调试步骤或纠错。",
"parameters": {
"type": "object",
"properties": {
"plan": {
"type": "string",
"description": "完整的宏观任务路线图。"
},
"focus": {
"type": "string",
"description": "当前阶段的工作重点。"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "ask_user",
"description": "当需要用户决策、提供额外信息或遇到无法自动解决的阻碍时,调用此工具中断任务并提问。",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "向用户提出的明确问题。"
},
"candidates": {
"type": "array",
"items": {
"type": "string"
},
"description": "提供给用户的可选快捷选项列表。"
}
},
"required": [
"question"
]
}
}
} }
] ]