feat: add NativeOAISession + minor fixes (code_run script fallback, thinking prompt tags)
This commit is contained in:
@@ -5,7 +5,7 @@ if sys.stderr is None: sys.stderr = open(os.devnull, "w")
|
|||||||
elif hasattr(sys.stderr, 'reconfigure'): sys.stderr.reconfigure(errors='replace')
|
elif hasattr(sys.stderr, 'reconfigure'): sys.stderr.reconfigure(errors='replace')
|
||||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
from llmcore import SiderLLMSession, LLMSession, ToolClient, ClaudeSession, XaiSession, NativeToolClient, NativeClaudeSession, build_multimodal_content
|
from llmcore import SiderLLMSession, LLMSession, ToolClient, ClaudeSession, XaiSession, NativeToolClient, NativeClaudeSession, build_multimodal_content, NativeOAISession
|
||||||
from agent_loop import agent_runner_loop
|
from agent_loop import agent_runner_loop
|
||||||
from ga import GenericAgentHandler, smart_format, get_global_memory, format_error
|
from ga import GenericAgentHandler, smart_format, get_global_memory, format_error
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ class GeneraticAgent:
|
|||||||
if not any(x in k for x in ['api', 'config', 'cookie']): continue
|
if not any(x in k for x in ['api', 'config', 'cookie']): continue
|
||||||
try:
|
try:
|
||||||
if 'native' in k and 'claude' in k: llm_sessions += [NativeToolClient(NativeClaudeSession(cfg=cfg))]
|
if 'native' in k and 'claude' in k: llm_sessions += [NativeToolClient(NativeClaudeSession(cfg=cfg))]
|
||||||
|
elif 'native' in k and 'oai' in k: llm_sessions += [NativeToolClient(NativeOAISession(cfg=cfg))]
|
||||||
elif 'claude' in k: llm_sessions += [ToolClient(ClaudeSession(cfg=cfg))]
|
elif 'claude' in k: llm_sessions += [ToolClient(ClaudeSession(cfg=cfg))]
|
||||||
elif 'oai' in k: llm_sessions += [ToolClient(LLMSession(cfg=cfg))]
|
elif 'oai' in k: llm_sessions += [ToolClient(LLMSession(cfg=cfg))]
|
||||||
elif 'xai' in k: llm_sessions += [ToolClient(XaiSession(cfg=cfg))]
|
elif 'xai' in k: llm_sessions += [ToolClient(XaiSession(cfg=cfg))]
|
||||||
|
|||||||
2
ga.py
2
ga.py
@@ -276,7 +276,7 @@ class GenericAgentHandler(BaseHandler):
|
|||||||
matches = re.findall(pattern, response.content, re.DOTALL)
|
matches = re.findall(pattern, response.content, re.DOTALL)
|
||||||
warning = ""
|
warning = ""
|
||||||
if not matches:
|
if not matches:
|
||||||
code = args.get("code")
|
code = args.get("code") or args.get("script")
|
||||||
if not code: return StepOutcome(None, next_prompt=f"【系统错误】:你调用了 code_run,但未在先在回复正文中提供 ```{code_type} 代码块。请重新输出代码并附带工具调用。")
|
if not code: return StepOutcome(None, next_prompt=f"【系统错误】:你调用了 code_run,但未在先在回复正文中提供 ```{code_type} 代码块。请重新输出代码并附带工具调用。")
|
||||||
warning = "\n下次要记得先在回复正文中提供代码块,而不是放在参数中"
|
warning = "\n下次要记得先在回复正文中提供代码块,而不是放在参数中"
|
||||||
else: code = matches[-1].strip() # 提取最后一个代码块(通常是模型修正后的最终逻辑)
|
else: code = matches[-1].strip() # 提取最后一个代码块(通常是模型修正后的最终逻辑)
|
||||||
|
|||||||
73
llmcore.py
73
llmcore.py
@@ -398,6 +398,75 @@ class XaiSession:
|
|||||||
def reset(self): self._last_response_id = None
|
def reset(self): self._last_response_id = None
|
||||||
|
|
||||||
|
|
||||||
|
class NativeOAISession:
|
||||||
|
def __init__(self, cfg):
|
||||||
|
self.api_key = cfg['apikey']; self.api_base = cfg['apibase'].rstrip('/')
|
||||||
|
self.default_model = cfg.get('model', 'gpt-4o')
|
||||||
|
self.context_win = cfg.get('context_win', 24000)
|
||||||
|
self.history = []; self.system = None; self.lock = threading.Lock()
|
||||||
|
def set_system(self, system_text): self.system = system_text
|
||||||
|
|
||||||
|
def raw_ask(self, messages, tools=None, system=None, model=None, temperature=0.5, max_tokens=6144, **kw):
|
||||||
|
"""OpenAI streaming. yields text chunks, generator return = list[content_block]"""
|
||||||
|
model = model or self.default_model
|
||||||
|
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
|
||||||
|
msgs = ([{"role": "system", "content": system}] if system else []) + messages
|
||||||
|
payload = {"model": model, "messages": msgs, "temperature": temperature, "max_tokens": max_tokens, "stream": True}
|
||||||
|
if tools: payload["tools"] = tools
|
||||||
|
try:
|
||||||
|
resp = requests.post(auto_make_url(self.api_base, "chat/completions"), headers=headers, json=payload, stream=True, timeout=120)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
err = f"Error: HTTP {resp.status_code} {resp.text[:500]}"; yield err; return [{"type": "text", "text": err}]
|
||||||
|
except Exception as e:
|
||||||
|
err = f"Error: {e}"; yield err; return [{"type": "text", "text": err}]
|
||||||
|
content_text = ""; tc_buf = {} # index -> {id, name, args_str}
|
||||||
|
for line in resp.iter_lines():
|
||||||
|
if not line: continue
|
||||||
|
line = line.decode('utf-8', errors='replace') if isinstance(line, bytes) else line
|
||||||
|
if not line.startswith("data: "): continue
|
||||||
|
data_str = line[6:]
|
||||||
|
if data_str.strip() == "[DONE]": break
|
||||||
|
try: evt = json.loads(data_str)
|
||||||
|
except: continue
|
||||||
|
delta = evt.get("choices", [{}])[0].get("delta", {})
|
||||||
|
if delta.get("content"):
|
||||||
|
text = delta["content"]; content_text += text; yield text
|
||||||
|
for tc in delta.get("tool_calls", []):
|
||||||
|
idx = tc.get("index", 0)
|
||||||
|
if idx not in tc_buf: tc_buf[idx] = {"id": tc.get("id", ""), "name": "", "args": ""}
|
||||||
|
if tc.get("function", {}).get("name"): tc_buf[idx]["name"] = tc["function"]["name"]
|
||||||
|
if tc.get("function", {}).get("arguments"): tc_buf[idx]["args"] += tc["function"]["arguments"]
|
||||||
|
blocks = []
|
||||||
|
if content_text: blocks.append({"type": "text", "text": content_text})
|
||||||
|
for idx in sorted(tc_buf):
|
||||||
|
tc = tc_buf[idx]
|
||||||
|
try: inp = json.loads(tc["args"]) if tc["args"] else {}
|
||||||
|
except: inp = {"_raw": tc["args"]}
|
||||||
|
blocks.append({"type": "tool_use", "id": tc["id"], "name": tc["name"], "input": inp})
|
||||||
|
return blocks
|
||||||
|
|
||||||
|
def ask(self, msg, tools=None, model=None, **kw):
|
||||||
|
"""Managed ask with history. yields text chunks, return MockResponse"""
|
||||||
|
if isinstance(msg, str): msg = {"role": "user", "content": msg}
|
||||||
|
elif isinstance(msg, list): msg = {"role": "user", "content": msg}
|
||||||
|
with self.lock:
|
||||||
|
self.history.append(msg)
|
||||||
|
while len(self.history) > 2:
|
||||||
|
cost = sum(len(json.dumps(m, ensure_ascii=False)) for m in self.history) + len(self.system or '')
|
||||||
|
if cost <= self.context_win * 4: break
|
||||||
|
self.history.pop(0); self.history.pop(0)
|
||||||
|
messages = list(self.history)
|
||||||
|
content_blocks = None
|
||||||
|
gen = self.raw_ask(messages, tools, self.system, model)
|
||||||
|
try:
|
||||||
|
while True: yield next(gen)
|
||||||
|
except StopIteration as e: content_blocks = e.value or []
|
||||||
|
if content_blocks and not (len(content_blocks) == 1 and content_blocks[0].get("text", "").startswith("Error:")):
|
||||||
|
self.history.append({"role": "assistant", "content": content_blocks})
|
||||||
|
text_parts = [b["text"] for b in content_blocks if b.get("type") == "text"]
|
||||||
|
content = "\n".join(text_parts).strip()
|
||||||
|
tool_calls = [MockToolCall(b["name"], b.get("input", {}), id=b.get("id", "")) for b in content_blocks if b.get("type") == "tool_use"]
|
||||||
|
return MockResponse("", content, tool_calls, str(content_blocks))
|
||||||
|
|
||||||
|
|
||||||
class NativeClaudeSession:
|
class NativeClaudeSession:
|
||||||
@@ -711,8 +780,8 @@ class NativeToolClient:
|
|||||||
THINKING_PROMPT = """
|
THINKING_PROMPT = """
|
||||||
### 行动规范(持续有效)
|
### 行动规范(持续有效)
|
||||||
每次回复请遵循:
|
每次回复请遵循:
|
||||||
1. 在 <thinking> 标签中先分析现状和策略
|
1. 在 <thinking></thinking> 标签中先分析现状和策略
|
||||||
2. 在 <summary> 中输出极简单行(<30字)物理快照:上次结果新信息+本次意图。此内容进入长期工作记忆。
|
2. 在 <summary></summary> 中输出极简单行(<30字)物理快照:上次结果新信息+本次意图。此内容进入长期工作记忆。
|
||||||
3. 如需调用工具,直接使用工具调用能力,然后结束回复。
|
3. 如需调用工具,直接使用工具调用能力,然后结束回复。
|
||||||
""".strip()
|
""".strip()
|
||||||
def __init__(self, backend):
|
def __init__(self, backend):
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ claude_config = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ── Claude Native API ───────────────────────────────────────────────────────────
|
# ── Claude Native API ───────────────────────────────────────────────────────────
|
||||||
# key命名同时含 'native' 和 'claude' 触发 NativeClaudeSession(原生Anthropic协议)
|
# key命名同时含 'native' 和 'claude' 触发 NativeClaudeSession
|
||||||
|
# 原生工具调用格式,缓解弱模型指令遵循问题,但更耗token
|
||||||
native_claude_config = {
|
native_claude_config = {
|
||||||
'apikey': 'sk-ant-...', # Anthropic原生apikey
|
'apikey': 'sk-ant-...', # Anthropic原生apikey
|
||||||
'apibase': 'https://api.anthropic.com',
|
'apibase': 'https://api.anthropic.com',
|
||||||
@@ -48,6 +49,16 @@ native_claude_config = {
|
|||||||
# 'context_win': 24000,
|
# 'context_win': 24000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── OpenAI-compatible Native API ─────────────────────────────────────────────
|
||||||
|
# key命名同时含 'native' 和 'oai' 触发 NativeOAISession
|
||||||
|
# 原生工具调用格式,缓解弱模型指令遵循问题,但更耗token
|
||||||
|
native_oai_config = {
|
||||||
|
'apikey': 'sk-...',
|
||||||
|
'apibase': 'http://your-proxy:2001',
|
||||||
|
'model': 'gpt-4o',
|
||||||
|
# 'context_win': 24000,
|
||||||
|
}
|
||||||
|
|
||||||
# ── Sider ───────────────────────────────────────────────────────────────────────
|
# ── Sider ───────────────────────────────────────────────────────────────────────
|
||||||
# key命名含 'sider' 触发 SiderLLMSession(需安装 sider_ai_api 包)
|
# key命名含 'sider' 触发 SiderLLMSession(需安装 sider_ai_api 包)
|
||||||
#sider_cookie = 'token=Bearer%20eyJhbGciOiJIUz...'
|
#sider_cookie = 'token=Bearer%20eyJhbGciOiJIUz...'
|
||||||
|
|||||||
Reference in New Issue
Block a user