asemxin commited on
Commit
cbadacf
·
1 Parent(s): f00da0f

feat: 集成 Brave Search 工具调用(ReAct 循环)

Browse files
Files changed (1) hide show
  1. 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
- """优先转发给本地 OpenClaw Gateway带人设+插件),Fallback 到外部 LLM+SOUL"""
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
- data = resp.json()
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
- payload = {"model": MODEL_NAME, "messages": messages, "stream": False}
344
- log(f"🤖 外部 LLM ({MODEL_NAME}): {user_text[:50]}...")
345
- resp = requests.post(
346
- f"{API_BASE_URL}/chat/completions",
347
- headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
348
- json=payload, timeout=60
349
- )
350
- if resp.status_code == 200:
351
- reply = resp.json()["choices"][0]["message"]["content"]
352
- log(f"🤖 LLM 回复: {reply[:60]}...")
353
- return reply
354
- log(f"❌ 外部 LLM 错误 {resp.status_code}: {resp.text[:100]}")
355
- return f"思考时遇到错误 ({resp.status_code})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"""