Files
LangBot/pkg/provider/modelmgr/requesters/anthropicmsgs.py

179 lines
6.1 KiB
Python
Raw Normal View History

2024-03-17 12:44:45 -04:00
from __future__ import annotations
import typing
import json
import platform
import socket
2024-03-17 12:44:45 -04:00
import anthropic
import httpx
2024-03-17 12:44:45 -04:00
from .. import errors, requester
2024-03-17 12:44:45 -04:00
from ....core import entities as core_entities
from ... import entities as llm_entities
from ...tools import entities as tools_entities
2024-05-22 20:09:29 +08:00
from ....utils import image
2024-03-17 12:44:45 -04:00
2024-11-21 23:28:19 +08:00
class AnthropicMessages(requester.LLMAPIRequester):
2024-03-17 12:44:45 -04:00
"""Anthropic Messages API 请求器"""
client: anthropic.AsyncAnthropic
2025-03-16 23:16:06 +08:00
default_config: dict[str, typing.Any] = {
'base_url': 'https://api.anthropic.com/v1',
2025-03-16 23:16:06 +08:00
'timeout': 120,
}
2024-03-17 12:44:45 -04:00
async def initialize(self):
# 兼容 Windows 缺失 TCP_KEEPINTVL 和 TCP_KEEPCNT 的问题
2025-05-10 17:47:14 +08:00
if platform.system() == 'Windows':
if not hasattr(socket, 'TCP_KEEPINTVL'):
socket.TCP_KEEPINTVL = 0
2025-05-10 17:47:14 +08:00
if not hasattr(socket, 'TCP_KEEPCNT'):
socket.TCP_KEEPCNT = 0
httpx_client = anthropic._base_client.AsyncHttpxClientWrapper(
base_url=self.requester_cfg['base_url'],
# cast to a valid type because mypy doesn't understand our type narrowing
timeout=typing.cast(httpx.Timeout, self.requester_cfg['timeout']),
limits=anthropic._constants.DEFAULT_CONNECTION_LIMITS,
follow_redirects=True,
2025-01-23 13:32:27 +08:00
trust_env=True,
2024-03-17 12:44:45 -04:00
)
self.client = anthropic.AsyncAnthropic(
api_key='',
http_client=httpx_client,
)
2024-03-17 12:44:45 -04:00
async def invoke_llm(
2024-03-17 12:44:45 -04:00
self,
query: core_entities.Query,
model: requester.RuntimeLLMModel,
messages: typing.List[llm_entities.Message],
funcs: typing.List[tools_entities.LLMFunction] = None,
extra_args: dict[str, typing.Any] = {},
) -> llm_entities.Message:
self.client.api_key = model.token_mgr.get_token()
2024-03-17 12:44:45 -04:00
args = extra_args.copy()
args['model'] = model.model_entity.name
2024-03-17 12:44:45 -04:00
2024-05-15 21:40:18 +08:00
# 处理消息
# system
system_role_message = None
for i, m in enumerate(messages):
if m.role == 'system':
2024-05-15 21:40:18 +08:00
system_role_message = m
break
if system_role_message:
messages.pop(i)
2025-05-10 18:04:58 +08:00
if isinstance(system_role_message, llm_entities.Message) and isinstance(system_role_message.content, str):
2024-05-15 21:40:18 +08:00
args['system'] = system_role_message.content
req_messages = []
for m in messages:
if m.role == 'tool':
tool_call_id = m.tool_call_id
req_messages.append(
{
'role': 'user',
'content': [
{
'type': 'tool_result',
'tool_use_id': tool_call_id,
'content': m.content,
}
],
}
)
continue
msg_dict = m.dict(exclude_none=True)
if isinstance(m.content, str) and m.content.strip() != '':
msg_dict['content'] = [{'type': 'text', 'text': m.content}]
2024-05-15 21:40:18 +08:00
elif isinstance(m.content, list):
2024-05-22 20:09:29 +08:00
for i, ce in enumerate(m.content):
if ce.type == 'image_base64':
2025-05-10 18:04:58 +08:00
image_b64, image_format = await image.extract_b64_and_format(ce.image_base64)
2024-05-22 20:09:29 +08:00
alter_image_ele = {
'type': 'image',
'source': {
'type': 'base64',
'media_type': f'image/{image_format}',
'data': image_b64,
},
2024-05-22 20:09:29 +08:00
}
msg_dict['content'][i] = alter_image_ele
2024-05-22 20:09:29 +08:00
if m.tool_calls:
for tool_call in m.tool_calls:
msg_dict['content'].append(
{
'type': 'tool_use',
'id': tool_call.id,
'name': tool_call.function.name,
'input': json.loads(tool_call.function.arguments),
}
)
del msg_dict['tool_calls']
2024-03-17 12:44:45 -04:00
req_messages.append(msg_dict)
args['messages'] = req_messages
if funcs:
tools = await self.ap.tool_mgr.generate_tools_for_anthropic(funcs)
2024-05-22 20:09:29 +08:00
if tools:
args['tools'] = tools
2024-05-22 20:09:29 +08:00
2024-03-17 23:22:26 -04:00
try:
# print(json.dumps(args, indent=4, ensure_ascii=False))
2024-03-17 23:22:26 -04:00
resp = await self.client.messages.create(**args)
args = {
'content': '',
'role': resp.role,
}
assert type(resp) is anthropic.types.message.Message
for block in resp.content:
2025-03-01 17:27:01 +01:00
if block.type == 'thinking':
2025-05-10 18:04:58 +08:00
args['content'] = '<think>' + block.thinking + '</think>\n' + args['content']
elif block.type == 'text':
args['content'] += block.text
elif block.type == 'tool_use':
assert type(block) is anthropic.types.tool_use_block.ToolUseBlock
tool_call = llm_entities.ToolCall(
id=block.id,
type='function',
2025-05-10 18:04:58 +08:00
function=llm_entities.FunctionCall(name=block.name, arguments=json.dumps(block.input)),
)
if 'tool_calls' not in args:
args['tool_calls'] = []
args['tool_calls'].append(tool_call)
return llm_entities.Message(**args)
2024-03-17 23:22:26 -04:00
except anthropic.AuthenticationError as e:
raise errors.RequesterError(f'api-key 无效: {e.message}')
except anthropic.BadRequestError as e:
raise errors.RequesterError(str(e.message))
except anthropic.NotFoundError as e:
if 'model: ' in str(e):
raise errors.RequesterError(f'模型无效: {e.message}')
else:
raise errors.RequesterError(f'请求地址无效: {e.message}')