feat: --verbose flag, lazy mykeys loading, temperature config support

- agentmain: add --verbose arg for subagent monitoring mode
- llmcore: lazy-load mykeys/proxies via module __getattr__
- llmcore: fix auto_make_url regex for trailing slash cases
- llmcore: support temperature override from session config
- docs: update subagent.md with --verbose usage note
This commit is contained in:
Liang Jiaqing
2026-04-12 14:29:32 +08:00
parent 9e03a675ae
commit 2b5cbff7be
4 changed files with 21 additions and 15 deletions

View File

@@ -158,6 +158,7 @@ if __name__ == '__main__':
parser.add_argument('--reflect', metavar='SCRIPT', help='反射模式加载监控脚本check()触发时发任务') parser.add_argument('--reflect', metavar='SCRIPT', help='反射模式加载监控脚本check()触发时发任务')
parser.add_argument('--input', help='任务内容') parser.add_argument('--input', help='任务内容')
parser.add_argument('--llm_no', type=int, default=0, help='LLM编号') parser.add_argument('--llm_no', type=int, default=0, help='LLM编号')
parser.add_argument('--verbose', action='store_true', help='输出包含工具执行结果(监察模式用)')
parser.add_argument('--bg', action='store_true', help='后台自举: spawn自身去掉--bg, print PID, exit') parser.add_argument('--bg', action='store_true', help='后台自举: spawn自身去掉--bg, print PID, exit')
args = parser.parse_args() args = parser.parse_args()
@@ -173,7 +174,7 @@ if __name__ == '__main__':
agent = GeneraticAgent() agent = GeneraticAgent()
agent.next_llm(args.llm_no) agent.next_llm(args.llm_no)
agent.verbose = False agent.verbose = args.verbose
threading.Thread(target=agent.run, daemon=True).start() threading.Thread(target=agent.run, daemon=True).start()
if args.task: if args.task:

View File

