Merge PR #6: 火山引擎适配 + 路径修复

# Conflicts:
#	.gitignore
#	agentmain.py
This commit is contained in:
Liang Jiaqing
2026-03-06 21:43:04 +08:00
8 changed files with 63 additions and 35 deletions

3
.gitignore vendored
View File

@@ -68,4 +68,5 @@ restore_commit.txt
sche_tasks/
QUICK_START.md
# CDP Bridge 密钥配置(首次运行自动生成)
assets/tmwd_cdp_bridge/config.js
assets/tmwd_cdp_bridge/config.js
**log.*

View File

@@ -9,7 +9,8 @@ from sidercall import SiderLLMSession, LLMSession, ToolClient, ClaudeSession, Xa
from agent_loop import agent_runner_loop, StepOutcome, BaseHandler
from ga import GenericAgentHandler, smart_format, get_global_memory, format_error
with open('assets/tools_schema.json', 'r', encoding='utf-8') as f:
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, 'assets/tools_schema.json'), 'r', encoding='utf-8') as f:
TS = f.read()
TOOLS_SCHEMA = json.loads(TS if os.name == 'nt' else TS.replace('powershell', 'bash'))
@@ -24,14 +25,16 @@ if not os.path.exists('assets/tmwd_cdp_bridge/config.js'):
f.write(f"const TID = '__ljq_{hex(random.randint(0, 99999999))[2:8]}';")
def get_system_prompt():
with open('assets/sys_prompt.txt', 'r', encoding='utf-8') as f: prompt = f.read()
with open(os.path.join(script_dir, 'assets/sys_prompt.txt'), 'r', encoding='utf-8') as f: prompt = f.read()
prompt += f"\nToday: {time.strftime('%Y-%m-%d %a')}\n"
prompt += get_global_memory()
return prompt
class GeneraticAgent:
def __init__(self):
if not os.path.exists('temp'): os.makedirs('temp')
script_dir = os.path.dirname(os.path.abspath(__file__))
temp_dir = os.path.join(script_dir, 'temp')
if not os.path.exists(temp_dir): os.makedirs(temp_dir)
from sidercall import mykeys
llm_sessions = []
for k, cfg in mykeys.items():
@@ -89,7 +92,8 @@ class GeneraticAgent:
self.history.append(f"[USER]: {rquery}")
sys_prompt = get_system_prompt()
handler = GenericAgentHandler(None, self.history, './temp')
script_dir = os.path.dirname(os.path.abspath(__file__))
handler = GenericAgentHandler(None, self.history, os.path.join(script_dir, 'temp'))
if self.handler and self.handler.key_info:
handler.key_info = self.handler.key_info
if '清除工作记忆' not in handler.key_info:
@@ -136,8 +140,9 @@ if __name__ == '__main__':
threading.Thread(target=agent.run, daemon=True).start()
if args.task:
d = f'temp/{args.task}'; rp = f'{d}/reply.txt'; nround = ''
with open(f'{d}/input.txt', encoding='utf-8') as f: raw = f.read()
script_dir = os.path.dirname(os.path.abspath(__file__))
d = os.path.join(script_dir, f'temp/{args.task}'); rp = os.path.join(d, 'reply.txt'); nround = ''
with open(os.path.join(d, 'input.txt'), encoding='utf-8') as f: raw = f.read()
while True:
dq = agent.put_task(raw, source='task')
while 'done' not in (item := dq.get(timeout=120)):
@@ -173,23 +178,27 @@ if __name__ == '__main__':
except Exception as e:
if once: raise
print(f'[Reflect] drain error: {e}'); result = f'[ERROR] {e}'
open('./temp/reflect.log', 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}]\n{result}\n\n')
script_dir = os.path.dirname(os.path.abspath(__file__))
open(os.path.join(script_dir, './temp/reflect.log'), 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}]\n{result}\n\n')
if on_done:
try: on_done(result)
except Exception as e: print(f'[Reflect] on_done error: {e}')
if once: print('[Reflect] ONCE=True, exiting.'); break
elif args.scheduled:
script_dir = os.path.dirname(os.path.abspath(__file__))
def drain(dq, tag):
while 'done' not in (item := dq.get()): pass
open('./temp/scheduler.log', 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}] {tag}\n{item["done"]}\n\n')
open(os.path.join(script_dir, './temp/scheduler.log'), 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}] {tag}\n{item["done"]}\n\n')
while True:
time.sleep(55 + random.random() * 10)
now = datetime.now()
if not os.path.isdir('./sche_tasks/pending'): continue
for f in os.listdir('./sche_tasks/pending'):
script_dir = os.path.dirname(os.path.abspath(__file__))
sche_tasks_dir = os.path.join(script_dir, './sche_tasks/pending')
if not os.path.isdir(sche_tasks_dir): continue
for f in os.listdir(sche_tasks_dir):
m = re.match(r'(\d{4}-\d{2}-\d{2})_(\d{4})_', f)
if m and now >= datetime.strptime(f'{m[1]} {m[2]}', '%Y-%m-%d %H%M'):
raw = open(f'./sche_tasks/pending/{f}', encoding='utf-8').read()
raw = open(os.path.join(sche_tasks_dir, f), encoding='utf-8').read()
dq = agent.put_task(f'按scheduled_task_sop执行任务文件 ../sche_tasks/pending/{f}立刻移到running\n内容:\n{raw}', source='scheduler')
threading.Thread(target=drain, args=(dq, f), daemon=True).start()
break
@@ -206,4 +215,4 @@ if __name__ == '__main__':
if 'done' in item: print(); break
except KeyboardInterrupt:
agent.abort()
print('\n[Interrupted]')
print('\n[Interrupted]')

