Spaces:
Running
Running
asemxin commited on
Commit ·
cbadacf
1
Parent(s): f00da0f
feat: 集成 Brave Search 工具调用(ReAct 循环)
Browse files- image_daemon.py +90 -18
image_daemon.py
CHANGED
|
@@ -14,6 +14,7 @@ APP_SECRET = os.environ.get("FEISHU_APP_SECRET", "")
|
|
| 14 |
API_BASE_URL = os.environ.get("API_BASE_URL", "https://asem12345-cliproxyapi.hf.space/v1")
|
| 15 |
API_KEY = os.environ.get("API_KEY", "")
|
| 16 |
MODEL_NAME = os.environ.get("MODEL_NAME", "gemini-3-flash")
|
|
|
|
| 17 |
|
| 18 |
# OpenClaw Gateway (本地)
|
| 19 |
OPENCLAW_GATEWAY = "http://127.0.0.1:18789/v1"
|
|
@@ -317,46 +318,117 @@ def check_openclaw_gateway():
|
|
| 317 |
except Exception as e:
|
| 318 |
log(f"⚠️ Gateway 不可用 ({e}),使用外部 LLM + SOUL 人设")
|
| 319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
# ---------- LLM 对话 ----------
|
| 321 |
def chat_with_llm(user_text, history=None):
|
| 322 |
-
"""
|
| 323 |
try:
|
| 324 |
if _use_gateway:
|
| 325 |
-
# 走 OpenClaw Gateway:人设和工具由它处理
|
| 326 |
resp = requests.post(
|
| 327 |
f"{OPENCLAW_GATEWAY}/chat/completions",
|
| 328 |
json={"model": "default", "messages": (history or []) + [{"role": "user", "content": user_text}], "stream": False},
|
| 329 |
timeout=120
|
| 330 |
)
|
| 331 |
if resp.status_code == 200:
|
| 332 |
-
|
| 333 |
-
reply = data["choices"][0]["message"]["content"]
|
| 334 |
log(f"🤖 Gateway 回复: {reply[:60]}...")
|
| 335 |
return reply
|
| 336 |
log(f"⚠️ Gateway 失败 ({resp.status_code}),Fallback到外部 LLM")
|
| 337 |
|
| 338 |
-
# Fallback:外部 LLM + SOUL.md 人设
|
| 339 |
if not API_KEY:
|
| 340 |
return "抱歉,我的大脑连接中断了 (API_KEY missing)"
|
|
|
|
|
|
|
| 341 |
soul = _soul_prompt or "You are a helpful assistant."
|
|
|
|
| 342 |
messages = [{"role": "system", "content": soul}] + (history or []) + [{"role": "user", "content": user_text}]
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
except Exception as e:
|
| 357 |
log(f"❌ chat_with_llm 异常: {e}")
|
| 358 |
return f"大脑短路了: {e}"
|
| 359 |
|
|
|
|
| 360 |
# ---------- 处理文本消息 ----------
|
| 361 |
def handle_text_message(message_id, chat_id, text):
|
| 362 |
"""LLM (带历史) -> 发送,如有待处理图片则做针对 Vision"""
|
|
|
|
| 14 |
API_BASE_URL = os.environ.get("API_BASE_URL", "https://asem12345-cliproxyapi.hf.space/v1")
|
| 15 |
API_KEY = os.environ.get("API_KEY", "")
|
| 16 |
MODEL_NAME = os.environ.get("MODEL_NAME", "gemini-3-flash")
|
| 17 |
+
BRAVE_API_KEY = os.environ.get("BRAVE_API_KEY", "")
|
| 18 |
|
| 19 |
# OpenClaw Gateway (本地)
|
| 20 |
OPENCLAW_GATEWAY = "http://127.0.0.1:18789/v1"
|
|
|
|
| 318 |
except Exception as e:
|
| 319 |
log(f"⚠️ Gateway 不可用 ({e}),使用外部 LLM + SOUL 人设")
|
| 320 |
|
| 321 |
+
# ---------- Brave Search ----------
|
| 322 |
+
def search_brave(query, count=5):
|
| 323 |
+
"""Brave Search API 一次搜索。返回整理得到的摘要字符串。"""
|
| 324 |
+
if not BRAVE_API_KEY:
|
| 325 |
+
return "搜索失败:BRAVE_API_KEY 未配置"
|
| 326 |
+
try:
|
| 327 |
+
resp = requests.get(
|
| 328 |
+
"https://api.search.brave.com/res/v1/web/search",
|
| 329 |
+
headers={"Accept": "application/json", "X-Subscription-Token": BRAVE_API_KEY},
|
| 330 |
+
params={"q": query, "count": count, "text_decorations": False, "extra_snippets": True},
|
| 331 |
+
timeout=10
|
| 332 |
+
)
|
| 333 |
+
if resp.status_code != 200:
|
| 334 |
+
return f"搜索失败 ({resp.status_code})"
|
| 335 |
+
results = resp.json().get("web", {}).get("results", [])
|
| 336 |
+
if not results:
|
| 337 |
+
return "未找到相关结果"
|
| 338 |
+
lines = []
|
| 339 |
+
for r in results[:count]:
|
| 340 |
+
title = r.get("title", "无标题")
|
| 341 |
+
url = r.get("url", "")
|
| 342 |
+
desc = r.get("description") or (r.get("extra_snippets") or [""])[0]
|
| 343 |
+
lines.append(f"- **{title}**\n {desc}\n {url}")
|
| 344 |
+
log(f"🔍 Brave 搜索「{query}」得到 {len(results)} 条")
|
| 345 |
+
return "\n".join(lines)
|
| 346 |
+
except Exception as e:
|
| 347 |
+
log(f"⚠️ Brave 搜索异常: {e}")
|
| 348 |
+
return f"搜索异常: {e}"
|
| 349 |
+
|
| 350 |
+
# Brave Search 的 OpenAI tools 定义
|
| 351 |
+
BRAVE_TOOL_DEF = [{
|
| 352 |
+
"type": "function",
|
| 353 |
+
"function": {
|
| 354 |
+
"name": "brave_search",
|
| 355 |
+
"description": "当需要查找实时信息、最新数据、公司信息、行业报告等时,调用此工具进行网页搜索",
|
| 356 |
+
"parameters": {
|
| 357 |
+
"type": "object",
|
| 358 |
+
"properties": {
|
| 359 |
+
"query": {
|
| 360 |
+
"type": "string",
|
| 361 |
+
"description": "搜索关键词,英文搜索通常效果更好"
|
| 362 |
+
}
|
| 363 |
+
},
|
| 364 |
+
"required": ["query"]
|
| 365 |
+
}
|
| 366 |
+
}
|
| 367 |
+
}]
|
| 368 |
+
|
| 369 |
# ---------- LLM 对话 ----------
|
| 370 |
def chat_with_llm(user_text, history=None):
|
| 371 |
+
"""带 Brave Search 工具的 ReAct 循环(最多搜 3 次)"""
|
| 372 |
try:
|
| 373 |
if _use_gateway:
|
|
|
|
| 374 |
resp = requests.post(
|
| 375 |
f"{OPENCLAW_GATEWAY}/chat/completions",
|
| 376 |
json={"model": "default", "messages": (history or []) + [{"role": "user", "content": user_text}], "stream": False},
|
| 377 |
timeout=120
|
| 378 |
)
|
| 379 |
if resp.status_code == 200:
|
| 380 |
+
reply = resp.json()["choices"][0]["message"]["content"]
|
|
|
|
| 381 |
log(f"🤖 Gateway 回复: {reply[:60]}...")
|
| 382 |
return reply
|
| 383 |
log(f"⚠️ Gateway 失败 ({resp.status_code}),Fallback到外部 LLM")
|
| 384 |
|
|
|
|
| 385 |
if not API_KEY:
|
| 386 |
return "抱歉,我的大脑连接中断了 (API_KEY missing)"
|
| 387 |
+
|
| 388 |
+
import json as _json
|
| 389 |
soul = _soul_prompt or "You are a helpful assistant."
|
| 390 |
+
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
|
| 391 |
messages = [{"role": "system", "content": soul}] + (history or []) + [{"role": "user", "content": user_text}]
|
| 392 |
+
|
| 393 |
+
for _ in range(4): # 最多 3 次工具调用 + 1 次最终回复
|
| 394 |
+
log(f"🤖 外部 LLM ({MODEL_NAME}): {user_text[:50]}...")
|
| 395 |
+
payload = {
|
| 396 |
+
"model": MODEL_NAME,
|
| 397 |
+
"messages": messages,
|
| 398 |
+
"stream": False
|
| 399 |
+
}
|
| 400 |
+
if BRAVE_API_KEY:
|
| 401 |
+
payload["tools"] = BRAVE_TOOL_DEF
|
| 402 |
+
resp = requests.post(f"{API_BASE_URL}/chat/completions", headers=headers, json=payload, timeout=60)
|
| 403 |
+
if resp.status_code != 200:
|
| 404 |
+
log(f"❌ 外部 LLM 错误 {resp.status_code}: {resp.text[:100]}")
|
| 405 |
+
return f"思考时遇到错误 ({resp.status_code})"
|
| 406 |
+
|
| 407 |
+
msg = resp.json()["choices"][0]["message"]
|
| 408 |
+
tool_calls = msg.get("tool_calls") or []
|
| 409 |
+
|
| 410 |
+
if not tool_calls:
|
| 411 |
+
reply = msg.get("content", "")
|
| 412 |
+
log(f"🤖 LLM 回复: {reply[:60]}...")
|
| 413 |
+
return reply
|
| 414 |
+
|
| 415 |
+
# 执行工具调用
|
| 416 |
+
messages.append(msg)
|
| 417 |
+
for tc in tool_calls:
|
| 418 |
+
fn = tc["function"]["name"]
|
| 419 |
+
args = _json.loads(tc["function"]["arguments"])
|
| 420 |
+
if fn == "brave_search":
|
| 421 |
+
result = search_brave(args.get("query", ""))
|
| 422 |
+
else:
|
| 423 |
+
result = f"未知工具: {fn}"
|
| 424 |
+
messages.append({"role": "tool", "tool_call_id": tc["id"], "content": result})
|
| 425 |
+
|
| 426 |
+
return "思考超时,请稍后再试"
|
| 427 |
except Exception as e:
|
| 428 |
log(f"❌ chat_with_llm 异常: {e}")
|
| 429 |
return f"大脑短路了: {e}"
|
| 430 |
|
| 431 |
+
|
| 432 |
# ---------- 处理文本消息 ----------
|
| 433 |
def handle_text_message(message_id, chat_id, text):
|
| 434 |
"""LLM (带历史) -> 发送,如有待处理图片则做针对 Vision"""
|