chore: self-commit core logic updates and agent state

This commit is contained in:
Jiaqing Liang
2026-01-30 14:51:52 +08:00
parent 35ce8f7c9e
commit 01c477c050
5 changed files with 68 additions and 68 deletions

View File

@@ -48,7 +48,7 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
response = client.chat(messages=messages, tools=tools_schema) response = client.chat(messages=messages, tools=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```')
showcontent = response.content showcontent = response.content
if '</file_content>' in showcontent: if '</file_content>' in showcontent:
showcontent = re.sub(r'<file_content>\s*(.*?)\s*</file_content>', r'\n````\n<file_content>\n\1\n</file_content>\n````', showcontent, flags=re.DOTALL) showcontent = re.sub(r'<file_content>\s*(.*?)\s*</file_content>', r'\n````\n<file_content>\n\1\n</file_content>\n````', showcontent, flags=re.DOTALL)

View File

@@ -10,7 +10,6 @@ import time, json, re
with open('tools_schema.json', 'r', encoding='utf-8') as f: with open('tools_schema.json', 'r', encoding='utf-8') as f:
TOOLS_SCHEMA = json.load(f) TOOLS_SCHEMA = json.load(f)
st.set_page_config(page_title="Cowork", layout="wide") st.set_page_config(page_title="Cowork", layout="wide")
from sidercall import SiderLLMSession, LLMSession, ToolClient from sidercall import SiderLLMSession, LLMSession, ToolClient
@@ -25,7 +24,7 @@ def init():
llmclient = init() llmclient = init()
from ga import GenericAgentHandler, smart_format from ga import GenericAgentHandler, smart_format, get_global_memory
def get_system_prompt(): def get_system_prompt():
if not os.path.exists('memory'): os.makedirs('memory') if not os.path.exists('memory'): os.makedirs('memory')
@@ -33,50 +32,16 @@ def get_system_prompt():
with open('memory/global_mem.txt', 'w', encoding='utf-8') as f: f.write('') with open('memory/global_mem.txt', 'w', encoding='utf-8') as f: f.write('')
if not os.path.exists('memory/global_mem_insight.txt'): 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('') with open('memory/global_mem_insight.txt', 'w', encoding='utf-8') as f: f.write('')
with open('sys_prompt.txt', 'r', encoding='utf-8') as f: with open('sys_prompt.txt', 'r', encoding='utf-8') as f: prompt = f.read()
prompt = f.read() prompt += get_global_memory()
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"
prompt += 'IMPORTANT PATHS: ../memory/global_mem.txt (Facts), ../memory/global_mem_insight.txt (Logic), ../ (Your Code Root).\n'
prompt += 'MEM_RULE: Insight is the index of Facts. Sync Insight whenever Facts change. For details, read Facts.\n'
prompt += "EXT: ../memory/ may contain other task-specific memories.\n"
prompt += insight + "\n"
except FileNotFoundError: pass
return prompt 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 = ""
def refine_user_goal(raw_query, last_goal):
"""通过 LLM 提炼用户真实意图"""
if not last_goal:
return raw_query
decide_prompt = f"""
用户之前的目标是: "{last_goal}"
用户现在输入了: "{raw_query}"
请判断:
1. 如果用户提供补充信息、或者是接续之前的任务,请输出合并后的【最终目标】。
2. 如果用户只是指出之前做法有错而非变更目标,那么请输出原目标不做修改。
3. 如果用户开启了一个完全不相关的新话题,请直接输出用户现在的输入内容。
请直接输出目标描述,不要包含任何多余的文字、解释或标点。
"""
try:
refined = llmclient.llm_func(decide_prompt).strip()
return refined if refined else raw_query
except:
return raw_query
def agent_backend_stream(raw_query): def agent_backend_stream(raw_query):
#final_goal = refine_user_goal(raw_query, st.session_state.last_goal)
#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", [])
rquery = smart_format(raw_query.replace('\n', ' ')) rquery = smart_format(raw_query.replace('\n', ' '), max_str_len=200)
history.append(f"[USER]: {rquery}") history.append(f"[USER]: {rquery}")
sys_prompt = get_system_prompt() sys_prompt = get_system_prompt()
@@ -85,7 +50,6 @@ def agent_backend_stream(raw_query):
ret = yield from agent_runner_loop(llmclient, ret = yield from agent_runner_loop(llmclient,
sys_prompt, raw_query, handler, sys_prompt, raw_query, handler,
TOOLS_SCHEMA, max_turns=25) TOOLS_SCHEMA, max_turns=25)
#st.session_state.last_goal = final_goal
st.session_state.last_history = handler.history_info st.session_state.last_history = handler.history_info
return ret return ret

18
ga.py
View File

@@ -396,13 +396,13 @@ class GenericAgentHandler(BaseHandler):
'''Agent觉得当前任务完成后有重要信息需要记忆时调用此工具。 '''Agent觉得当前任务完成后有重要信息需要记忆时调用此工具。
目前只支持全局记忆,暂不处理过程记忆或特定任务经验。 目前只支持全局记忆,暂不处理过程记忆或特定任务经验。
''' '''
next_prompt = '''### [总结提炼经验] 既然你觉得当前任务有重要信息需要记忆,请提取最近一次任务中【事实验证成功且长期有效】的环境事实与用户偏好,更新至全局记忆。 prompt = '''### [总结提炼经验] 既然你觉得当前任务有重要信息需要记忆,请提取最近一次任务中【事实验证成功且长期有效】的环境事实与用户偏好,更新至全局记忆。
1. 严禁记录任何任务特定中间执行过程或临时变量经验,那是过程记忆不是全局记忆。 1. 严禁记录任何任务特定中间执行过程或临时变量经验,那是过程记忆不是全局记忆。
2. 若无高价值新事实,那就不更新任何内容。 2. 若无高价值新事实,那就不更新任何内容。
3. 尽量先查看现有全局记忆形式仅作少量修改不要影响其余部分。insight也要同步更新全局记忆的短印象来提醒存在性。 3. 尽量先查看现有全局记忆形式仅作少量修改不要影响其余部分。insight也要同步更新全局记忆的短印象来提醒存在性。
4. 优先使用file_read和file_patch来保证少量修改。''' 4. 优先使用file_read和file_patch来保证少量修改。''' + get_global_memory()
yield "[Info] Start distilling good memory for long-term storage.\n" yield "[Info] Start distilling good memory for long-term storage.\n"
return StepOutcome({"status": "success"}, next_prompt=next_prompt) return StepOutcome({"status": "success"}, next_prompt=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:])
@@ -411,3 +411,15 @@ class GenericAgentHandler(BaseHandler):
if self.plan: prompt += f"\n<plan>{self.plan}</plan>" if self.plan: prompt += f"\n<plan>{self.plan}</plan>"
if self.focus: prompt += f"\n<focus>{self.focus}</focus>" if self.focus: prompt += f"\n<focus>{self.focus}</focus>"
return prompt + "\n请继续执行下一步。" return prompt + "\n请继续执行下一步。"
def get_global_memory():
prompt = "\n"
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"
prompt += 'IMPORTANT PATHS: ../memory/global_mem.txt (Facts), ../memory/global_mem_insight.txt (Logic), ../ (Your Code Root).\n'
prompt += 'MEM_RULE: Insight is the index of Facts. Sync Insight whenever Facts change. For details, read Facts.\n'
prompt += "EXT: ../memory/ may contain other task-specific memories.\n"
prompt += insight + "\n"
except FileNotFoundError: pass
return prompt

View File

@@ -11,41 +11,65 @@ class SiderLLMSession:
def __init__(self, multiturns=6): def __init__(self, multiturns=6):
self._core = Session(cookie=sider_cookie, proxies={'https':'127.0.0.1:2082'}) self._core = Session(cookie=sider_cookie, proxies={'https':'127.0.0.1:2082'})
def ask(self, prompt, model="gemini-3.0-flash"): def ask(self, prompt, model="gemini-3.0-flash"):
if len(prompt) > 30000: prompt = prompt[-29500:] if len(prompt) > 29000:
print(f"[Warn] Prompt too long ({len(prompt)} chars), truncating.")
prompt = prompt[-29000:]
return ''.join(self._core.chat(prompt, model)) return ''.join(self._core.chat(prompt, model))
class LLMSession: class LLMSession:
def __init__(self, api_key=capikey, api_base="http://113.45.39.247:3001/v1", multiturns=6): def __init__(self, api_key=capikey, api_base="http://113.45.39.247:3001/v1", multiturns=6, context_win=32000):
self.api_key = api_key self.api_key = api_key
self.api_base = api_base self.api_base = api_base
self.raw_msgs = []
self.messages = [] self.messages = []
self.multiturns = multiturns self.context_win = context_win
def ask(self, prompt, model="openai/gpt-5.1"): def raw_ask(self, messages, model, temperature=0.5):
self.messages.append({"role": "user", "content": prompt}) headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
if len(self.messages) > self.multiturns:
self.messages = self.messages[-self.multiturns:]
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
try: try:
response = requests.post( response = requests.post(
f"{self.api_base}/chat/completions", f"{self.api_base}/chat/completions", headers=headers, timeout=60,
headers=headers, json={"model": model, "messages": messages, "temperature": temperature} )
json={
"model": model,
"messages": self.messages,
"temperature": 0.5
},
timeout=60
)
res_json = response.json() res_json = response.json()
content = res_json["choices"][0]["message"]["content"] content = res_json["choices"][0]["message"]["content"]
self.messages.append({"role": "assistant", "content": content})
return content return content
except Exception as e: except Exception as e:
return f"Error: {str(e)}" return f"Error: {str(e)}"
def make_messages(self, raw_list, omit_images=True):
messages = []
for msg in raw_list:
if omit_images and msg['image']:
messages.append({"role": msg['role'], "content": "[Image omitted, if you needed it, ask me]\n" + msg['prompt']})
elif not omit_images and msg['image']:
messages.append({"role": msg['role'], "content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{msg['image']}"}},
{"type": "text", "text": msg['prompt']} ]})
else:
messages.append({"role": msg['role'], "content": msg['prompt']})
return messages
def summary_history(self, model):
keep = max(2, len(self.raw_msgs)//2)
old, self.raw_msgs = self.raw_msgs[:-keep], self.raw_msgs[-keep:]
if len(old) == 0: old = self.raw_msgs; self.raw_msgs = []
p = "Summarize prev summary and prev conversations into compact memory (facts/decisions/constraints/open questions). Do NOT restate long schemas. The new summary should less than 1000 tokens.\n"
messages = self.make_messages(old, omit_images=True)
messages += [{"role":"user", "content":p}]
self.summary = self.raw_ask(messages, model, temperature=0.1)
self.raw_msgs.insert(0, {"role":"system", "prompt":"Prev summary:\n"+self.summary, "image":None})
def ask(self, prompt, model="openai/gpt-5.1", image_base64=None):
self.raw_msgs.append({"role": "user", "prompt": prompt, "image": image_base64})
messages = self.make_messages(self.raw_msgs[:-1], omit_images=True)
messages += self.make_messages([self.raw_msgs[-1]], omit_images=False)
total_len = sum(2000 if isinstance(m["content"], list) else len(str(m["content"]))//4 for m in messages) # estimate token count
content = self.raw_ask(messages, model)
if not content.startswith("Error:"):
self.raw_msgs.append({"role": "assistant", "prompt": content, "image": None})
if total_len > self.context_win: self.summary_history(model)
return content
class MockFunction: class MockFunction:
def __init__(self, name, arguments): def __init__(self, name, arguments):

View File

@@ -1,6 +1,6 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
js_optHTML = '''function optHTML() { js_optHTML = r'''function optHTML() {
function createEnhancedDOMCopy() { function createEnhancedDOMCopy() {
const nodeInfo = new WeakMap(); const nodeInfo = new WeakMap();
const ignoreTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK', 'COLGROUP', 'COL', 'TEMPLATE', 'PARAM', 'SOURCE']; const ignoreTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK', 'COLGROUP', 'COL', 'TEMPLATE', 'PARAM', 'SOURCE'];
@@ -237,7 +237,7 @@ optHTML()'''
js_findMainList = '''function findMainList(startElement = null) { js_findMainList = r'''function findMainList(startElement = null) {
const containerElement = startElement || document.body; const containerElement = startElement || document.body;
const rect = containerElement.getBoundingClientRect(); const rect = containerElement.getBoundingClientRect();
const centerX = startElement ? (rect.left + rect.width/2) : (window.innerWidth/2); const centerX = startElement ? (rect.left + rect.width/2) : (window.innerWidth/2);