View File

@@ -15,7 +15,8 @@ def generate_tool_schema():
"""
通过代码内省,将 Handler 的逻辑映射为高语义的工具描述。
"""
with open('../ga.py', 'r', encoding='utf-8') as f:
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, '../ga.py'), 'r', encoding='utf-8') as f:
ga_code = f.read()
# 极简且具备高度概括能力的元 Prompt
meta_prompt = f"""
@@ -69,7 +70,8 @@ def generate_tool_schema():
final_schema = json.loads(clean_json)
if final_schema:
with open('tools_schema.json', 'w', encoding='utf-8') as f:
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, 'tools_schema.json'), 'w', encoding='utf-8') as f:
json.dump(final_schema, f, indent=2, ensure_ascii=False)
print("✅ 成功从代码内省生成 Schema 并持久化。")
return final_schema
@@ -79,7 +81,10 @@ def generate_tool_schema():
return None
def make_system_prompt(ga_code_path='../ga.py'):
def make_system_prompt(ga_code_path=None):
script_dir = os.path.dirname(os.path.abspath(__file__))
if ga_code_path is None:
ga_code_path = os.path.join(script_dir, '../ga.py')
with open(ga_code_path, 'r', encoding='utf-8') as f:
ga_code = f.read()
@@ -117,7 +122,7 @@ def make_system_prompt(ga_code_path='../ga.py'):
print("📝 生成的 System Prompt 内容如下:\n")
print(system_prompt_content)
clean_content = re.sub(r'<[^>]+>', '', system_prompt_content)
with open('sys_prompt.txt', 'w', encoding='utf-8') as f:
with open(os.path.join(script_dir, 'sys_prompt.txt'), 'w', encoding='utf-8') as f:
f.write(clean_content)
return clean_content

11
ga.py
View File

@@ -16,7 +16,8 @@ def code_run(code, code_type="python", timeout=60, cwd=None, code_cwd=None, stop
"""
preview = (code[:60].replace('\n', ' ') + '...') if len(code) > 60 else code.strip()
yield f"[Action] Running {code_type} in {os.path.basename(cwd)}: {preview}\n"
cwd = cwd or os.path.join(os.getcwd(), 'temp'); tmp_path = None
script_dir = os.path.dirname(os.path.abspath(__file__))
cwd = cwd or os.path.join(script_dir, 'temp'); tmp_path = None
if code_type == "python":
tmp_file = tempfile.NamedTemporaryFile(suffix=".ai.py", delete=False, mode='w', encoding='utf-8', dir=code_cwd)
tmp_file.write(code)
@@ -150,7 +151,8 @@ def format_error(e):
def log_memory_access(path):
if 'memory' not in path: return
stats_file = 'memory/file_access_stats.json'
script_dir = os.path.dirname(os.path.abspath(__file__))
stats_file = os.path.join(script_dir, 'memory/file_access_stats.json')
try:
with open(stats_file, 'r', encoding='utf-8') as f: stats = json.load(f)
except: stats = {}
@@ -493,8 +495,9 @@ class GenericAgentHandler(BaseHandler):
def get_global_memory():
prompt = "\n"
try:
with open('memory/global_mem_insight.txt', 'r', encoding='utf-8') as f: insight = f.read()
with open('assets/insight_fixed_structure.txt', 'r', encoding='utf-8') as f: structure = f.read()
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, 'memory/global_mem_insight.txt'), 'r', encoding='utf-8') as f: insight = f.read()
with open(os.path.join(script_dir, 'assets/insight_fixed_structure.txt'), 'r', encoding='utf-8') as f: structure = f.read()
prompt += f"\n[Memory]\n"
prompt += f'cwd = {os.path.abspath("./temp")} (用./引用)\n'
prompt += structure + '\n../memory/global_mem_insight.txt:\n'

