fix: greedy match for file_content extraction; tgapp: add running indicator, HTML formatting, single instance lock; misc improvements

This commit is contained in:
Liang Jiaqing
2026-02-13 13:34:21 +08:00
parent 6bc5f3e80d
commit 98c349a1e8
5 changed files with 45 additions and 23 deletions

View File

@@ -7,7 +7,6 @@ class StepOutcome:
next_prompt: Optional[str] = None next_prompt: Optional[str] = None
should_exit: bool = False should_exit: bool = False
def try_call_generator(func, *args, **kwargs): def try_call_generator(func, *args, **kwargs):
ret = func(*args, **kwargs) ret = func(*args, **kwargs)
if hasattr(ret, '__iter__') and not isinstance(ret, (str, bytes, dict, list)): if hasattr(ret, '__iter__') and not isinstance(ret, (str, bytes, dict, list)):
@@ -66,9 +65,9 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema,
if tool_name == 'no_tool': pass if tool_name == 'no_tool': pass
else: else:
yield f"🛠️ **正在调用工具:** `{tool_name}`" showarg = get_pretty_json(args)
if verbose: yield f"📥**参数:**\n````text\n{get_pretty_json(args)}\n````\n" if not verbose and len(showarg) > 200: showarg = showarg[:200] + ' ...'
else: yield '\n\n\n' yield f"🛠️ **正在调用工具:** `{tool_name}` 📥**参数:**\n````text\n{showarg}\n````\n"
gen = handler.dispatch(tool_name, args, response) gen = handler.dispatch(tool_name, args, response)
if verbose: if verbose:
yield '`````\n' yield '`````\n'

8
ga.py
View File

@@ -226,8 +226,7 @@ def smart_format(data, max_depth=2, max_str_len=100, omit_str=' ... '):
return json.dumps(truncate(data, 0), indent=2, ensure_ascii=False, default=str) return json.dumps(truncate(data, 0), indent=2, ensure_ascii=False, default=str)
class GenericAgentHandler(BaseHandler): class GenericAgentHandler(BaseHandler):
''' '''Generic Agent 工具库,包含多种工具的实现。工具函数自动加上了 do_ 前缀。实际工具名没有前缀。
Generic Agent 工具库,包含多种工具的实现。工具函数自动加上了 do_ 前缀。实际工具名没有前缀。
''' '''
def __init__(self, parent, last_history=None, cwd='./'): def __init__(self, parent, last_history=None, cwd='./'):
self.parent = parent self.parent = parent
@@ -331,7 +330,7 @@ class GenericAgentHandler(BaseHandler):
yield f"[Action] {action_str} file: {os.path.basename(path)}\n" yield f"[Action] {action_str} file: {os.path.basename(path)}\n"
def extract_robust_content(text): def extract_robust_content(text):
tag = re.search(r"<file_content>(.*?)</file_content>", text, re.DOTALL) tag = re.search(r"<file_content>(.*)</file_content>", text, re.DOTALL)
if tag: return tag.group(1).strip() if tag: return tag.group(1).strip()
s, e = text.find("```"), text.rfind("```") s, e = text.find("```"), text.rfind("```")
if -1 < s < e: return text[text.find("\n", s)+1 : e].strip() if -1 < s < e: return text[text.find("\n", s)+1 : e].strip()
@@ -450,7 +449,8 @@ class GenericAgentHandler(BaseHandler):
prompt = f"\n### [WORKING MEMORY]\n<history>\n{h_str}\n</history>" prompt = f"\n### [WORKING MEMORY]\n<history>\n{h_str}\n</history>"
if self.key_info: prompt += f"\n<key_info>{self.key_info}</key_info>" if self.key_info: prompt += f"\n<key_info>{self.key_info}</key_info>"
if self.related_sop: prompt += f"\n有不清晰的地方请再次读取{self.related_sop}" if self.related_sop: prompt += f"\n有不清晰的地方请再次读取{self.related_sop}"
print(prompt) try: print(prompt)
except: pass
return prompt return prompt
def get_global_memory(): def get_global_memory():

View File

