hotreload mykeys, fix SSE residual block, unify context_win, cleanup

This commit is contained in:
Liang Jiaqing committed 2026-04-26 17:46:24 +08:00
1 parent 89b45f3d1d
commit e07c1b046b
5 files changed
+60 -44

No files matched your search

+25 -21
View File
@@ -1,16 +1,12 @@
import os, sys, threading, queue, time, json, re, random, locale
os.environ.setdefault('GA_LANG', 'zh' if any(k in (locale.getlocale()[0] or '').lower() for k in ('zh', 'chinese')) else 'en')
try:
import readline
except Exception:
readline = None
if sys.stdout is None: sys.stdout = open(os.devnull, "w")
elif hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(errors='replace')
if sys.stderr is None: sys.stderr = open(os.devnull, "w")
elif hasattr(sys.stderr, 'reconfigure'): sys.stderr.reconfigure(errors='replace')
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from llmcore import LLMSession, ToolClient, ClaudeSession, MixinSession, NativeToolClient, NativeClaudeSession, NativeOAISession
from llmcore import reload_mykeys, LLMSession, ToolClient, ClaudeSession, MixinSession, NativeToolClient, NativeClaudeSession, NativeOAISession
from agent_loop import agent_runner_loop
from ga import GenericAgentHandler, smart_format, get_global_memory, format_error, consume_file
@@ -47,7 +43,20 @@ class GeneraticAgent:
def __init__(self):
script_dir = os.path.dirname(os.path.abspath(__file__))
os.makedirs(os.path.join(script_dir, 'temp'), exist_ok=True)
from llmcore import mykeys
self.lock = threading.Lock()
self.task_dir = None
self.history = []
self.task_queue = queue.Queue()
self.is_running = False; self.stop_sig = False
self.llm_no = 0; self.inc_out = False
self.handler = None; self.verbose = True
self.load_llm_sessions()
def load_llm_sessions(self):
mykeys, changed = reload_mykeys()
if not changed and hasattr(self, 'llmclients'): return
try: oldhistory = self.llmclient.backend.history
except: oldhistory = None
llm_sessions = []
for k, cfg in mykeys.items():
if not any(x in k for x in ['api', 'config', 'cookie']): continue
@@ -66,16 +75,11 @@ class GeneraticAgent:
else: llm_sessions[i] = ToolClient(mixin)
except Exception as e: print(f'[WARN] Failed to init MixinSession with cfg {s["mixin_cfg"]}: {e}')
self.llmclients = llm_sessions
self.lock = threading.Lock()
self.task_dir = None
self.history = []
self.task_queue = queue.Queue()
self.is_running = False; self.stop_sig = False
self.llm_no = 0; self.inc_out = False
self.handler = None; self.verbose = True
self.llmclient = self.llmclients[self.llm_no]
self.llmclient = self.llmclients[self.llm_no%len(self.llmclients)]
if oldhistory: self.llmclient.backend.history = oldhistory
def next_llm(self, n=-1):
self.load_llm_sessions()
self.llm_no = ((self.llm_no + 1) if n < 0 else n) % len(self.llmclients)
lastc = self.llmclient
self.llmclient = self.llmclients[self.llm_no]
@@ -85,7 +89,9 @@ class GeneraticAgent:
name = self.get_llm_name(model=True)
if 'glm' in name or 'minimax' in name or 'kimi' in name: load_tool_schema('_cn')
else: load_tool_schema()
def list_llms(self): return [(i, self.get_llm_name(b), i == self.llm_no) for i, b in enumerate(self.llmclients)]
def list_llms(self):
self.load_llm_sessions()
return [(i, self.get_llm_name(b), i == self.llm_no) for i, b in enumerate(self.llmclients)]
def get_llm_name(self, b=None, model=False):
b = self.llmclient if b is None else b
if isinstance(b, dict): return 'BADCONFIG_MIXIN'
@@ -139,12 +145,8 @@ class GeneraticAgent:
handler.working['passed_sessions'] = ps = self.handler.working.get('passed_sessions', 0) + 1
if ps > 0: handler.working['key_info'] += f'\n[SYSTEM] 此为 {ps} 个对话前设置的key_info,若已在新任务,先更新或清除工作记忆。\n'
self.handler = handler
user_input = raw_query
if source == 'feishu' and len(self.history) > 1: # 如果有历史记录且来自飞书,注入到首轮 user_input 中(支持/restore恢复上下文)
user_input = handler._get_anchor_prompt() + f"\n\n### 用户当前消息\n{raw_query}"
#if 'gpt' in self.get_llm_name(model=True): handler._done_hooks.append('请确定任务是否完成,如果完成请给出信息完整的简报回答,如未完成需要继续工具调用直到完成任务,确实需要问用户应使用ask_user工具')
# although new handler, the **full** history is in llmclient, so it is full history!
gen = agent_runner_loop(self.llmclient, sys_prompt, user_input,
gen = agent_runner_loop(self.llmclient, sys_prompt, raw_query,
handler, TOOLS_SCHEMA, max_turns=70, verbose=self.verbose)
try:
full_resp = ""; last_pos = 0
@@ -251,6 +253,8 @@ if __name__ == '__main__':
except Exception as e: print(f'[Reflect] on_done error: {e}')
if getattr(mod, 'ONCE', False): print('[Reflect] ONCE=True, exiting.'); break
else:
try: import readline
except Exception: pass
agent.inc_out = True
while True:
q = input('> ').strip()
-3
View File
@@ -24,7 +24,4 @@ def _pinit(self, *a, **k):
if os.name == 'nt': k['creationflags'] = (k.get('creationflags') or 0) | 0x08000000
_Pi(self, *a, **k)
subprocess.Popen.__init__ = _pinit
if hasattr(os, 'startfile'):
def _nosf(*a, **k): raise RuntimeError("startfile disabled in sandbox")
os.startfile = _nosf
sys.excepthook = lambda t, v, tb: (sys.__excepthook__(t, v, tb), print(f"\n[Agent Hint]: NO GUESSING! You MUST probe first. If missing common package, pip.")) if issubclass(t, (ImportError, AttributeError)) else sys.__excepthook__(t, v, tb)
+6 -3
View File
@@ -36,13 +36,16 @@ if 'autonomous_enabled' not in st.session_state: st.session_state.autonomous_ena
@st.fragment
def render_sidebar():
llm_options = agent.list_llms()
current_idx = agent.llm_no
st.caption(f"LLM Core: {current_idx}: {agent.get_llm_name()}", help="点击切换备用链路")
llm_labels = {idx: f"{idx}: {(name or '').strip()}" for idx, name, _ in llm_options}
st.caption(f"LLM Core: {llm_labels.get(current_idx, str(current_idx))}", help="下拉切换备用链路")
selected_idx = st.selectbox("备用链路", [idx for idx, _, _ in llm_options], index=next((i for i, (idx, _, _) in enumerate(llm_options) if idx == current_idx), 0), format_func=llm_labels.get, label_visibility="collapsed", key="sidebar_llm_select")
if selected_idx != current_idx:
agent.next_llm(selected_idx); st.rerun(scope="fragment")
last_reply_time = st.session_state.get('last_reply_time', 0)
if last_reply_time > 0:
st.caption(f"空闲时间:{int(time.time()) - last_reply_time}秒", help="当超过30分钟未收到回复时,系统会自动任务")
if st.button("切换备用链路"):
agent.next_llm(); st.rerun(scope="fragment")
if st.button("强行停止任务"):
agent.abort(); st.toast("已发送停止信号"); st.rerun()
if st.button("重新注入工具"):
+4 -3
View File
@@ -445,7 +445,7 @@ class GenericAgentHandler(BaseHandler):
if not response or (not content.strip() and not thinking.strip()):
yield "[Warn] LLM returned an empty response. Retrying...\n"
return StepOutcome({}, next_prompt="[System] Blank response, regenerate and tooluse")
if len(content) > 100 and ('未收到完整响应 !!!]' in content[-100:] or '!!!Error: [SSL:' in content[-100:]):
if len(content) > 50 and ('未收到完整响应 !!!]' in content[-100:] or '!!!Error: [SSL:' in content[-100:]):
return StepOutcome({}, next_prompt="[System] Incomplete response. Regenerate and tooluse.")
if 'max_tokens !!!]' in content[-100:]:
return StepOutcome({}, next_prompt="[System] max_tokens limit reached. Use multi small steps to do it.")
@@ -490,10 +490,11 @@ class GenericAgentHandler(BaseHandler):
'''Agent觉得当前任务完成后有重要信息需要记忆时调用此工具。'''
prompt = '''### [总结提炼经验] 既然你觉得当前任务有重要信息需要记忆,请提取最近一次任务中【事实验证成功且长期有效】的环境事实、用户偏好、重要步骤,更新记忆。
本工具是标记开启结算过程,若已在更新记忆过程或没有值得记忆的点,忽略本次调用。
**提取行动验证成功的信息**:
**如果没有经验证的,未来能用上的信息,忽略本次调用!**
**只能提取行动验证成功的信息**:
- **环境事实**(路径/凭证/配置)→ `file_patch` 更新 L2,同步 L1
- **复杂任务经验**(关键坑点/前置条件/重要步骤)→ L3 精简 SOP(只记你被坑得多次重试的核心要点)
**禁止**:临时变量、具体推理过程、未验证信息、通用常识、你可以轻松复现的细节。
**禁止**:临时变量、具体推理过程、未验证信息、通用常识、你可以轻松复现的细节、只是做了但没有验证的信息
**操作**:严格遵循提供的L0的记忆更新SOP。先 `file_read` 看现有 → 判断类型 → 最小化更新 → 无新内容跳过,保证对记忆库最小局部修改。\n
''' + get_global_memory()
yield "[Info] Start distilling good memory for long-term storage.\n"
+25 -14
View File
@@ -1,26 +1,33 @@
import os, json, re, time, requests, sys, threading, urllib3, base64, mimetypes, uuid
import os, json, re, time, requests, sys, threading, urllib3, base64, importlib, uuid
from datetime import datetime
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
_RESP_CACHE_KEY = str(uuid.uuid4())
def _load_mykeys():
global _mykey_path
try:
import mykey; return {k: v for k, v in vars(mykey).items() if not k.startswith('_')}
import mykey; importlib.reload(mykey); _mykey_path = mykey.__file__
return {k: v for k, v in vars(mykey).items() if not k.startswith('_')}
except ImportError: pass
p = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mykey.json')
_mykey_path = p = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mykey.json')
if not os.path.exists(p): raise Exception('[ERROR] mykey.py or mykey.json not found, please create one from mykey_template.')
with open(p, encoding='utf-8') as f: return json.load(f)
_mykey_path = _mykey_mtime = None
def reload_mykeys():
global _mykey_mtime
mt = os.stat(_mykey_path).st_mtime_ns if _mykey_path else -1
if mt == _mykey_mtime: return globals().get('mykeys', {}), False
mk = _load_mykeys(); _mykey_mtime = os.stat(_mykey_path).st_mtime_ns
print(f'[Info] Load mykeys from {_mykey_path}')
globals().update(mykeys=mk)
if mk.get('langfuse_config'):
try: from plugins import langfuse_tracing
except Exception: pass
return mk, True
def __getattr__(name): # once guard in PEP 562
if name in ('mykeys', 'proxies'):
mk = _load_mykeys()
proxy = mk.get("proxy", 'http://127.0.0.1:2082')
px = {"http": proxy, "https": proxy} if proxy else None
globals().update(mykeys=mk, proxies=px)
if mk.get('langfuse_config'):
try: from plugins import langfuse_tracing
except Exception: pass
return globals()[name]
if name == 'mykeys': return reload_mykeys()[0]
raise AttributeError(f"module 'llmcore' has no attribute {name}")
def compress_history_tags(messages, keep_recent=10, max_len=800, force=False):
@@ -158,6 +165,11 @@ def _parse_claude_sse(resp_lines):
if not warn:
if not got_message_stop and not stop_reason: warn = "\n\n[!!! 流异常中断,未收到完整响应 !!!]"
elif stop_reason == "max_tokens": warn = "\n\n[!!! Response truncated: max_tokens !!!]"
if current_block:
if current_block["type"] == "tool_use":
try: current_block["input"] = json.loads(tool_json_buf) if tool_json_buf else {}
except: current_block["input"] = {"_raw": tool_json_buf}
content_blocks.append(current_block); current_block = None
if warn:
print(f"[WARN] {warn.strip()}")
content_blocks.append({"type": "text", "text": warn}); yield warn
@@ -488,7 +500,7 @@ class BaseSession:
self.api_key = cfg['apikey']
self.api_base = cfg['apibase'].rstrip('/')
self.model = cfg.get('model', '')
self.context_win = cfg.get('context_win', 24000)
self.context_win = cfg.get('context_win', 28000)
self.history = []
self.lock = threading.Lock()
self.system = ""
@@ -598,7 +610,6 @@ def _fix_messages(messages):
class NativeClaudeSession(BaseSession):
def __init__(self, cfg):
super().__init__(cfg)
self.context_win = cfg.get("context_win", 28000)
self.fake_cc_system_prompt = cfg.get("fake_cc_system_prompt", False)
self.user_agent = cfg.get("user_agent", "claude-cli/2.1.113 (external, cli)")
self._session_id = str(uuid.uuid4())