@@ -1,4 +1,4 @@
Facts(L2): ../memory/global_mem.txt | Code: ../ | SOPs(L3): ../memory/*.md or *.py | META-SOP(L0): ../memory/memory_management_sop.md Facts(L2): ../memory/global_mem.txt | CodeRoot: ../ | SOPs(L3): ../memory/*.md or *.py | META-SOP(L0): ../memory/memory_management_sop.md
L1 Insight是极简索引L2/L3变更时同步L1索引必须极简。写记忆前先读META-SOP(L0)。 L1 Insight是极简索引L2/L3变更时同步L1索引必须极简。写记忆前先读META-SOP(L0)。
[CONSTITUTION] [CONSTITUTION]

View File

@@ -10,9 +10,14 @@ def _load_mykeys():
if not os.path.exists(p): raise Exception('[ERROR] mykey.py or mykey.json not found, please create one from mykey_template.') 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) with open(p, encoding='utf-8') as f: return json.load(f)
mykeys = _load_mykeys() def __getattr__(name):
proxy = mykeys.get("proxy", 'http://127.0.0.1:2082') if name in ('mykeys', 'proxies'):
proxies = {"http": proxy, "https": proxy} if proxy else None 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)
return globals()[name]
raise AttributeError(f"module 'llmcore' has no attribute {name}")
def compress_history_tags(messages, keep_recent=10, max_len=800, force=False): def compress_history_tags(messages, keep_recent=10, max_len=800, force=False):
"""Compress <thinking>/<tool_use>/<tool_result> tags in older messages to save tokens.""" """Compress <thinking>/<tool_use>/<tool_result> tags in older messages to save tokens."""
@@ -61,8 +66,7 @@ def _sanitize_leading_user_msg(msg):
if isinstance(c, list): # content 本身也可能是 list[{type:text,text:...}] if isinstance(c, list): # content 本身也可能是 list[{type:text,text:...}]
texts.extend(b.get('text', '') for b in c if isinstance(b, dict)) texts.extend(b.get('text', '') for b in c if isinstance(b, dict))
else: texts.append(str(c)) else: texts.append(str(c))
elif block.get('type') == 'text': elif block.get('type') == 'text': texts.append(block.get('text', ''))
texts.append(block.get('text', ''))
msg['content'] = [{"type": "text", "text": '\n'.join(t for t in texts if t)}] msg['content'] = [{"type": "text", "text": '\n'.join(t for t in texts if t)}]
return msg return msg
@@ -83,7 +87,8 @@ def trim_messages_history(history, context_win):
def auto_make_url(base, path): def auto_make_url(base, path):
b, p = base.rstrip('/'), path.strip('/') b, p = base.rstrip('/'), path.strip('/')
if b.endswith('$'): return b[:-1].rstrip('/') if b.endswith('$'): return b[:-1].rstrip('/')
return b if b.endswith(p) else f"{b}/{p}" if re.search(r'/v\d+$', b) else f"{b}/v1/{p}" if b.endswith(p): return b
return f"{b}/{p}" if re.search(r'/v\d+(/|$)', b) else f"{b}/v1/{p}"
def build_multimodal_content(prompt_text, image_paths): def build_multimodal_content(prompt_text, image_paths):
parts = [] parts = []
@@ -439,6 +444,7 @@ class BaseSession:
if effort and not self.reasoning_effort: print(f"[WARN] Invalid reasoning_effort {effort!r}, ignored.") if effort and not self.reasoning_effort: print(f"[WARN] Invalid reasoning_effort {effort!r}, ignored.")
mode = str(cfg.get('api_mode', 'chat_completions')).strip().lower().replace('-', '_') mode = str(cfg.get('api_mode', 'chat_completions')).strip().lower().replace('-', '_')
self.api_mode = 'responses' if mode in ('responses', 'response') else 'chat_completions' self.api_mode = 'responses' if mode in ('responses', 'response') else 'chat_completions'
self.temperature = cfg.get('temperature')
def ask(self, prompt, stream=False): def ask(self, prompt, stream=False):
def _ask_gen(): def _ask_gen():
content = '' content = ''
@@ -519,6 +525,7 @@ class NativeClaudeSession(BaseSession):
def raw_ask(self, messages, temperature=0.5, max_tokens=6144): def raw_ask(self, messages, temperature=0.5, max_tokens=6144):
messages = _fix_messages(messages) messages = _fix_messages(messages)
model = self.default_model model = self.default_model
if self.temperature is not None: temperature = self.temperature
beta_parts = ["claude-code-20250219", "interleaved-thinking-2025-05-14", "redact-thinking-2026-02-12", "prompt-caching-scope-2026-01-05"] beta_parts = ["claude-code-20250219", "interleaved-thinking-2025-05-14", "redact-thinking-2026-02-12", "prompt-caching-scope-2026-01-05"]
if "[1m]" in model.lower(): if "[1m]" in model.lower():
beta_parts.insert(1, "context-1m-2025-08-07"); model = model.replace("[1m]", "").replace("[1M]", "") beta_parts.insert(1, "context-1m-2025-08-07"); model = model.replace("[1m]", "").replace("[1M]", "")
@@ -553,7 +560,6 @@ class NativeClaudeSession(BaseSession):
self.history.append(msg) self.history.append(msg)
trim_messages_history(self.history, self.context_win) trim_messages_history(self.history, self.context_win)
messages = [{"role": m["role"], "content": list(m["content"])} for m in self.history] messages = [{"role": m["role"], "content": list(m["content"])} for m in self.history]
content_blocks = None content_blocks = None
gen = self.raw_ask(messages) gen = self.raw_ask(messages)
try: try:
@@ -580,8 +586,8 @@ class NativeOAISession(NativeClaudeSession):
"""OpenAI streaming. yields text chunks, generator return = list[content_block]""" """OpenAI streaming. yields text chunks, generator return = list[content_block]"""
msgs = ([{"role": "system", "content": self.system}] if self.system else []) + _msgs_claude2oai(messages) msgs = ([{"role": "system", "content": self.system}] if self.system else []) + _msgs_claude2oai(messages)
return (yield from _openai_stream(self.api_base, self.api_key, msgs, self.default_model, self.api_mode, return (yield from _openai_stream(self.api_base, self.api_key, msgs, self.default_model, self.api_mode,
temperature=temperature, max_tokens=max_tokens, tools=self.tools, temperature=temperature, max_tokens=max_tokens,
reasoning_effort=self.reasoning_effort, tools=self.tools, reasoning_effort=self.reasoning_effort,
max_retries=self.max_retries, connect_timeout=self.connect_timeout, max_retries=self.max_retries, connect_timeout=self.connect_timeout,
read_timeout=self.read_timeout, proxies=self.proxies)) read_timeout=self.read_timeout, proxies=self.proxies))
@@ -591,10 +597,8 @@ def openai_tools_to_claude(tools):
for t in tools: for t in tools:
if 'input_schema' in t: result.append(t); continue # 已是claude格式 if 'input_schema' in t: result.append(t); continue # 已是claude格式
fn = t.get('function', t) fn = t.get('function', t)
result.append({ result.append({'name': fn['name'], 'description': fn.get('description', ''),
'name': fn['name'], 'description': fn.get('description', ''), 'input_schema': fn.get('parameters', {'type': 'object', 'properties': {}})})
'input_schema': fn.get('parameters', {'type': 'object', 'properties': {}})
})
return result return result

View File

@@ -10,6 +10,7 @@
- 通信output.txt(append,`[ROUND END]`=轮完成) → 写reply.txt继续 → 不写10min退出。reply后输出为output1/2/3.txt(同格式) - 通信output.txt(append,`[ROUND END]`=轮完成) → 写reply.txt继续 → 不写10min退出。reply后输出为output1/2/3.txt(同格式)
- 干预文件:`_stop`(当轮结束退出) | `_keyinfo`(注入working memory) | `_intervene`(追加指令) - 干预文件:`_stop`(当轮结束退出) | `_keyinfo`(注入working memory) | `_intervene`(追加指令)
- **主agent空闲时应读output观察进度必要时用干预文件纠偏禁止无脑长时间sleep轮询** - **主agent空闲时应读output观察进度必要时用干预文件纠偏禁止无脑长时间sleep轮询**
- 监察模式启动时加`--verbose`output将包含工具执行结果主agent可直接审查原始数据而非仅信任摘要
## 场景1测试模式 - 行为验证 ## 场景1测试模式 - 行为验证
**用途**观察agent真实行为修正RULES/L2/L3/SOP **用途**观察agent真实行为修正RULES/L2/L3/SOP