hotreload mykeys, fix SSE residual block, unify context_win, cleanup
This commit is contained in:
1 parent
89b45f3d1d
commit
e07c1b046b
5 files changed
+60
-44
No files matched your search
+25
-21
@@ -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()
|
||||
|
||||
@@ -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
@@ -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("重新注入工具"):
|
||||
|
||||
@@ -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
@@ -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())
|
||||
|
||||
Reference in new issue
Block a user