openrouter.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """
  2. OpenRouter Provider
  3. 使用 OpenRouter API 调用各种模型(包括 Claude Sonnet 4.5)
  4. 支持 OpenAI 兼容的 API 格式
  5. """
  6. import os
  7. import json
  8. import httpx
  9. from typing import List, Dict, Any, Optional
  10. async def openrouter_llm_call(
  11. messages: List[Dict[str, Any]],
  12. model: str = "anthropic/claude-sonnet-4.5",
  13. tools: Optional[List[Dict]] = None,
  14. **kwargs
  15. ) -> Dict[str, Any]:
  16. """
  17. OpenRouter LLM 调用函数
  18. Args:
  19. messages: OpenAI 格式消息列表
  20. model: 模型名称(如 "anthropic/claude-sonnet-4.5")
  21. tools: OpenAI 格式工具定义
  22. **kwargs: 其他参数(temperature, max_tokens 等)
  23. Returns:
  24. {
  25. "content": str,
  26. "tool_calls": List[Dict] | None,
  27. "prompt_tokens": int,
  28. "completion_tokens": int,
  29. "cost": float
  30. }
  31. """
  32. api_key = os.getenv("OPEN_ROUTER_API_KEY")
  33. if not api_key:
  34. raise ValueError("OPEN_ROUTER_API_KEY environment variable not set")
  35. base_url = "https://openrouter.ai/api/v1"
  36. endpoint = f"{base_url}/chat/completions"
  37. # 构建请求
  38. payload = {
  39. "model": model,
  40. "messages": messages,
  41. }
  42. # 添加可选参数
  43. if tools:
  44. payload["tools"] = tools
  45. if "temperature" in kwargs:
  46. payload["temperature"] = kwargs["temperature"]
  47. if "max_tokens" in kwargs:
  48. payload["max_tokens"] = kwargs["max_tokens"]
  49. # OpenRouter 特定参数
  50. headers = {
  51. "Authorization": f"Bearer {api_key}",
  52. "HTTP-Referer": "https://github.com/your-repo", # 可选,用于统计
  53. "X-Title": "Agent Framework", # 可选,显示在 OpenRouter dashboard
  54. }
  55. # 调用 API
  56. async with httpx.AsyncClient(timeout=120.0) as client:
  57. try:
  58. response = await client.post(endpoint, json=payload, headers=headers)
  59. response.raise_for_status()
  60. result = response.json()
  61. except httpx.HTTPStatusError as e:
  62. error_body = e.response.text
  63. print(f"[OpenRouter] Error {e.response.status_code}: {error_body}")
  64. raise
  65. except Exception as e:
  66. print(f"[OpenRouter] Request failed: {e}")
  67. raise
  68. # 解析响应(OpenAI 格式)
  69. choice = result["choices"][0] if result.get("choices") else {}
  70. message = choice.get("message", {})
  71. content = message.get("content", "")
  72. tool_calls = message.get("tool_calls")
  73. # 提取 usage
  74. usage = result.get("usage", {})
  75. prompt_tokens = usage.get("prompt_tokens", 0)
  76. completion_tokens = usage.get("completion_tokens", 0)
  77. # 计算成本(OpenRouter 通常在响应中提供,但这里简化为 0)
  78. cost = 0.0
  79. return {
  80. "content": content,
  81. "tool_calls": tool_calls,
  82. "prompt_tokens": prompt_tokens,
  83. "completion_tokens": completion_tokens,
  84. "cost": cost
  85. }
  86. def create_openrouter_llm_call(
  87. model: str = "anthropic/claude-sonnet-4.5"
  88. ):
  89. """
  90. 创建 OpenRouter LLM 调用函数
  91. Args:
  92. model: 模型名称
  93. - "anthropic/claude-sonnet-4.5"
  94. - "anthropic/claude-opus-4.5"
  95. - "openai/gpt-4o"
  96. 等等
  97. Returns:
  98. 异步 LLM 调用函数
  99. """
  100. async def llm_call(
  101. messages: List[Dict[str, Any]],
  102. model: str = model,
  103. tools: Optional[List[Dict]] = None,
  104. **kwargs
  105. ) -> Dict[str, Any]:
  106. return await openrouter_llm_call(messages, model, tools, **kwargs)
  107. return llm_call