View File

@@ -15,7 +15,8 @@ def get_screen_width():
def start_streamlit(port):
global proc
cmd = [sys.executable, "-m", "streamlit", "run", "stapp.py", "--server.port", str(port), "--server.address", "localhost", "--server.headless", "true", "--theme.base", "dark"] # 暗黑模式
script_dir = os.path.dirname(os.path.abspath(__file__))
cmd = [sys.executable, "-m", "streamlit", "run", os.path.join(script_dir, "stapp.py"), "--server.port", str(port), "--server.address", "localhost", "--server.headless", "true", "--theme.base", "dark"] # 暗黑模式
proc = subprocess.Popen(cmd)
atexit.register(proc.kill)
@@ -72,7 +73,8 @@ if __name__ == '__main__':
threading.Thread(target=start_streamlit, args=(port,), daemon=True).start()
if args.tg:
tgproc = subprocess.Popen([sys.executable, "tgapp.py"], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0)
script_dir = os.path.dirname(os.path.abspath(__file__))
tgproc = subprocess.Popen([sys.executable, os.path.join(script_dir, "tgapp.py")], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0)
atexit.register(tgproc.kill)
print('[Launch] Telegram Bot started')
else: print('[Launch] Telegram Bot not enabled (use --tg to start)')
@@ -80,7 +82,8 @@ if __name__ == '__main__':
if not args.no_sched:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); sock.bind(('127.0.0.1', 45762)); sock.listen(1)
scheduler_proc = subprocess.Popen([sys.executable, "agentmain.py", "--scheduled", "--llm_no", str(args.llm_no)], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0);
script_dir = os.path.dirname(os.path.abspath(__file__))
scheduler_proc = subprocess.Popen([sys.executable, os.path.join(script_dir, "agentmain.py"), "--scheduled", "--llm_no", str(args.llm_no)], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0);
atexit.register(lambda: (scheduler_proc.kill(), sock.close()))
print('[Launch] Task Scheduler started')
except OSError:

View File

@@ -4,7 +4,8 @@ from datetime import datetime
INTERVAL = 60 # 原版 55+random*10
ONCE = False
PENDING = './sche_tasks/pending'
script_dir = os.path.dirname(os.path.abspath(__file__))
PENDING = os.path.join(script_dir, '../sche_tasks/pending')
def check():
if not os.path.isdir(PENDING): return None
@@ -12,6 +13,6 @@ def check():
for f in os.listdir(PENDING):
m = re.match(r'(\d{4}-\d{2}-\d{2})_(\d{4})_', f)
if m and now >= datetime.strptime(f'{m[1]} {m[2]}', '%Y-%m-%d %H%M'):
raw = open(f'{PENDING}/{f}', encoding='utf-8').read()
raw = open(os.path.join(PENDING, f), encoding='utf-8').read()
return f'按scheduled_task_sop执行任务文件 ../sche_tasks/pending/{f}立刻移到running\n内容:\n{raw}'
return None

