diff --git a/agent_loop.py b/agent_loop.py
index f1d2c38..7f53380 100644
--- a/agent_loop.py
+++ b/agent_loop.py
@@ -48,7 +48,7 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
response = client.chat(messages=messages, tools=tools_schema)
if response.thinking: yield '' + response.thinking + '\n\n'
- if '```' in response.content: response.content = response.content.replace('```', ' \n```')
+ if '```' in response.content: response.content = response.content.replace('```', '\n```')
showcontent = response.content
if '' in showcontent:
showcontent = re.sub(r'\s*(.*?)\s*', r'\n````\n\n\1\n\n````', showcontent, flags=re.DOTALL)
diff --git a/agentapp.py b/agentapp.py
index cdadf58..27750f4 100644
--- a/agentapp.py
+++ b/agentapp.py
@@ -10,7 +10,6 @@ import time, json, re
with open('tools_schema.json', 'r', encoding='utf-8') as f:
TOOLS_SCHEMA = json.load(f)
-
st.set_page_config(page_title="Cowork", layout="wide")
from sidercall import SiderLLMSession, LLMSession, ToolClient
@@ -25,7 +24,7 @@ def init():
llmclient = init()
-from ga import GenericAgentHandler, smart_format
+from ga import GenericAgentHandler, smart_format, get_global_memory
def get_system_prompt():
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('')
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('sys_prompt.txt', 'r', encoding='utf-8') as f:
- 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"
- 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
+ with open('sys_prompt.txt', 'r', encoding='utf-8') as f: prompt = f.read()
+ prompt += get_global_memory()
return prompt
if "last_goal" not in st.session_state:
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):
- #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", [])
- rquery = smart_format(raw_query.replace('\n', ' '))
+ rquery = smart_format(raw_query.replace('\n', ' '), max_str_len=200)
history.append(f"[USER]: {rquery}")
sys_prompt = get_system_prompt()
@@ -85,7 +50,6 @@ def agent_backend_stream(raw_query):
ret = yield from agent_runner_loop(llmclient,
sys_prompt, raw_query, handler,
TOOLS_SCHEMA, max_turns=25)
- #st.session_state.last_goal = final_goal
st.session_state.last_history = handler.history_info
return ret
diff --git a/ga.py b/ga.py
index eb44228..a75fa7d 100644
--- a/ga.py
+++ b/ga.py
@@ -396,13 +396,13 @@ class GenericAgentHandler(BaseHandler):
'''Agent觉得当前任务完成后有重要信息需要记忆时调用此工具。
目前只支持全局记忆,暂不处理过程记忆或特定任务经验。
'''
- next_prompt = '''### [总结提炼经验] 既然你觉得当前任务有重要信息需要记忆,请提取最近一次任务中【事实验证成功且长期有效】的环境事实与用户偏好,更新至全局记忆。
+ prompt = '''### [总结提炼经验] 既然你觉得当前任务有重要信息需要记忆,请提取最近一次任务中【事实验证成功且长期有效】的环境事实与用户偏好,更新至全局记忆。
1. 严禁记录任何任务特定中间执行过程或临时变量经验,那是过程记忆不是全局记忆。
2. 若无高价值新事实,那就不更新任何内容。
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"
- return StepOutcome({"status": "success"}, next_prompt=next_prompt)
+ return StepOutcome({"status": "success"}, next_prompt=prompt)
def _get_anchor_prompt(self):
h_str = "\n".join(self.history_info[-20:])
@@ -411,3 +411,15 @@ class GenericAgentHandler(BaseHandler):
if self.plan: prompt += f"\n{self.plan}"
if self.focus: prompt += f"\n{self.focus}"
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
\ No newline at end of file
diff --git a/sidercall.py b/sidercall.py
index 5592c14..1418f49 100644
--- a/sidercall.py
+++ b/sidercall.py
@@ -11,41 +11,65 @@ class SiderLLMSession:
def __init__(self, multiturns=6):
self._core = Session(cookie=sider_cookie, proxies={'https':'127.0.0.1:2082'})
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))
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_base = api_base
+ self.raw_msgs = []
self.messages = []
- self.multiturns = multiturns
-
- def ask(self, prompt, model="openai/gpt-5.1"):
- self.messages.append({"role": "user", "content": prompt})
- if len(self.messages) > self.multiturns:
- self.messages = self.messages[-self.multiturns:]
- headers = {
- "Authorization": f"Bearer {self.api_key}",
- "Content-Type": "application/json"
- }
+ self.context_win = context_win
+
+ def raw_ask(self, messages, model, temperature=0.5):
+ headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
try:
response = requests.post(
- f"{self.api_base}/chat/completions",
- headers=headers,
- json={
- "model": model,
- "messages": self.messages,
- "temperature": 0.5
- },
- timeout=60
- )
+ f"{self.api_base}/chat/completions", headers=headers, timeout=60,
+ json={"model": model, "messages": messages, "temperature": temperature} )
res_json = response.json()
content = res_json["choices"][0]["message"]["content"]
- self.messages.append({"role": "assistant", "content": content})
return content
except Exception as 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:
def __init__(self, name, arguments):
diff --git a/simphtml.py b/simphtml.py
index 0f8caf2..18c277e 100644
--- a/simphtml.py
+++ b/simphtml.py
@@ -1,6 +1,6 @@
from bs4 import BeautifulSoup
-js_optHTML = '''function optHTML() {
+js_optHTML = r'''function optHTML() {
function createEnhancedDOMCopy() {
const nodeInfo = new WeakMap();
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 rect = containerElement.getBoundingClientRect();
const centerX = startElement ? (rect.left + rect.width/2) : (window.innerWidth/2);