Compare commits

...

47 Commits

Author SHA1 Message Date
Junyan Qin
02bc1fc45e chore: release v3.4.10.2 2025-03-05 21:12:52 +08:00
Junyan Qin (Chin)
5585981dc3 Merge pull request #1165 from fdc310/master
修复因为gewe将字段修改回原版而导致的我的判断逻辑问题
2025-03-05 21:12:21 +08:00
Dong_master
a4777f194b 修复因为gewe将字段修改回原版而导致的我的判断逻辑问题 2025-03-05 19:56:28 +08:00
Junyan Qin
41aeda8dc0 chore: release v3.4.10.1 2025-03-05 17:16:05 +08:00
Junyan Qin (Chin)
2ed522667e Merge pull request #1162 from fdc310/master
'修复了gewe更新回调参数data和typename字段改变造成的不回复的问题'
2025-03-05 17:14:27 +08:00
Dong_master
1932444666 '修复了gewe更新回调参数data和typename字段改变造成的不回复的问题' 2025-03-05 16:48:46 +08:00
Dong_master
b49b7e963d '修复了gewe更新回调参数data和typename字段改变造成的不回复的问题' 2025-03-05 00:54:39 +08:00
Junyan Qin
435c11ff27 doc(README): add more model in README 2025-03-03 21:26:39 +08:00
Junyan Qin
2e93600437 feat: update llm-models.json template 2025-03-03 21:02:48 +08:00
Junyan Qin (Chin)
faecb70d0f Merge pull request #1154 from Yi-Lyu/master
将微信消息时间戳传递给 dify,便于 dify 通过消息时间戳来做业务逻辑。
2025-03-02 20:21:08 +08:00
Junyan Qin
92e1ac5c3a feat: add supports for passing time to dify workflow 2025-03-02 20:18:33 +08:00
Junyan Qin
8963a2117b feat: add field time in MessageEvent 2025-03-02 20:16:34 +08:00
Ethan
aa300258ab feat: enhance user message preprocessing to include message creation timestamp 2025-03-02 19:45:10 +08:00
Ethan
48841daff5 feat: enhance user message preprocessing to include message creation timestamp 2025-03-02 19:30:07 +08:00
Ethan
8878f1ed87 feat: enhance user message preprocessing to include message creation timestamp 2025-03-02 19:20:10 +08:00
Ethan
f6205d79c0 feat: enhance user message preprocessing to include message creation timestamp 2025-03-02 19:18:26 +08:00
Ethan
d6d5dac6b3 feat: enhance user message preprocessing to include message creation timestamp 2025-03-02 19:10:53 +08:00
Ethan
05b979e68a feat: enhance user message preprocessing to include message creation timestamp 2025-03-02 19:10:09 +08:00
Ethan
9f7d9e4c0d feat: enhance user message preprocessing to include message creation timestamp 2025-03-02 18:49:32 +08:00
Junyan Qin
98a9fed726 chore: release v3.4.10 2025-03-02 18:08:59 +08:00
Junyan Qin
720a218259 perf: simplify platform entities 2025-03-02 17:59:13 +08:00
Junyan Qin (Chin)
60c0adc6f9 Merge pull request #1152 from RockChinQ/feat/dingtalk-audio
feat(dingtalk): add supports for audio receiving
2025-03-02 17:38:19 +08:00
Junyan Qin
bc8c346e68 fix(dingtalk): group and person id not distinguished 2025-03-02 17:35:06 +08:00
Junyan Qin
a198b6da0b feat(dingtalk): add supports for audio receiving 2025-03-02 17:03:19 +08:00
Junyan Qin (Chin)
0f3dc35df4 Merge pull request #1150 from Tigrex-Dai/master
添加针对Anthropic新模型的thinking开关
2025-03-02 15:39:58 +08:00
Junyan Qin
7b6e6b046a perf(claude): simplify the thinking resp processing 2025-03-02 15:24:08 +08:00
Tigrex Dai
9e503191d6 Update anthropicmsgs.py 2025-03-01 17:27:01 +01:00
Tigrex Dai
1fd23a0d8d Merge branch 'RockChinQ:master' into master 2025-03-01 16:53:23 +01:00
Junyan Qin
3811700a78 chore: update llm-models.json template 2025-03-01 21:33:47 +08:00
Junyan Qin
8762ba3d9c feat(anthropic): add supports for tool use #763 2025-03-01 20:34:22 +08:00
Junyan Qin
c42b5aab5a feat: update components.yaml 2025-03-01 11:45:15 +08:00
Junyan Qin (Chin)
d724899ec0 Merge pull request #1148 from RockChinQ/feat/requester-manifests
feat: add manifests for all requesters
2025-03-01 00:12:55 +08:00
Junyan Qin
81aacdd76e refactor: lookup requester from discover engine 2025-03-01 00:12:23 +08:00
Junyan Qin
0aa072b4e8 feat: add manifests for all requesters 2025-02-28 22:47:34 +08:00
Tigrex Dai
6335e9dd8b Update anthropicmsgs.py 2025-02-28 13:02:06 +01:00
Tigrex Dai
a785289ac9 Update entities.py 2025-02-28 13:00:44 +01:00
Junyan Qin (Chin)
f8bace040c Merge pull request #1142 from fdc310/master
个人微信中主动发送图片的修改,但是只能发送链接
2025-02-28 11:33:43 +08:00
Dong_master
d62d597695 '个人微信中主动发送图片的修改,但是只能发送链接' 2025-02-28 01:31:59 +08:00
Dong_master
d938129884 '删除先' 2025-02-28 01:30:55 +08:00
Dong_master
327f448321 Resolved merge conflict in gewechat.py 2025-02-28 01:22:15 +08:00
Dong_master
19af3740c1 '个人微信中主动发送图片的修改,但是只能发送链接' 2025-02-28 01:17:25 +08:00
Junyan Qin
11b1110eed chore: release v3.4.9.5 2025-02-27 17:04:54 +08:00
Junyan Qin (Chin)
682b897e21 Merge pull request #1130 from fdc310/master
'个人微信中主动发送信息send_message的修改'
2025-02-26 15:54:02 +08:00
Junyan Qin
998ad7623c perf(gewechat): simplify 2025-02-26 15:53:26 +08:00
Junyan Qin (Chin)
4f1db33abc Merge pull request #1131 from shockno1/master
Update gewechat.py 添加gewe微信接口中voice语音的处理
2025-02-26 15:38:56 +08:00
shockno1
ca6cb60bdd Update gewechat.py 添加gewe微信接口中voice语音的处理
添加gewe微信接口中voice语音的处理
2025-02-26 12:45:28 +08:00
Dong_master
133e48a5a9 '个人微信中主动发送信息send_message的修改' 2025-02-26 02:54:42 +08:00
41 changed files with 691 additions and 216 deletions