View File

@@ -37,7 +37,7 @@ class SiderLLMSession:
return full_text
class ClaudeSession:
def __init__(self, api_key, api_base, model="claude-opus", context_win=10000):
def __init__(self, api_key, api_base, model="claude-opus", context_win=9000):
self.api_key, self.api_base, self.default_model, self.context_win = api_key, api_base.rstrip('/'), model, context_win
self.raw_msgs, self.lock = [], threading.Lock()
def _trim_messages(self, messages):
@@ -51,7 +51,7 @@ class ClaudeSession:
else: break
if current > self.context_win * 3.6: print(f'[DEBUG] {len(result)} contexts, whole length {current//4} tokens.')
return result[::-1] or messages[-2:]
def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=6144):
def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=4096):
model = model or self.default_model
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}
@@ -100,6 +100,9 @@ class LLMSession:
else: self.api_mode = "chat_completions"
def _endpoint(self, path):
# 处理火山引擎API它已经包含完整路径
if 'ark.cn-beijing.volces.com' in self.api_base or 'ark.cn-shanghai.volces.com' in self.api_base:
return f"{self.api_base}/{path.lstrip('/')}"
if self.api_base.endswith('/v1'): return f"{self.api_base}/{path.lstrip('/')}"
if self.api_base.endswith('$'): return f"{self.api_base.rstrip('$')}/{path.lstrip('/')}"
return f"{self.api_base}/v1/{path.lstrip('/')}"
@@ -387,7 +390,8 @@ class ToolClient:
def chat(self, messages, tools=None):
full_prompt = self._build_protocol_prompt(messages, tools)
print("Full prompt length:", len(full_prompt), 'chars')
with open(f'./temp/model_responses_{os.getpid()}.txt', 'a', encoding='utf-8', errors="replace") as f:
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f:
f.write(f"=== Prompt === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{full_prompt}\n")
gen = self.backend.ask(full_prompt, stream=True)
raw_text = ''; summarytag = '[NextWillSummary]'
@@ -397,7 +401,8 @@ class ToolClient:
print('Complete response received.')
if raw_text.endswith(summarytag):
self.last_tools = ''; raw_text = raw_text[:-len(summarytag)]
with open(f'./temp/model_responses_{os.getpid()}.txt', 'a', encoding='utf-8', errors="replace") as f:
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, f'./temp/model_responses_{os.getpid()}.txt'), 'a', encoding='utf-8', errors="replace") as f:
f.write(f"=== Response === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{raw_text}\n\n")
return self._parse_mixed_response(raw_text)
@@ -538,4 +543,4 @@ if __name__ == "__main__":
response = get_final(llmclient.chat(
messages=[{"role": "user", "content": "<tool_result>10.176.45.12</tool_result>"}]
))
print(response.content)
print(response.content)

View File

@@ -80,18 +80,19 @@ def agent_backend_stream(prompt):
if "messages" not in st.session_state: st.session_state.messages = []
for msg in st.session_state.messages:
with st.chat_message(msg["role"]): st.markdown(msg["content"])
with st.chat_message(msg["role"]): st.markdown(msg["content"], unsafe_allow_html=True)
if prompt := st.chat_input("请输入指令"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"): st.markdown(prompt)
#允许消息内容中包含 HTML 代码并直接渲染,但注意这会有 XSS 安全风险,仅在内容可信时使用
with st.chat_message("user"): st.markdown(prompt, unsafe_allow_html=True)
with st.chat_message("assistant"):
message_placeholder = st.empty()
response = ''
for response in agent_backend_stream(prompt):
message_placeholder.markdown(response + "")
message_placeholder.markdown(response)
message_placeholder.markdown(response + "", unsafe_allow_html=True)
message_placeholder.markdown(response, unsafe_allow_html=True)
st.session_state.messages.append({"role": "assistant", "content": response})
st.session_state.last_reply_time = int(time.time())