@@ -17,7 +17,7 @@ def get_screen_width():
except: return 1920 except: return 1920
def start_streamlit(port): def start_streamlit(port):
global proc global proc, tgproc
cmd = [ cmd = [
sys.executable, "-m", "streamlit", "run", "stapp.py", sys.executable, "-m", "streamlit", "run", "stapp.py",
"--server.port", str(port), "--server.port", str(port),
@@ -26,6 +26,8 @@ def start_streamlit(port):
] ]
proc = subprocess.Popen(cmd) proc = subprocess.Popen(cmd)
atexit.register(proc.kill) atexit.register(proc.kill)
tgproc = subprocess.Popen([sys.executable, "tgapp.py"], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0)
atexit.register(tgproc.kill)
def inject(text): def inject(text):
window.evaluate_js(f""" window.evaluate_js(f"""

View File

@@ -53,10 +53,10 @@ class ClaudeSession:
return result[::-1] or messages[-2:] return result[::-1] or messages[-2:]
def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=4096): def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=4096):
model = model or self.default_model model = model or self.default_model
headers = {"x-api-key": self.api_key, "Content-Type": "application/json"} headers = {"x-api-key": self.api_key, "Content-Type": "application/json", "anthropic-version": "2023-06-01"}
payload = {"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, "stream": True} payload = {"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, "stream": True}
try: try:
with requests.post(f"{self.api_base}/v1/messages", headers=headers, json=payload, stream=True, timeout=(5,60), verify=False) as r: with requests.post(f"{self.api_base}/v1/messages", headers=headers, json=payload, stream=True, timeout=(5,60)) as r:
r.raise_for_status() r.raise_for_status()
for line in r.iter_lines(): for line in r.iter_lines():
if not line: continue if not line: continue
@@ -208,9 +208,8 @@ class GeminiSession:
return iter([full_text]) if stream else full_text return iter([full_text]) if stream else full_text
class MockFunction: class MockFunction:
def __init__(self, name, arguments): def __init__(self, name, arguments): self.name, self.arguments = name, arguments
self.name = name
self.arguments = arguments
class MockToolCall: class MockToolCall:
def __init__(self, name, args): def __init__(self, name, args):
@@ -265,7 +264,7 @@ class ToolClient:
请按照以下步骤思考并行动,标签之间需要回车换行: 请按照以下步骤思考并行动,标签之间需要回车换行:
1. **思考**: 在 `<thinking>` 标签中先进行思考,分析现状和策略。 1. **思考**: 在 `<thinking>` 标签中先进行思考,分析现状和策略。
2. **总结**: 在 `<summary>` 中输出*极为简短*的高度概括的单行(<30字物理快照包括上次工具调用结果获取的新信息+本次工具调用意图和预期。此内容将进入长期工作记忆,记录关键信息,严禁输出无实际信息增量的描述。 2. **总结**: 在 `<summary>` 中输出*极为简短*的高度概括的单行(<30字物理快照包括上次工具调用结果获取的新信息+本次工具调用意图和预期。此内容将进入长期工作记忆,记录关键信息,严禁输出无实际信息增量的描述。
3. **行动**: 如果需要调用工具,请在回复正文之后输出一个 **<tool_use>块**,然后结束,我会稍后给你返回<tool_result>块。 3. **行动**: 如调用工具,请在回复正文之后输出一个 **<tool_use>块**,然后结束,我会稍后给你返回<tool_result>块。
格式: ```<tool_use>\n{{"name": "工具名", "arguments": {{参数}}}}\n</tool_use>\n``` 格式: ```<tool_use>\n{{"name": "工具名", "arguments": {{参数}}}}\n</tool_use>\n```
### 可用工具库(已挂载,持续有效) ### 可用工具库(已挂载,持续有效)

View File

@@ -1,4 +1,4 @@
import os, sys, re, threading, asyncio, queue as Q import os, sys, re, threading, asyncio, queue as Q, socket
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from agentmain import GeneraticAgent from agentmain import GeneraticAgent
from telegram import Update from telegram import Update
@@ -17,6 +17,21 @@ def _clean(t):
t = re.sub(p, '', t, flags=re.DOTALL) t = re.sub(p, '', t, flags=re.DOTALL)
return re.sub(r'\n{3,}', '\n\n', t).strip() or '...' return re.sub(r'\n{3,}', '\n\n', t).strip() or '...'
import html as _html
def _inline_md(s):
s = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', s)
s = re.sub(r'(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)', r'<i>\1</i>', s)
s = re.sub(r'`([^`]+)`', r'<code>\1</code>', s)
return s
def _to_html(t):
parts, pos = [], 0
for m in re.finditer(r'(`{3,})(?:\w*\n)?([\s\S]*?)\1', t):
parts.append(_inline_md(_html.escape(t[pos:m.start()])))
parts.append('<pre><code>' + _html.escape(m.group(2)) + '</code></pre>')
pos = m.end()
parts.append(_inline_md(_html.escape(t[pos:])))
return ''.join(parts)
async def _stream(dq, msg): async def _stream(dq, msg):
last_text = "" last_text = ""
while True: while True:
@@ -35,10 +50,13 @@ async def _stream(dq, msg):
except Exception: pass except Exception: pass
last_text = "" last_text = ""
show = show[-3900:] show = show[-3900:]
if show != last_text: display = show if done else show + ""
try: await msg.edit_text(show) if display != last_text:
except Exception: pass try: await msg.edit_text(_to_html(display), parse_mode='HTML')
last_text = show except Exception:
try: await msg.edit_text(display)
except Exception: pass
last_text = display
if done: break if done: break
async def handle_msg(update, ctx): async def handle_msg(update, ctx):
@@ -67,10 +85,14 @@ async def cmd_llm(update, ctx):
await update.message.reply_text("LLMs:\n" + "\n".join(lines)) await update.message.reply_text("LLMs:\n" + "\n".join(lines))
if __name__ == '__main__': if __name__ == '__main__':
# Single instance lock using socket
try:
_lock_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); _lock_sock.bind(('127.0.0.1', 19527))
except OSError: sys.exit('Another instance is already running.')
if not ALLOWED: if not ALLOWED:
sys.exit('ERROR: tg_allowed_users in mykey.py is empty or missing. Set it to avoid unauthorized access.') sys.exit('ERROR: tg_allowed_users in mykey.py is empty or missing. Set it to avoid unauthorized access.')
threading.Thread(target=agent.run, daemon=True).start() threading.Thread(target=agent.run, daemon=True).start()
proxy = os.environ.get('HTTPS_PROXY') or 'http://127.0.0.1:2082' proxy = vars(mykey).get('proxy', '127.0.0.1:2082')
app = ApplicationBuilder().token(mykey.tg_bot_token).proxy(proxy).get_updates_proxy(proxy).build() app = ApplicationBuilder().token(mykey.tg_bot_token).proxy(proxy).get_updates_proxy(proxy).build()
app.add_handler(CommandHandler("stop", cmd_abort)) app.add_handler(CommandHandler("stop", cmd_abort))
app.add_handler(CommandHandler("llm", cmd_llm)) app.add_handler(CommandHandler("llm", cmd_llm))