View File

@@ -115,6 +115,20 @@
| [阿里云百炼](https://bailian.console.aliyun.com/) | ✅ | 大模型聚合平台, LLMOps 平台 |
| [火山方舟](https://console.volcengine.com/ark/region:ark+cn-beijing/model?vendor=Bytedance&view=LIST_VIEW) | ✅ | 大模型聚合平台, LLMOps 平台 |
### TTS
| 平台/模型 | 备注 |
| --- | --- |
| [FishAudio](https://fish.audio/zh-CN/discovery/) | [插件](https://github.com/the-lazy-me/NewChatVoice) |
| [海豚 AI](https://www.ttson.cn/?source=thelazy) | [插件](https://github.com/the-lazy-me/NewChatVoice) |
| [AzureTTS](https://portal.azure.com/) | [插件](https://github.com/Ingnaryk/LangBot_AzureTTS) |
### 文生图
| 平台/模型 | 备注 |
| --- | --- |
| 阿里云百炼 | [插件](https://github.com/Thetail001/LangBot_BailianTextToImagePlugin)
## 😘 社区贡献
LangBot 离不开以下贡献者和社区内所有人的贡献,我们欢迎任何形式的贡献和反馈。

View File

@@ -10,6 +10,10 @@ spec:
ComponentTemplate:
fromFiles:
- pkg/platform/adapter.yaml
- pkg/provider/modelmgr/requester.yaml
MessagePlatformAdapter:
fromDirs:
- path: pkg/platform/sources/
LLMAPIRequester:
fromDirs:
- path: pkg/provider/modelmgr/requesters/

View File

@@ -1,4 +1,5 @@
import asyncio
import json
import dingtalk_stream
from dingtalk_stream import AckMessage
@@ -17,7 +18,7 @@ class EchoTextHandler(dingtalk_stream.ChatbotHandler):
await self.client.update_incoming_message(incoming_message)
return AckMessage.STATUS_OK, 'OK'
async def get_incoming_message(self):
"""异步等待消息的到来"""
while self.incoming_message is None:

View File

@@ -1,4 +1,5 @@
import base64
import json
import time
from typing import Callable
import dingtalk_stream
@@ -92,8 +93,31 @@ class DingTalkClient:
base64_str = base64.b64encode(file_bytes).decode('utf-8') # 返回字符串格式
return base64_str
else:
raise Exception("获取图片失败")
raise Exception("获取文件失败")
async def get_audio_url(self,download_code:str):
if not await self.check_access_token():
await self.get_access_token()
url = 'https://api.dingtalk.com/v1.0/robot/messageFiles/download'
params = {
"downloadCode":download_code,
"robotCode":self.robot_code
}
headers ={
"x-acs-dingtalk-access-token": self.access_token
}
async with httpx.AsyncClient() as client:
response = await client.post(url, headers=headers, json=params)
if response.status_code == 200:
result = response.json()
download_url = result.get("downloadUrl")
if download_url:
return await self.download_url_to_base64(download_url)
else:
raise Exception("获取音频失败")
else:
raise Exception(f"Error: {response.status_code}, {response.text}")
async def update_incoming_message(self, message):
"""异步更新 DingTalkClient 中的 incoming_message"""
message_data = await self.get_message(message)
@@ -133,6 +157,7 @@ class DingTalkClient:
async def get_message(self,incoming_message:dingtalk_stream.chatbot.ChatbotMessage):
try:
# print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False))
message_data = {
"IncomingMessage":incoming_message,
}
@@ -160,10 +185,14 @@ class DingTalkClient:
message_data['Picture'] = await self.download_image(incoming_message.get_image_list()[0])
message_data['Type'] = 'image'
elif incoming_message.message_type == 'audio':
message_data['Audio'] = await self.get_audio_url(incoming_message.to_dict()['content']['downloadCode'])
# 删掉开头的@消息
if message_data["Content"].startswith("@"+self.robot_name):
message_data["Content"][len("@"+self.robot_name):]
message_data['Type'] = 'audio'
copy_message_data = message_data.copy()
del copy_message_data['IncomingMessage']
# print("message_data:", json.dumps(copy_message_data, indent=4, ensure_ascii=False))
except Exception:
traceback.print_exc()

View File

@@ -1,4 +1,5 @@
from typing import Dict, Any, Optional
import dingtalk_stream
class DingTalkEvent(dict):
@staticmethod
@@ -15,7 +16,7 @@ class DingTalkEvent(dict):
return self.get("Content","")
@property
def incoming_message(self):
def incoming_message(self) -> Optional["dingtalk_stream.chatbot.ChatbotMessage"]:
return self.get("IncomingMessage")
@property
@@ -25,6 +26,10 @@ class DingTalkEvent(dict):
@property
def picture(self):
return self.get("Picture","")
@property
def audio(self):
return self.get("Audio","")
@property
def conversation(self):
@@ -61,4 +66,4 @@ class DingTalkEvent(dict):
Returns:
str: 字符串表示。
"""
return f"<WecomEvent {super().__repr__()}>"
return f"<DingTalkEvent {super().__repr__()}>"

View File

@@ -187,6 +187,9 @@ class ComponentDiscoveryEngine:
if name == 'ComponentTemplate':
continue
components[name] = self.load_blueprint_comp_group(component, owner)
self.ap.logger.debug(f'Components: {components}')
return blueprint_manifest, components

View File

@@ -28,16 +28,23 @@ class DingTalkMessageConverter(adapter.MessageConverter):
return msg.text
@staticmethod
async def target2yiri(event:DingTalkEvent):
async def target2yiri(event:DingTalkEvent, bot_name:str):
yiri_msg_list = []
yiri_msg_list.append(
platform_message.Source(id = '0',time=datetime.datetime.now())
platform_message.Source(id = event.incoming_message.message_id,time=datetime.datetime.now())
)
for atUser in event.incoming_message.at_users:
if atUser.dingtalk_id == event.incoming_message.chatbot_user_id:
yiri_msg_list.append(platform_message.At(target=bot_name))
if event.content:
yiri_msg_list.append(platform_message.Plain(text=event.content))
text_content = event.content.replace("@"+bot_name, '')
yiri_msg_list.append(platform_message.Plain(text=text_content))
if event.picture:
yiri_msg_list.append(platform_message.Image(base64=event.picture))
if event.audio:
yiri_msg_list.append(platform_message.Voice(base64=event.audio))
chain = platform_message.MessageChain(yiri_msg_list)
@@ -54,33 +61,33 @@ class DingTalkEventConverter(adapter.EventConverter):
@staticmethod
async def target2yiri(
event:DingTalkEvent
event:DingTalkEvent,
bot_name:str
):
message_chain = await DingTalkMessageConverter.target2yiri(event)
message_chain = await DingTalkMessageConverter.target2yiri(event, bot_name)
if event.conversation == 'FriendMessage':
return platform_events.FriendMessage(
sender=platform_entities.Friend(
id= 0,
nickname ='nickname',
id=event.incoming_message.sender_id,
nickname = event.incoming_message.sender_nick,
remark=""
),
message_chain = message_chain,
time = datetime.datetime.now(),
time = event.incoming_message.create_at,
source_platform_object=event,
)
elif event.conversation == 'GroupMessage':
message_chain.insert(0, platform_message.At(target="justbot"))
sender = platform_entities.GroupMember(
id = 111,
member_name="name",
id = event.incoming_message.sender_id,
member_name=event.incoming_message.sender_nick,
permission= 'MEMBER',
group = platform_entities.Group(
id = 111,
name = 'MEMBER',
id = event.incoming_message.conversation_id,
name = event.incoming_message.conversation_title,
permission=platform_entities.Permission.Member
),
special_title='',
@@ -88,7 +95,7 @@ class DingTalkEventConverter(adapter.EventConverter):
last_speak_timestamp=0,
mute_time_remaining=0
)
time = datetime.datetime.now(),
time = event.incoming_message.create_at
return platform_events.GroupMessage(
sender =sender,
message_chain = message_chain,
@@ -117,6 +124,8 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
missing_keys = [key for key in required_keys if key not in config]
if missing_keys:
raise ParamNotEnoughError("钉钉缺少相关配置项,请查看文档或联系管理员")
self.bot_account_id = self.config["robot_name"]
self.bot = DingTalkClient(
client_id=config["client_id"],
@@ -153,10 +162,9 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
],
):
async def on_message(event: DingTalkEvent):
self.bot_account_id = 'justbot'
try:
return await callback(
await self.event_converter.target2yiri(event), self
await self.event_converter.target2yiri(event, self.config["robot_name"]), self
)
except:
traceback.print_exc()
@@ -167,7 +175,6 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
self.bot.on_message("GroupMessage")(on_message)
async def run_async(self):
await self.bot.start()
async def kill(self) -> bool:

View File

@@ -43,8 +43,13 @@ class GewechatMessageConverter(adapter.MessageConverter):
elif isinstance(component, platform_message.Plain):
content_list.append({"type": "text", "content": component.text})
elif isinstance(component, platform_message.Image):
# content_list.append({"type": "image", "image_id": component.image_id})
pass
if not component.url:
pass
content_list.append({"type": "image", "image": component.url})
elif isinstance(component, platform_message.Voice):
content_list.append({"type": "voice", "url": component.url, "length": component.length})
elif isinstance(component, platform_message.Forward):
for node in component.node_list:
content_list.extend(await GewechatMessageConverter.yiri2target(node.message_chain))
@@ -83,6 +88,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
platform_message.Plain(text="[图片内容为空]")
])
try:
base64_str, image_format = await image.get_gewechat_image_base64(
gewechat_url=self.config["gewechat_url"],
@@ -101,9 +107,13 @@ class GewechatMessageConverter(adapter.MessageConverter):
except Exception as e:
print(f"处理图片消息失败: {str(e)}")
return platform_message.MessageChain([
platform_message.Plain(text="[图片处理失败]")
platform_message.Plain(text=f"[图片处理失败]")
])
elif message["Data"]["MsgType"] == 34:
audio_base64 = message["Data"]["ImgBuf"]["buffer"]
return platform_message.MessageChain(
[platform_message.Voice(base64=f"data:audio/silk;base64,{audio_base64}")]
)
elif message["Data"]["MsgType"] == 49:
# 支持微信聊天记录的消息类型,将 XML 内容转换为 MessageChain 传递
try:
@@ -217,6 +227,13 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
async def gewechat_callback():
data = await quart.request.json
# print(json.dumps(data, indent=4, ensure_ascii=False))
if 'data' in data:
data['Data'] = data['data']
if 'type_name' in data:
data['TypeName'] = data['type_name']
# print(json.dumps(data, indent=4, ensure_ascii=False))
if 'testMsg' in data:
return 'ok'
@@ -238,7 +255,19 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
target_id: str,
message: platform_message.MessageChain
):
pass
geweap_msg = await self.message_converter.yiri2target(message)
# 此处加上群消息at处理
# ats = [item["target"] for item in geweap_msg if item["type"] == "at"]
for msg in geweap_msg:
if msg['type'] == 'text':
await self.bot.post_text(app_id=self.config['app_id'], to_wxid=target_id, content=msg['content'])
elif msg['type'] == 'image':
await self.bot.post_image(app_id=self.config['app_id'], to_wxid=target_id, img_url=msg["image"])
async def reply_message(
self,
@@ -336,4 +365,4 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
)
async def kill(self) -> bool:
pass
pass

View File

@@ -74,7 +74,11 @@ class QQOfficialEventConverter(adapter.EventConverter):
remark = "",
)
return platform_events.FriendMessage(
sender = friend,message_chain = yiri_chain,time = event.timestamp,
sender = friend,message_chain = yiri_chain,time = int(
datetime.datetime.strptime(
event.timestamp, "%Y-%m-%dT%H:%M:%S%z"
).timestamp()
),
source_platform_object=event
)
@@ -105,7 +109,11 @@ class QQOfficialEventConverter(adapter.EventConverter):
last_speak_timestamp=0,
mute_time_remaining=0
)
time = event.timestamp
time = int(
datetime.datetime.strptime(
event.timestamp, "%Y-%m-%dT%H:%M:%S%z"
).timestamp()
)
return platform_events.GroupMessage(
sender = sender,
message_chain=yiri_chain,
@@ -128,7 +136,11 @@ class QQOfficialEventConverter(adapter.EventConverter):
last_speak_timestamp=0,
mute_time_remaining=0
)
time = event.timestamp,
time = int(
datetime.datetime.strptime(
event.timestamp, "%Y-%m-%dT%H:%M:%S%z"
).timestamp()
),
return platform_events.GroupMessage(
sender =sender,
message_chain = yiri_chain,

View File

@@ -13,27 +13,20 @@ import pydantic.v1 as pydantic
class Entity(pydantic.BaseModel):
"""实体,表示一个用户或群。"""
id: int
"""QQ 号或群号"""
@abc.abstractmethod
def get_avatar_url(self) -> str:
"""头像图片链接。"""
"""ID"""
@abc.abstractmethod
def get_name(self) -> str:
"""名称。"""
class Friend(Entity):
"""好友"""
"""私聊对象"""
id: typing.Union[int, str]
"""QQ 号"""
"""ID"""
nickname: typing.Optional[str]
"""昵称。"""
remark: typing.Optional[str]
"""备注。"""
def get_avatar_url(self) -> str:
return f'http://q4.qlogo.cn/g?b=qq&nk={self.id}&s=140'
def get_name(self) -> str:
return self.nickname or self.remark or ''
@@ -59,8 +52,6 @@ class Group(Entity):
"""群名称。"""
permission: Permission
"""Bot 在群中的权限。"""
def get_avatar_url(self) -> str:
return f'https://p.qlogo.cn/gh/{self.id}/{self.id}/'
def get_name(self) -> str:
return self.name
@@ -69,11 +60,11 @@ class Group(Entity):
class GroupMember(Entity):
"""群成员。"""
id: typing.Union[int, str]
"""QQ 号"""
"""群员 ID"""
member_name: str
"""员名称。"""
"""群员名称。"""
permission: Permission
"""Bot 在群中的权限。"""
"""在群中的权限。"""
group: Group
"""群。"""
special_title: str = ''
@@ -84,61 +75,6 @@ class GroupMember(Entity):
"""最后一次发言的时间。"""
mute_time_remaining: int = 0
"""禁言剩余时间。"""
def get_avatar_url(self) -> str:
return f'http://q4.qlogo.cn/g?b=qq&nk={self.id}&s=140'
def get_name(self) -> str:
return self.member_name
class Client(Entity):
"""来自其他客户端的用户。"""
id: typing.Union[int, str]
"""识别 id。"""
platform: str
"""来源平台。"""
def get_avatar_url(self) -> str:
raise NotImplementedError
def get_name(self) -> str:
return self.platform
class Subject(pydantic.BaseModel):
"""另一种实体类型表示。"""
id: typing.Union[int, str]
"""QQ 号或群号。"""
kind: typing.Literal['Friend', 'Group', 'Stranger']
"""类型。"""
class Config(pydantic.BaseModel):
"""配置项类型。"""
def modify(self, **kwargs) -> 'Config':
"""修改部分设置。"""
for k, v in kwargs.items():
if k in self.__fields__:
setattr(self, k, v)
else:
raise ValueError(f'未知配置项: {k}')
return self
class GroupConfigModel(Config):
"""群配置。"""
name: str
"""群名称。"""
confess_talk: bool
"""是否允许坦白说。"""
allow_member_invite: bool
"""是否允许成员邀请好友入群。"""
auto_approve: bool
"""是否开启自动审批入群。"""
anonymous_chat: bool
"""是否开启匿名聊天。"""
announcement: str = ''
"""群公告。"""
class MemberInfoModel(Config, GroupMember):
"""群成员信息。"""

View File

@@ -43,21 +43,6 @@ class Event(pydantic.BaseModel):
return Event
###############################
# Bot Event
class BotEvent(Event):
"""Bot 自身事件。
Args:
type: 事件名。
qq: Bot 的 QQ 号。
"""
type: str
"""事件名。"""
qq: int
"""Bot 的 QQ 号。"""
###############################
# Message Event
class MessageEvent(Event):
@@ -72,6 +57,9 @@ class MessageEvent(Event):
message_chain: platform_message.MessageChain
"""消息内容。"""
time: float | None = None
"""消息发送时间戳。"""
source_platform_object: typing.Optional[typing.Any] = None
"""原消息平台对象。
供消息平台适配器开发者使用,如果回复用户时需要使用原消息事件对象的信息,
@@ -79,7 +67,7 @@ class MessageEvent(Event):
class FriendMessage(MessageEvent):
"""好友消息。
"""私聊消息。
Args:
type: 事件名。
@@ -111,19 +99,3 @@ class GroupMessage(MessageEvent):
@property
def group(self) -> platform_entities.Group:
return self.sender.group
class StrangerMessage(MessageEvent):
"""陌生人消息。
Args:
type: 事件名。
sender: 发送消息的人。
message_chain: 消息内容。
"""
type: str = 'StrangerMessage'
"""事件名。"""
sender: platform_entities.Friend
"""发送消息的人。"""
message_chain: platform_message.MessageChain
"""消息内容。"""

View File

@@ -116,18 +116,6 @@ class MessageChain(PlatformBaseModel):
print('At Me')
```
消息链对索引操作进行了增强。以消息组件类型为索引,获取消息链中的全部该类型的消息组件。
```py
plain_list = message_chain[Plain]
'[Plain("Hello World!")]'
```
可以用加号连接两个消息链。
```py
MessageChain(['Hello World!']) + MessageChain(['Goodbye World!'])
# 返回 MessageChain([Plain("Hello World!"), Plain("Goodbye World!")])
```
"""
__root__: typing.List[MessageComponent]
@@ -488,9 +476,9 @@ class Quote(MessageComponent):
group_id: typing.Optional[typing.Union[int, str]] = None
"""被引用回复的原消息所接收的群号当为好友消息时为0。"""
sender_id: typing.Optional[typing.Union[int, str]] = None
"""被引用回复的原消息的发送者的QQ号"""
"""被引用回复的原消息的发送者的ID"""
target_id: typing.Optional[typing.Union[int, str]] = None
"""被引用回复的原消息的接收者者的QQ号或群号"""
"""被引用回复的原消息的接收者者的ID或群ID"""
origin: MessageChain
"""被引用回复的原消息的消息链对象。"""
@@ -504,7 +492,7 @@ class At(MessageComponent):
type: str = "At"
"""消息组件类型。"""
target: typing.Union[int, str]
"""群员 QQ 号"""
"""群员 ID"""
display: typing.Optional[str] = None
"""At时显示的文字发送消息时无效自动使用群名片。"""
def __eq__(self, other):
@@ -527,9 +515,9 @@ class Image(MessageComponent):
type: str = "Image"
"""消息组件类型。"""
image_id: typing.Optional[str] = None
"""图片的 image_id群图片与好友图片格式不同。不为空时将忽略 url 属性。"""
"""图片的 image_id不为空时将忽略 url 属性。"""
url: typing.Optional[pydantic.HttpUrl] = None
"""图片的 URL发送时可作网络图片的链接接收时为腾讯图片服务器的链接,可用于图片下载。"""
"""图片的 URL发送时可作网络图片的链接接收时为图片的链接,可用于图片下载。"""
path: typing.Union[str, Path, None] = None
"""图片的路径,发送本地图片。"""
base64: typing.Optional[str] = None
@@ -663,7 +651,7 @@ class Voice(MessageComponent):
voice_id: typing.Optional[str] = None
"""语音的 voice_id不为空时将忽略 url 属性。"""
url: typing.Optional[str] = None
"""语音的 URL发送时可作网络语音的链接接收时为腾讯语音服务器的链接,可用于语音下载。"""
"""语音的 URL发送时可作网络语音的链接接收时为语音文件的链接,可用于语音下载。"""
path: typing.Optional[str] = None
"""语音的路径,发送本地语音。"""
base64: typing.Optional[str] = None
@@ -691,8 +679,6 @@ class Voice(MessageComponent):
):
"""下载语音到本地。
语音采用 silk v3 格式silk 格式的编码解码请使用 [graiax-silkcoder](https://pypi.org/project/graiax-silkcoder/)。
Args:
filename: 下载到本地的文件路径。与 `directory` 二选一。
directory: 下载到本地的文件夹路径。与 `filename` 二选一。
@@ -750,13 +736,13 @@ class Voice(MessageComponent):
class ForwardMessageNode(pydantic.BaseModel):
"""合并转发中的一条消息。"""
sender_id: typing.Optional[typing.Union[int, str]] = None
"""发送人QQ号"""
"""发送人ID"""
sender_name: typing.Optional[str] = None
"""显示名称。"""
message_chain: typing.Optional[MessageChain] = None
"""消息内容。"""
message_id: typing.Optional[int] = None
"""消息的 message_id,可以只使用此属性,从缓存中读取消息内容"""
"""消息的 message_id。"""
time: typing.Optional[datetime] = None
"""发送时间。"""
@pydantic.validator('message_chain', check_fields=False)

View File

@@ -4,7 +4,7 @@ import aiohttp
from . import entities, requester
from ...core import app
from ...discover import engine
from . import token
from .requesters import bailianchatcmpl, chatcmpl, anthropicmsgs, moonshotchatcmpl, deepseekchatcmpl, ollamachat, giteeaichatcmpl, volcarkchatcmpl, xaichatcmpl, zhipuaichatcmpl, lmstudiochatcmpl, siliconflowchatcmpl, volcarkchatcmpl
@@ -16,6 +16,8 @@ class ModelManager:
ap: app.Application
requester_components: list[engine.Component]
model_list: list[entities.LLMModelInfo]
requesters: dict[str, requester.LLMAPIRequester]
@@ -38,14 +40,21 @@ class ModelManager:
async def initialize(self):
self.requester_components = self.ap.discover.get_components_by_kind('LLMAPIRequester')
# 初始化token_mgr, requester
for k, v in self.ap.provider_cfg.data['keys'].items():
self.token_mgrs[k] = token.TokenManager(k, v)
for api_cls in requester.preregistered_requesters:
# for api_cls in requester.preregistered_requesters:
# api_inst = api_cls(self.ap)
# await api_inst.initialize()
# self.requesters[api_inst.name] = api_inst
for component in self.requester_components:
api_cls = component.get_python_component_class()
api_inst = api_cls(self.ap)
await api_inst.initialize()
self.requesters[api_inst.name] = api_inst
self.requesters[component.metadata.name] = api_inst
# 尝试从api获取最新的模型信息
try:

View File

@@ -10,18 +10,6 @@ from . import entities as modelmgr_entities
from ..tools import entities as tools_entities
preregistered_requesters: list[typing.Type[LLMAPIRequester]] = []
def requester_class(name: str):
def decorator(cls: typing.Type[LLMAPIRequester]) -> typing.Type[LLMAPIRequester]:
cls.name = name
preregistered_requesters.append(cls)
return cls
return decorator
class LLMAPIRequester(metaclass=abc.ABCMeta):
"""LLM API请求器
"""

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import typing
import json
import traceback
import base64
@@ -16,7 +17,6 @@ from ...tools import entities as tools_entities
from ....utils import image
@requester.requester_class("anthropic-messages")
class AnthropicMessages(requester.LLMAPIRequester):
"""Anthropic Messages API 请求器"""
@@ -69,11 +69,32 @@ class AnthropicMessages(requester.LLMAPIRequester):
req_messages = []
for m in messages:
if isinstance(m.content, str) and m.content.strip() != "":
req_messages.append(m.dict(exclude_none=True))
elif isinstance(m.content, list):
if m.role == 'tool':
tool_call_id = m.tool_call_id
msg_dict = m.dict(exclude_none=True)
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
}
]
elif isinstance(m.content, list):
for i, ce in enumerate(m.content):
@@ -90,25 +111,60 @@ class AnthropicMessages(requester.LLMAPIRequester):
}
msg_dict["content"][i] = alter_image_ele
req_messages.append(msg_dict)
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"]
req_messages.append(msg_dict)
args["messages"] = req_messages
if funcs:
tools = await self.ap.tool_mgr.generate_tools_for_anthropic(funcs)
# anthropic的tools处在beta阶段sdk不稳定故暂时不支持
#
# if funcs:
# tools = await self.ap.tool_mgr.generate_tools_for_openai(funcs)
# if tools:
# args["tools"] = tools
if tools:
args["tools"] = tools
try:
# print(json.dumps(args, indent=4, ensure_ascii=False))
resp = await self.client.messages.create(**args)
return llm_entities.Message(
content=resp.content[0].text,
role=resp.role
)
args = {
'content': '',
'role': resp.role,
}
assert type(resp) is anthropic.types.message.Message
for block in resp.content:
if block.type == 'thinking':
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",
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)
except anthropic.AuthenticationError as e:
raise errors.RequesterError(f'api-key 无效: {e.message}')
except anthropic.BadRequestError as e:

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: anthropic-messages
label:
en_US: Anthropic
zh_CN: Anthropic
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://api.anthropic.com/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./anthropicmsgs.py
attr: AnthropicMessages

View File

@@ -7,7 +7,6 @@ from .. import requester
from ....core import app
@requester.requester_class("bailian-chat-completions")
class BailianChatCompletions(chatcmpl.OpenAIChatCompletions):
"""阿里云百炼大模型平台 ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: bailian-chat-completions
label:
en_US: Aliyun Bailian
zh_CN: 阿里云百炼
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://dashscope.aliyuncs.com/compatible-mode/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./bailianchatcmpl.py
attr: BailianChatCompletions

View File

@@ -20,7 +20,6 @@ from ...tools import entities as tools_entities
from ....utils import image
@requester.requester_class("openai-chat-completions")
class OpenAIChatCompletions(requester.LLMAPIRequester):
"""OpenAI ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: openai-chat-completions
label:
en_US: OpenAI
zh_CN: OpenAI
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://api.openai.com/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./chatcmpl.py
attr: OpenAIChatCompletions

View File

@@ -7,7 +7,6 @@ from ... import entities as llm_entities
from ...tools import entities as tools_entities
@requester.requester_class("deepseek-chat-completions")
class DeepseekChatCompletions(chatcmpl.OpenAIChatCompletions):
"""Deepseek ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: deepseek-chat-completions
label:
en_US: DeepSeek
zh_CN: 深度求索
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://api.deepseek.com"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./deepseekchatcmpl.py
attr: DeepseekChatCompletions

View File

@@ -14,7 +14,6 @@ from ...tools import entities as tools_entities
from .. import entities as modelmgr_entities
@requester.requester_class("gitee-ai-chat-completions")
class GiteeAIChatCompletions(chatcmpl.OpenAIChatCompletions):
"""Gitee AI ChatCompletions API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: gitee-ai-chat-completions
label:
en_US: Gitee AI
zh_CN: Gitee AI
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://ai.gitee.com/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./giteeaichatcmpl.py
attr: GiteeAIChatCompletions

View File

@@ -7,7 +7,6 @@ from .. import requester
from ....core import app
@requester.requester_class("lmstudio-chat-completions")
class LmStudioChatCompletions(chatcmpl.OpenAIChatCompletions):
"""LMStudio ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: lmstudio-chat-completions
label:
en_US: LM Studio
zh_CN: LM Studio
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "http://127.0.0.1:1234/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./lmstudiochatcmpl.py
attr: LmStudioChatCompletions

View File

@@ -9,7 +9,6 @@ from ... import entities as llm_entities
from ...tools import entities as tools_entities
@requester.requester_class("moonshot-chat-completions")
class MoonshotChatCompletions(chatcmpl.OpenAIChatCompletions):
"""Moonshot ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: moonshot-chat-completions
label:
en_US: Moonshot
zh_CN: 月之暗面
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://api.moonshot.com/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./moonshotchatcmpl.py
attr: MoonshotChatCompletions

View File

@@ -20,7 +20,6 @@ from ....utils import image
REQUESTER_NAME: str = "ollama-chat"
@requester.requester_class(REQUESTER_NAME)
class OllamaChatCompletions(requester.LLMAPIRequester):
"""Ollama平台 ChatCompletion API请求器"""
client: ollama.AsyncClient

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: ollama-chat
label:
en_US: Ollama
zh_CN: Ollama
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "http://127.0.0.1:11434"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./ollamachat.py
attr: OllamaChatCompletions

View File

@@ -7,7 +7,6 @@ from .. import requester
from ....core import app
@requester.requester_class("siliconflow-chat-completions")
class SiliconFlowChatCompletions(chatcmpl.OpenAIChatCompletions):
"""SiliconFlow ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: siliconflow-chat-completions
label:
en_US: SiliconFlow
zh_CN: 硅基流动
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://api.siliconflow.cn/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./siliconflowchatcmpl.py
attr: SiliconFlowChatCompletions

View File

@@ -7,7 +7,6 @@ from .. import requester
from ....core import app
@requester.requester_class("volcark-chat-completions")
class VolcArkChatCompletions(chatcmpl.OpenAIChatCompletions):
"""火山方舟大模型平台 ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: volcark-chat-completions
label:
en_US: Volc Engine Ark
zh_CN: 火山方舟
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://ark.cn-beijing.volces.com/api/v3"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./volcarkchatcmpl.py
attr: VolcArkChatCompletions

View File

@@ -7,7 +7,6 @@ from .. import requester
from ....core import app
@requester.requester_class("xai-chat-completions")
class XaiChatCompletions(chatcmpl.OpenAIChatCompletions):
"""xAI ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: xai-chat-completions
label:
en_US: xAI
zh_CN: xAI
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://api.x.ai/v1"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./xaichatcmpl.py
attr: XaiChatCompletions

View File

@@ -7,7 +7,6 @@ from . import chatcmpl
from .. import requester
@requester.requester_class("zhipuai-chat-completions")
class ZhipuAIChatCompletions(chatcmpl.OpenAIChatCompletions):
"""智谱AI ChatCompletion API 请求器"""

View File

@@ -0,0 +1,34 @@
apiVersion: v1
kind: LLMAPIRequester
metadata:
name: zhipuai-chat-completions
label:
en_US: ZhipuAI
zh_CN: 智谱 AI
spec:
config:
- name: base-url
label:
en_US: Base URL
zh_CN: 基础 URL
type: string
required: true
default: "https://open.bigmodel.cn/api/paas/v4"
- name: args
label:
en_US: Args
zh_CN: 附加参数
type: object
required: true
default: {}
- name: timeout
label:
en_US: Timeout
zh_CN: 超时时间
type: int
required: true
default: 120
execution:
python:
path: ./zhipuaichatcmpl.py
attr: ZhipuAIChatCompletions

View File

@@ -5,6 +5,7 @@ import json
import uuid
import re
import base64
import datetime
import aiohttp
@@ -69,6 +70,7 @@ class DifyServiceAPIRunner(runner.RequestRunner):
"""
plain_text = ""
image_ids = []
if isinstance(query.user_message.content, list):
for ce in query.user_message.content:
if ce.type == "text":
@@ -230,6 +232,9 @@ class DifyServiceAPIRunner(runner.RequestRunner):
plain_text, image_ids = await self._preprocess_user_message(query)
# 尝试获取 CreateTime
create_time = int(query.message_event.time) if query.message_event.time else int(datetime.datetime.now().timestamp())
files = [
{
"type": "image",
@@ -246,6 +251,7 @@ class DifyServiceAPIRunner(runner.RequestRunner):
"langbot_user_message_text": plain_text,
"langbot_session_id": f"{query.session.launcher_type.value}_{query.session.launcher_id}",
"langbot_conversation_id": cov_id,
"langbot_msg_create_time": create_time,
},
user=f"{query.session.launcher_type.value}_{query.session.launcher_id}",
files=files,

View File

@@ -1,4 +1,4 @@
semantic_version = "v3.4.9.4"
semantic_version = "v3.4.10.2"
debug_mode = False

View File

@@ -74,19 +74,29 @@
"name": "claude-3-opus-latest",
"requester": "anthropic-messages",
"token_mgr": "anthropic",
"vision_supported": true
"vision_supported": true,
"tool_call_supported": true
},
{
"name": "claude-3-5-sonnet-latest",
"requester": "anthropic-messages",
"token_mgr": "anthropic",
"vision_supported": true
"vision_supported": true,
"tool_call_supported": true
},
{
"name": "claude-3-5-haiku-latest",
"requester": "anthropic-messages",
"token_mgr": "anthropic",
"vision_supported": true
"vision_supported": true,
"tool_call_supported": true
},
{
"name": "claude-3-7-sonnet-latest",
"requester": "anthropic-messages",
"token_mgr": "anthropic",
"vision_supported": true,
"tool_call_supported": true
},
{
"name": "moonshot-v1-8k",
@@ -156,61 +166,72 @@
{
"name": "glm-4-plus",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai"
"token_mgr": "zhipuai",
"tool_call_supported": true
},
{
"name": "glm-4-0520",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai"
"token_mgr": "zhipuai",
"tool_call_supported": true
},
{
"name": "glm-4-air",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai"
"token_mgr": "zhipuai",
"tool_call_supported": true
},
{
"name": "glm-4-airx",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai"
"token_mgr": "zhipuai",
"tool_call_supported": true
},
{
"name": "glm-4-long",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai"
"token_mgr": "zhipuai",
"tool_call_supported": true
},
{
"name": "glm-4-flashx",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai"
"token_mgr": "zhipuai",
"tool_call_supported": true
},
{
"name": "glm-4-flash",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai"
"token_mgr": "zhipuai",
"tool_call_supported": true
},
{
"name": "glm-4v-plus",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai",
"vision_supported": true
"vision_supported": true,
"tool_call_supported": true
},
{
"name": "glm-4v",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai",
"vision_supported": true
"vision_supported": true,
"tool_call_supported": true
},
{
"name": "glm-4v-flash",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai",
"vision_supported": true
"vision_supported": true,
"tool_call_supported": true
},
{
"name": "glm-zero-preview",
"requester": "zhipuai-chat-completions",
"token_mgr": "zhipuai",
"vision_supported": true
"vision_supported": true,
"tool_call_supported": true
}
]
}