fix: improve JSON parsing error handling with bad_json mechanism

This commit is contained in:
Liang Jiaqing
2026-02-12 21:25:15 +08:00
parent 87e522ff8e
commit df6645adcc
11 changed files with 3000 additions and 24 deletions

BIN
.vs/GenericAgent/v17/.wsuo Normal file

Binary file not shown.

View File

@@ -0,0 +1,12 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\Ljq\\Documents\\mywork\\MyTools\\AutoOperation\\webagent\\GenericAgent\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": []
}
]
}

3
.vs/ProjectSettings.json Normal file
View File

@@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}

View File

@@ -0,0 +1,6 @@
{
"ExpandedNodes": [
""
],
"PreviewInSolutionExplorer": false
}

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

View File

@@ -24,6 +24,8 @@ class BaseHandler:
ret = yield from try_call_generator(getattr(self, method_name), args, response) ret = yield from try_call_generator(getattr(self, method_name), args, response)
_ = yield from try_call_generator(self.tool_after_callback, tool_name, args, response, ret) _ = yield from try_call_generator(self.tool_after_callback, tool_name, args, response, ret)
return ret return ret
elif tool_name == 'bad_json':
return StepOutcome(None, next_prompt=args.get('msg', 'bad_json'), should_exit=False)
else: else:
yield f"❌ 未知工具: {tool_name}\n" yield f"❌ 未知工具: {tool_name}\n"
return StepOutcome(None, next_prompt=f"未知工具 {tool_name}", should_exit=False) return StepOutcome(None, next_prompt=f"未知工具 {tool_name}", should_exit=False)

View File

@@ -30,15 +30,14 @@ class GeneraticAgent:
if not os.path.exists('temp'): os.makedirs('temp') if not os.path.exists('temp'): os.makedirs('temp')
from sidercall import sider_cookie, oai_configs, claude_configs from sidercall import sider_cookie, oai_configs, claude_configs
llm_sessions = [] llm_sessions = []
for cfg in claude_configs.values():
llm_sessions += [ClaudeSession(api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'])]
if sider_cookie: llm_sessions += [SiderLLMSession(default_model=x) for x in \ if sider_cookie: llm_sessions += [SiderLLMSession(default_model=x) for x in \
["gemini-3.0-flash", "claude-haiku-4.5", "kimi-k2"]] ["gemini-3.0-flash", "claude-haiku-4.5", "kimi-k2"]]
for cfg in oai_configs.values(): for cfg in oai_configs.values():
llm_sessions += [LLMSession(api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'])] llm_sessions += [LLMSession(api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'])]
for cfg in claude_configs.values():
llm_sessions += [ClaudeSession(api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'])]
if len(llm_sessions) > 0: if len(llm_sessions) > 0:
llmclient = ToolClient(llm_sessions, auto_save_tokens=True) self.llmclient = ToolClient(llm_sessions, auto_save_tokens=True)
self.llmclient = llmclient
else: else:
self.llmclient = None self.llmclient = None
self.lock = threading.Lock() self.lock = threading.Lock()

16
ga.py
View File

@@ -84,20 +84,10 @@ def code_run(code, code_type="python", timeout=60, cwd=None, code_cwd=None, stop
def ask_user(question: str, candidates: list = None): def ask_user(question: str, candidates: list = None):
"""question: 向用户提出的问题。candidates: 可选的候选项列表。需要保证should_exit为True
""" """
构造一个中断请求。 return {"status": "INTERRUPT", "intent": "HUMAN_INTERVENTION",
question: 向用户提出的问题。 "data": {"question": question, "candidates": candidates or []}}
candidates: 可选的候选项列表。
需要保证should_exit为True
"""
return {
"status": "INTERRUPT",
"intent": "HUMAN_INTERVENTION",
"data": {
"question": question,
"candidates": candidates or []
}
}
from simphtml import execute_js_rich, get_html from simphtml import execute_js_rich, get_html

2964
restore_commit.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -56,18 +56,18 @@ class GeminiSession:
return iter([full_text]) if stream else full_text return iter([full_text]) if stream else full_text
class ClaudeSession: class ClaudeSession:
def __init__(self, api_key, api_base, model="claude-opus", context_win=32000): def __init__(self, api_key, api_base, model="claude-opus", context_win=24000):
self.api_key, self.api_base, self.default_model, self.context_win = api_key, api_base.rstrip('/'), model, context_win 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() self.raw_msgs, self.lock = [], threading.Lock()
def _trim_messages(self, messages): def _trim_messages(self, messages):
total = sum(len(m['prompt'])//4 for m in messages) total = sum(len(m['prompt'])//4 for m in messages)
if total <= self.context_win: return messages if total <= self.context_win: return messages
trimmed = [] target, current, result = self.context_win * 0.9, 0, []
for msg in reversed(messages): for msg in reversed(messages):
if sum(len(m['prompt'])//4 for m in trimmed) + len(msg['prompt'])//4 <= self.context_win * 0.9: if (msg_len := len(msg['prompt'])//4) + current <= target:
trimmed.insert(0, msg) result.append(msg); current += msg_len
else: break else: break
return trimmed if trimmed else 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"}
@@ -315,9 +315,9 @@ class ToolClient:
args = data.get('arguments') or data.get('args') or data.get('params') or data.get('parameters') args = data.get('arguments') or data.get('args') or data.get('params') or data.get('parameters')
if args is None: args = data if args is None: args = data
if func_name: tool_calls = [MockToolCall(func_name, args)] if func_name: tool_calls = [MockToolCall(func_name, args)]
except json.JSONDecodeError: except json.JSONDecodeError as e:
print("[Warn] Failed to parse tool_use JSON:", json_str) print("[Warn] Failed to parse tool_use JSON:", json_str)
remaining_text += f"[Warning] JSON 解析失败,模型输出了无效的 JSON." tool_calls = [MockToolCall('bad_json', {'msg': f'Failed to parse tool_use JSON: {str(e)}'})]
except Exception as e: except Exception as e:
print("[Error] Exception during tool_use parsing:", str(e), data) print("[Error] Exception during tool_use parsing:", str(e), data)