chore: self-commit core logic updates and agent state
This commit is contained in:
44
agentapp.py
44
agentapp.py
@@ -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
18
ga.py
@@ -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
|
||||||
66
sidercall.py
66
sidercall.py
@@ -11,42 +11,66 @@ 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):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user