Compare commits

...

22 Commits

Author SHA1 Message Date
Junyan Qin
bd0438df76 chore: release v3.4.10.4 2025-03-16 17:05:10 +08:00
Junyan Qin (Chin)
9ca1fc59ef Merge pull request #1198 from fdc310/master
当机器人群名称改名后群聊单独at机器人时候替换@信息为空
2025-03-16 16:24:52 +08:00
Dong_master
84a80a5ec8 当是单独群聊at机器人时候替换@信息为空 2025-03-15 22:21:21 +08:00
Dong_master
4b2e248646 当是单独群聊at机器人时候替换@信息为空 2025-03-15 22:13:15 +08:00
Dong_master
b90e45590a 当微信群有at消息时删除第一个at已达到能激活管理员模式 2025-03-15 01:15:56 +08:00
Junyan Qin (Chin)
ff93d563a8 Merge pull request #1194 from fdc310/master
'增加了主动发送at信息'
2025-03-14 22:41:41 +08:00
Junyan Qin (Chin)
53228498ed Merge pull request #1191 from wangcham/master
fix: eliminate critical message queue blockage in wxoa
2025-03-14 22:29:03 +08:00
Dong_master
8ece82e43a '增加了主动发送at信息' 2025-03-14 02:33:52 +08:00
wangcham
8b4684675e fix: eliminate critical message queue blockage in wxoa 2025-03-13 10:44:09 -04:00
Junyan Qin (Chin)
8cca12fff2 Merge pull request #1190 from wangcham/master
feat: add support for longer response in wxoa
2025-03-13 17:30:19 +08:00
Junyan Qin
a74111612e chore: config for wxoa mode 2025-03-13 17:29:05 +08:00
wangcham
c7799a65c4 fix: update config in wxoa 2025-03-13 05:15:03 -04:00
wangcham
aabb01c50f feat: add support for longer response in wxoa 2025-03-12 23:39:43 -04:00
Junyan Qin
95e2ada965 fix(schema): adapt str type session id 2025-03-12 21:32:19 +08:00
Junyan Qin (Chin)
3fe7d53c76 Merge pull request #1188 from RockChinQ/feat/query-variables
feat: add query variables
2025-03-12 21:21:50 +08:00
Junyan Qin
e8634bb1ab feat(variables): add api for plugin 2025-03-12 20:57:42 +08:00
Junyan Qin
dbe46b5770 feat: add query variables 2025-03-12 19:13:04 +08:00
Junyan Qin (Chin)
6d9fba30b1 Merge pull request #1187 from wangcham/master
feat: add support for sending active messages in wecom
2025-03-12 16:49:47 +08:00
wangcham
6a866bf871 feat: add support for sending active messages in wecom 2025-03-12 04:03:02 -04:00
Junyan Qin (Chin)
3c961e4652 Merge pull request #1184 from wangcham/master
feat: add support for sending active messages in dingtalk
2025-03-11 16:42:29 +08:00
wangcham
7abd999420 feat: add support for sending active messages in dingtalk 2025-03-11 04:27:17 -04:00
Junyan Qin
fca8fbb135 perf: no longer add slash as ignored prefix as default 2025-03-11 12:06:37 +08:00
21 changed files with 385 additions and 48 deletions

View File

@@ -23,6 +23,7 @@ class EchoTextHandler(dingtalk_stream.ChatbotHandler):
"""异步等待消息的到来""" """异步等待消息的到来"""
while self.incoming_message is None: while self.incoming_message is None:
await asyncio.sleep(0.1) # 异步等待,避免阻塞 await asyncio.sleep(0.1) # 异步等待,避免阻塞
return self.incoming_message return self.incoming_message
async def get_dingtalk_client(client_id, client_secret): async def get_dingtalk_client(client_id, client_secret):

View File

@@ -157,6 +157,7 @@ class DingTalkClient:
async def get_message(self,incoming_message:dingtalk_stream.chatbot.ChatbotMessage): async def get_message(self,incoming_message:dingtalk_stream.chatbot.ChatbotMessage):
try: try:
# print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False)) # print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False))
message_data = { message_data = {
"IncomingMessage":incoming_message, "IncomingMessage":incoming_message,
@@ -197,6 +198,53 @@ class DingTalkClient:
traceback.print_exc() traceback.print_exc()
return message_data return message_data
async def send_proactive_message_to_one(self,target_id:str,content:str):
if not await self.check_access_token():
await self.get_access_token()
url = 'https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend'
headers ={
"x-acs-dingtalk-access-token":self.access_token,
"Content-Type":"application/json",
}
data ={
"robotCode":self.robot_code,
"userIds":[target_id],
"msgKey": "sampleText",
"msgParam": json.dumps({"content":content}),
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(url,headers=headers,json=data)
except Exception:
traceback.print_exc()
async def send_proactive_message_to_group(self,target_id:str,content:str):
if not await self.check_access_token():
await self.get_access_token()
url = 'https://api.dingtalk.com/v1.0/robot/groupMessages/send'
headers ={
"x-acs-dingtalk-access-token":self.access_token,
"Content-Type":"application/json",
}
data ={
"robotCode":self.robot_code,
"openConversationId":target_id,
"msgKey": "sampleText",
"msgParam": json.dumps({"content":content}),
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(url,headers=headers,json=data)
except Exception:
traceback.print_exc()
async def start(self): async def start(self):
"""启动 WebSocket 连接,监听消息""" """启动 WebSocket 连接,监听消息"""

View File

@@ -1,4 +1,5 @@
# 微信公众号的加解密算法与企业微信一样,所以直接使用企业微信的加解密算法文件 # 微信公众号的加解密算法与企业微信一样,所以直接使用企业微信的加解密算法文件
from collections import deque
import time import time
import traceback import traceback
from ..wecom_api.WXBizMsgCrypt3 import WXBizMsgCrypt from ..wecom_api.WXBizMsgCrypt3 import WXBizMsgCrypt
@@ -12,6 +13,7 @@ import httpx
import asyncio import asyncio
import time import time
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pkg.platform.sources import officialaccount as oa
@@ -25,6 +27,8 @@ xml_template = """
</xml> </xml>
""" """
user_msg_queue = {}
class OAClient(): class OAClient():
def __init__(self,token:str,EncodingAESKey:str,AppID:str,Appsecret:str): def __init__(self,token:str,EncodingAESKey:str,AppID:str,Appsecret:str):
@@ -169,6 +173,152 @@ class OAClient():
await handler(event) await handler(event)
class OAClientForLongerResponse():
def __init__(self,token:str,EncodingAESKey:str,AppID:str,Appsecret:str):
self.token = token
self.aes = EncodingAESKey
self.appid = AppID
self.appsecret = Appsecret
self.base_url = 'https://api.weixin.qq.com'
self.access_token = ''
self.app = Quart(__name__)
self.app.add_url_rule('/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST'])
self._message_handlers = {
"example":[],
}
self.access_token_expiry_time = None
async def handle_callback_request(self):
try:
start_time = time.time()
signature = request.args.get("signature", "")
timestamp = request.args.get("timestamp", "")
nonce = request.args.get("nonce", "")
echostr = request.args.get("echostr", "")
msg_signature = request.args.get("msg_signature", "")
if msg_signature is None:
raise Exception("msg_signature不在请求体中")
if request.method == 'GET':
check_str = "".join(sorted([self.token, timestamp, nonce]))
check_signature = hashlib.sha1(check_str.encode("utf-8")).hexdigest()
return echostr if check_signature == signature else "拒绝请求"
elif request.method == "POST":
encryt_msg = await request.data
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid)
ret, xml_msg = wxcpt.DecryptMsg(encryt_msg, msg_signature, timestamp, nonce)
xml_msg = xml_msg.decode('utf-8')
if ret != 0:
raise Exception("消息解密失败")
# 解析 XML
root = ET.fromstring(xml_msg)
from_user = root.find("FromUserName").text
to_user = root.find("ToUserName").text
from pkg.platform.sources import officialaccount as oa
if oa.msg_queue.get(from_user) and oa.msg_queue[from_user][0]["content"]:
queue_top = oa.msg_queue[from_user].pop(0)
queue_content = queue_top["content"]
# 弹出用户消息
if user_msg_queue.get(from_user) and user_msg_queue[from_user]:
user_msg_queue[from_user].pop(0)
response_xml = xml_template.format(
to_user=from_user,
from_user=to_user,
create_time=int(time.time()),
content=queue_content
)
return response_xml
else:
response_xml = xml_template.format(
to_user=from_user,
from_user=to_user,
create_time=int(time.time()),
content="AI正在思考中请发送任意内容获取回答。"
)
if user_msg_queue.get(from_user) and user_msg_queue[from_user][0]["content"]:
return response_xml
else:
message_data = await self.get_message(xml_msg)
if message_data:
event = OAEvent.from_payload(message_data)
if event:
user_msg_queue.setdefault(from_user,[]).append(
{
"content":event.message,
}
)
await self._handle_message(event)
return response_xml
except Exception as e:
traceback.print_exc()
async def get_message(self, xml_msg: str):
root = ET.fromstring(xml_msg)
message_data = {
"ToUserName": root.find("ToUserName").text,
"FromUserName": root.find("FromUserName").text,
"CreateTime": int(root.find("CreateTime").text),
"MsgType": root.find("MsgType").text,
"Content": root.find("Content").text if root.find("Content") is not None else None,
"MsgId": int(root.find("MsgId").text) if root.find("MsgId") is not None else None,
}
return message_data
async def run_task(self, host: str, port: int, *args, **kwargs):
"""
启动 Quart 应用。
"""
await self.app.run_task(host=host, port=port, *args, **kwargs)
def on_message(self, msg_type: str):
"""
注册消息类型处理器。
"""
def decorator(func: Callable[[OAEvent], None]):
if msg_type not in self._message_handlers:
self._message_handlers[msg_type] = []
self._message_handlers[msg_type].append(func)
return func
return decorator
async def _handle_message(self, event: OAEvent):
"""
处理消息事件。
"""
msg_type = event.type
if msg_type in self._message_handlers:
for handler in self._message_handlers[msg_type]:
await handler(event)

View File

@@ -66,7 +66,7 @@ class WecomClient():
else: else:
raise Exception("未获取用户") raise Exception("未获取用户")
async def send_to_all(self,content:str): async def send_to_all(self,content:str,agent_id:int):
if not self.check_access_token_for_contacts(): if not self.check_access_token_for_contacts():
self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts) self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts)
@@ -77,7 +77,7 @@ class WecomClient():
params = { params = {
"touser" : user_ids_string, "touser" : user_ids_string,
"msgtype" : "text", "msgtype" : "text",
"agentid" : 1000002, "agentid" : agent_id,
"text" : { "text" : {
"content" : content, "content" : content,
}, },

View File

@@ -72,6 +72,9 @@ class Query(pydantic.BaseModel):
user_message: typing.Optional[llm_entities.Message] = None user_message: typing.Optional[llm_entities.Message] = None
"""此次请求的用户消息对象,由前置处理器阶段设置""" """此次请求的用户消息对象,由前置处理器阶段设置"""
variables: typing.Optional[dict[str, typing.Any]] = None
"""变量由前置处理器阶段设置。在prompt中嵌入或由 Runner 传递到 LLMOps 平台。"""
use_model: typing.Optional[entities.LLMModelInfo] = None use_model: typing.Optional[entities.LLMModelInfo] = None
"""使用的模型,由前置处理器阶段设置""" """使用的模型,由前置处理器阶段设置"""
@@ -86,10 +89,31 @@ class Query(pydantic.BaseModel):
# ======= 内部保留 ======= # ======= 内部保留 =======
current_stage: "pkg.pipeline.stagemgr.StageInstContainer" = None current_stage: "pkg.pipeline.stagemgr.StageInstContainer" = None
"""当前所处阶段"""
class Config: class Config:
arbitrary_types_allowed = True arbitrary_types_allowed = True
# ========== 插件可调用的 API请求 API ==========
def set_variable(self, key: str, value: typing.Any):
"""设置变量"""
if self.variables is None:
self.variables = {}
self.variables[key] = value
def get_variable(self, key: str) -> typing.Any:
"""获取变量"""
if self.variables is None:
return None
return self.variables.get(key)
def get_variables(self) -> dict[str, typing.Any]:
"""获取所有变量"""
if self.variables is None:
return {}
return self.variables
class Conversation(pydantic.BaseModel): class Conversation(pydantic.BaseModel):
"""对话,包含于 Session 中,一个 Session 可以有多个历史 Conversation但只有一个当前使用的 Conversation""" """对话,包含于 Session 中,一个 Session 可以有多个历史 Conversation但只有一个当前使用的 Conversation"""

View File

@@ -0,0 +1,26 @@
from __future__ import annotations
from .. import migration
@migration.migration_class("wxoa-mode", 35)
class WxoaModeMigration(migration.Migration):
"""迁移"""
async def need_migrate(self) -> bool:
"""判断当前环境是否需要运行此迁移"""
for adapter in self.ap.platform_cfg.data['platform-adapters']:
if adapter['adapter'] == 'officialaccount':
if 'Mode' not in adapter:
return True
return False
async def run(self):
"""执行迁移"""
for adapter in self.ap.platform_cfg.data['platform-adapters']:
if adapter['adapter'] == 'officialaccount':
if 'Mode' not in adapter:
adapter['Mode'] = 'drop'
await self.ap.platform_cfg.dump_config()

View File

@@ -11,7 +11,7 @@ from ..migrations import m015_gitee_ai_config, m016_dify_service_api, m017_dify_
from ..migrations import m020_wecom_config, m021_lark_config, m022_lmstudio_config, m023_siliconflow_config, m024_discord_config, m025_gewechat_config from ..migrations import m020_wecom_config, m021_lark_config, m022_lmstudio_config, m023_siliconflow_config, m024_discord_config, m025_gewechat_config
from ..migrations import m026_qqofficial_config, m027_wx_official_account_config, m028_aliyun_requester_config from ..migrations import m026_qqofficial_config, m027_wx_official_account_config, m028_aliyun_requester_config
from ..migrations import m029_dashscope_app_api_config, m030_lark_config_cmpl, m031_dingtalk_config, m032_volcark_config from ..migrations import m029_dashscope_app_api_config, m030_lark_config_cmpl, m031_dingtalk_config, m032_volcark_config
from ..migrations import m033_dify_thinking_config, m034_gewechat_file_url_config from ..migrations import m033_dify_thinking_config, m034_gewechat_file_url_config, m035_wxoa_mode
@stage.stage_class("MigrationStage") @stage.stage_class("MigrationStage")
class MigrationStage(stage.BootingStage): class MigrationStage(stage.BootingStage):

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import datetime
from .. import stage, entities, stagemgr from .. import stage, entities, stagemgr
from ...core import entities as core_entities from ...core import entities as core_entities
@@ -34,7 +35,7 @@ class PreProcessor(stage.PipelineStage):
conversation = await self.ap.sess_mgr.get_conversation(session) conversation = await self.ap.sess_mgr.get_conversation(session)
# 从会话取出消息和情景预设到query # 设置query
query.session = session query.session = session
query.prompt = conversation.prompt.copy() query.prompt = conversation.prompt.copy()
query.messages = conversation.messages.copy() query.messages = conversation.messages.copy()
@@ -43,6 +44,11 @@ class PreProcessor(stage.PipelineStage):
query.use_funcs = conversation.use_funcs if query.use_model.tool_call_supported else None query.use_funcs = conversation.use_funcs if query.use_model.tool_call_supported else None
query.variables = {
"session_id": f"{query.session.launcher_type.value}_{query.session.launcher_id}",
"conversation_id": conversation.uuid,
"msg_create_time": int(query.message_event.time) if query.message_event.time else int(datetime.datetime.now().timestamp()),
}
# 检查vision是否启用没启用就删除所有图片 # 检查vision是否启用没启用就删除所有图片
if not self.ap.provider_cfg.data['enable-vision'] or (self.ap.provider_cfg.data['runner'] == 'local-agent' and not query.use_model.vision_supported): if not self.ap.provider_cfg.data['enable-vision'] or (self.ap.provider_cfg.data['runner'] == 'local-agent' and not query.use_model.vision_supported):

View File

@@ -152,7 +152,11 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
async def send_message( async def send_message(
self, target_type: str, target_id: str, message: platform_message.MessageChain self, target_type: str, target_id: str, message: platform_message.MessageChain
): ):
pass content = await DingTalkMessageConverter.yiri2target(message)
if target_type == 'person':
await self.bot.send_proactive_message_to_one(target_id,content)
if target_type == 'group':
await self.bot.send_proactive_message_to_group(target_id,content)
def register_listener( def register_listener(
self, self,

View File

@@ -62,20 +62,29 @@ class GewechatMessageConverter(adapter.MessageConverter):
bot_account_id: str bot_account_id: str
) -> platform_message.MessageChain: ) -> platform_message.MessageChain:
if message["Data"]["MsgType"] == 1: if message["Data"]["MsgType"] == 1:
# 检查消息开头,如果有 wxid_sbitaz0mt65n22:\n 则删掉 # 检查消息开头,如果有 wxid_sbitaz0mt65n22:\n 则删掉
regex = re.compile(r"^wxid_.*:") regex = re.compile(r"^wxid_.*:")
# print(message)
line_split = message["Data"]["Content"]["string"].split("\n") line_split = message["Data"]["Content"]["string"].split("\n")
if len(line_split) > 0 and regex.match(line_split[0]): if len(line_split) > 0 and regex.match(line_split[0]):
message["Data"]["Content"]["string"] = "\n".join(line_split[1:]) message["Data"]["Content"]["string"] = "\n".join(line_split[1:])
at_string = f'@{bot_account_id}' # 正则表达式模式,匹配'@'后跟任意数量的非空白字符
pattern = r'@\S+'
at_string = f"@{bot_account_id}"
content_list = [] content_list = []
if at_string in message["Data"]["Content"]["string"]: if at_string in message["Data"]["Content"]["string"]:
content_list.append(platform_message.At(target=bot_account_id)) content_list.append(platform_message.At(target=bot_account_id))
content_list.append(platform_message.Plain(message["Data"]["Content"]["string"].replace(at_string, "", 1))) content_list.append(platform_message.Plain(message["Data"]["Content"]["string"].replace(at_string, '', 1)))
# 更优雅的替换改名后@机器人仅仅限于单独AT的情况
elif '在群聊中@了你' in message["Data"]["PushContent"]:
content_list.append(platform_message.At(target=bot_account_id))
content_list.append(platform_message.Plain(re.sub(pattern, '', message["Data"]["Content"]["string"])))
else: else:
content_list = [platform_message.Plain(message["Data"]["Content"]["string"])] content_list = [platform_message.Plain(message["Data"]["Content"]["string"])]
@@ -257,11 +266,23 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
): ):
geweap_msg = await self.message_converter.yiri2target(message) geweap_msg = await self.message_converter.yiri2target(message)
# 此处加上群消息at处理 # 此处加上群消息at处理
# ats = [item["target"] for item in geweap_msg if item["type"] == "at"] ats = [item["target"] for item in geweap_msg if item["type"] == "at"]
for msg in geweap_msg: for msg in geweap_msg:
# at主动发送消息
if msg['type'] == 'text': if msg['type'] == 'text':
self.bot.post_text(app_id=self.config['app_id'], to_wxid=target_id, content=msg['content']) if ats:
member_info = self.bot.get_chatroom_member_detail(
self.config["app_id"],
target_id,
ats[::-1]
)["data"]
for member in member_info:
msg['content'] = f'@{member["nickName"]} {msg["content"]}'
self.bot.post_text(app_id=self.config['app_id'], to_wxid=target_id, content=msg['content'],
ats=",".join(ats))
elif msg['type'] == 'image': elif msg['type'] == 'image':
@@ -296,7 +317,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
app_id=self.config["app_id"], app_id=self.config["app_id"],
to_wxid=message_source.source_platform_object["Data"]["FromUserName"]["string"], to_wxid=message_source.source_platform_object["Data"]["FromUserName"]["string"],
content=msg["content"], content=msg["content"],
ats=','.join(ats) ats=",".join(ats)
) )
async def is_muted(self, group_id: int) -> bool: async def is_muted(self, group_id: int) -> bool:

View File

@@ -2,21 +2,19 @@ from __future__ import annotations
import typing import typing
import asyncio import asyncio
import traceback import traceback
import time
import datetime import datetime
from pkg.core import app from pkg.core import app
from pkg.platform.adapter import MessagePlatformAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
from collections import deque
import aiocqhttp
import aiohttp
from libs.official_account_api.oaevent import OAEvent from libs.official_account_api.oaevent import OAEvent
from pkg.platform.adapter import MessagePlatformAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
from libs.official_account_api.api import OAClient from libs.official_account_api.api import OAClient
from libs.official_account_api.api import OAClientForLongerResponse
from pkg.core import app from pkg.core import app
from .. import adapter from .. import adapter
from ...pipeline.longtext.strategies import forward
from ...core import app from ...core import app
from ..types import message as platform_message from ..types import message as platform_message
from ..types import events as platform_events from ..types import events as platform_events
@@ -26,6 +24,7 @@ from ...command.errors import ParamNotEnoughError
# 生成的ai回答 # 生成的ai回答
generated_content = {} generated_content = {}
msg_queue = {}
class OAMessageConverter(adapter.MessageConverter): class OAMessageConverter(adapter.MessageConverter):
@staticmethod @staticmethod
@@ -88,17 +87,30 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
"EncodingAESKey", "EncodingAESKey",
"AppSecret", "AppSecret",
"AppID", "AppID",
"Mode",
] ]
missing_keys = [key for key in required_keys if key not in config] missing_keys = [key for key in required_keys if key not in config]
if missing_keys: if missing_keys:
raise ParamNotEnoughError("企业微信缺少相关配置项,请查看文档或联系管理员") raise ParamNotEnoughError("微信公众号缺少相关配置项,请查看文档或联系管理员")
self.bot = OAClient(
token=config['token'], if self.config['Mode'] == "drop":
EncodingAESKey=config['EncodingAESKey'], self.bot = OAClient(
Appsecret=config['AppSecret'], token=config['token'],
AppID=config['AppID'], EncodingAESKey=config['EncodingAESKey'],
) Appsecret=config['AppSecret'],
AppID=config['AppID'],
)
elif self.config['Mode'] == "passive":
self.bot = OAClientForLongerResponse(
token=config['token'],
EncodingAESKey=config['EncodingAESKey'],
Appsecret=config['AppSecret'],
AppID=config['AppID'],
)
else:
raise KeyError("请设置微信公众号通信模式")
async def reply_message(self, message_source: platform_events.FriendMessage, message: platform_message.MessageChain, quote_origin: bool = False): async def reply_message(self, message_source: platform_events.FriendMessage, message: platform_message.MessageChain, quote_origin: bool = False):
global generated_content global generated_content
@@ -109,6 +121,20 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
generated_content[message_source.message_chain.message_id] = content generated_content[message_source.message_chain.message_id] = content
from_user = message_source.sender.id
if from_user not in msg_queue:
msg_queue[from_user] = []
msg_queue[from_user].append(
{
"msg_id":message_source.message_chain.message_id,
"content":content,
}
)
async def send_message( async def send_message(
self, target_type: str, target_id: str, message: platform_message.MessageChain self, target_type: str, target_id: str, message: platform_message.MessageChain
): ):

View File

@@ -2,15 +2,14 @@ from __future__ import annotations
import typing import typing
import asyncio import asyncio
import traceback import traceback
import time
import datetime import datetime
import aiocqhttp
import aiohttp
from pkg.platform.adapter import MessagePlatformAdapter from pkg.platform.adapter import MessagePlatformAdapter
from pkg.platform.types import events as platform_events, message as platform_message from pkg.platform.types import events as platform_events, message as platform_message
from pkg.core import app from pkg.core import app
from .. import adapter from .. import adapter
from ...pipeline.longtext.strategies import forward
from ...core import app from ...core import app
from ..types import message as platform_message from ..types import message as platform_message
from ..types import events as platform_events from ..types import events as platform_events

View File

@@ -197,7 +197,19 @@ class WecomAdapter(adapter.MessagePlatformAdapter):
async def send_message( async def send_message(
self, target_type: str, target_id: str, message: platform_message.MessageChain self, target_type: str, target_id: str, message: platform_message.MessageChain
): ):
pass """企业微信目前只有发送给个人的方法,
构造target_id的方式为前半部分为账户id后半部分为agent_id,中间使用“|”符号隔开。
"""
content_list = await WecomMessageConverter.yiri2target(message, self.bot)
parts = target_id.split("|")
user_id = parts[0]
agent_id = int(parts[1])
if target_type == 'person':
for content in content_list:
if content["type"] == "text":
await self.bot.send_private_msg(user_id,agent_id,content["content"])
if content["type"] == "image":
await self.bot.send_image(user_id,agent_id,content["media"])
def register_listener( def register_listener(
self, self,

View File

@@ -167,6 +167,10 @@ class DashScopeAPIRunner(runner.RequestRunner):
image_ids = [] # 用户输入的图片ID列表 (暂不支持) image_ids = [] # 用户输入的图片ID列表 (暂不支持)
plain_text, image_ids = await self._preprocess_user_message(query) plain_text, image_ids = await self._preprocess_user_message(query)
biz_params = {}
biz_params.update(self.biz_params)
biz_params.update(query.variables)
#发送对话请求 #发送对话请求
response = dashscope.Application.call( response = dashscope.Application.call(
@@ -176,7 +180,7 @@ class DashScopeAPIRunner(runner.RequestRunner):
stream=True, # 流式输出 stream=True, # 流式输出
incremental_output=True, # 增量输出,使用流式输出需要开启增量输出 incremental_output=True, # 增量输出,使用流式输出需要开启增量输出
session_id=query.session.using_conversation.uuid, # 会话ID用于多轮对话 session_id=query.session.using_conversation.uuid, # 会话ID用于多轮对话
biz_params=self.biz_params # 工作流应用的自定义输入参数传递 biz_params=biz_params, # 工作流应用的自定义输入参数传递
# rag_options={ # 主要用于文件交互,暂不支持 # rag_options={ # 主要用于文件交互,暂不支持
# "session_file_ids": ["FILE_ID1"], # FILE_ID1 替换为实际的临时文件ID,逗号隔开多个 # "session_file_ids": ["FILE_ID1"], # FILE_ID1 替换为实际的临时文件ID,逗号隔开多个
# } # }

View File

@@ -111,8 +111,12 @@ class DifyServiceAPIRunner(runner.RequestRunner):
basic_mode_pending_chunk = '' basic_mode_pending_chunk = ''
inputs = {}
inputs.update(query.variables)
async for chunk in self.dify_client.chat_messages( async for chunk in self.dify_client.chat_messages(
inputs={}, inputs=inputs,
query=plain_text, query=plain_text,
user=f"{query.session.launcher_type.value}_{query.session.launcher_id}", user=f"{query.session.launcher_type.value}_{query.session.launcher_id}",
conversation_id=cov_id, conversation_id=cov_id,
@@ -162,8 +166,12 @@ class DifyServiceAPIRunner(runner.RequestRunner):
ignored_events = ["agent_message"] ignored_events = ["agent_message"]
inputs = {}
inputs.update(query.variables)
async for chunk in self.dify_client.chat_messages( async for chunk in self.dify_client.chat_messages(
inputs={}, inputs=inputs,
query=plain_text, query=plain_text,
user=f"{query.session.launcher_type.value}_{query.session.launcher_id}", user=f"{query.session.launcher_type.value}_{query.session.launcher_id}",
response_mode="streaming", response_mode="streaming",
@@ -227,14 +235,11 @@ class DifyServiceAPIRunner(runner.RequestRunner):
if not query.session.using_conversation.uuid: if not query.session.using_conversation.uuid:
query.session.using_conversation.uuid = str(uuid.uuid4()) query.session.using_conversation.uuid = str(uuid.uuid4())
cov_id = query.session.using_conversation.uuid query.variables["conversation_id"] = query.session.using_conversation.uuid
plain_text, image_ids = await self._preprocess_user_message(query) 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 = [ files = [
{ {
"type": "image", "type": "image",
@@ -246,13 +251,17 @@ class DifyServiceAPIRunner(runner.RequestRunner):
ignored_events = ["text_chunk", "workflow_started"] ignored_events = ["text_chunk", "workflow_started"]
inputs = { # these variables are legacy variables, we need to keep them for compatibility
"langbot_user_message_text": plain_text,
"langbot_session_id": query.variables["session_id"],
"langbot_conversation_id": query.variables["conversation_id"],
"langbot_msg_create_time": query.variables["msg_create_time"],
}
inputs.update(query.variables)
async for chunk in self.dify_client.workflow_run( async for chunk in self.dify_client.workflow_run(
inputs={ inputs=inputs,
"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}", user=f"{query.session.launcher_type.value}_{query.session.launcher_id}",
files=files, files=files,
timeout=self.ap.provider_cfg.data["dify-service-api"]["workflow"]["timeout"], timeout=self.ap.provider_cfg.data["dify-service-api"]["workflow"]["timeout"],

View File

@@ -1,4 +1,4 @@
semantic_version = "v3.4.10.3" semantic_version = "v3.4.10.4"
debug_mode = False debug_mode = False

View File

@@ -16,7 +16,7 @@
}, },
"income-msg-check": true, "income-msg-check": true,
"ignore-rules": { "ignore-rules": {
"prefix": ["/"], "prefix": [],
"regexp": [] "regexp": []
}, },
"check-sensitive-words": true, "check-sensitive-words": true,

View File

@@ -77,6 +77,7 @@
"EncodingAESKey":"", "EncodingAESKey":"",
"AppID":"", "AppID":"",
"AppSecret":"", "AppSecret":"",
"Mode":"drop",
"host": "0.0.0.0", "host": "0.0.0.0",
"port": 2287 "port": 2287
}, },

View File

@@ -23,7 +23,7 @@
"items": { "items": {
"type": "string", "type": "string",
"format": "regex", "format": "regex",
"pattern": "^(person|group)_(\\d)*$" "pattern": "^(person|group)_.*$"
}, },
"default": [] "default": []
}, },
@@ -34,7 +34,7 @@
"items": { "items": {
"type": "string", "type": "string",
"format": "regex", "format": "regex",
"pattern": "^(person|group)_(\\d)*$" "pattern": "^(person|group)_.*$"
}, },
"default": [] "default": []
} }
@@ -99,7 +99,7 @@
} }
}, },
"patternProperties": { "patternProperties": {
"^\\d+$": { "^.*$": {
"type": "object", "type": "object",
"properties": { "properties": {
"at": { "at": {

View File

@@ -385,6 +385,12 @@
"default": "", "default": "",
"description": "微信公众号的AppSecret" "description": "微信公众号的AppSecret"
}, },
"Mode": {
"type": "string",
"default": "drop",
"description": "对于超过15s的响应的处理模式",
"enum": ["drop", "passive"]
},
"host": { "host": {
"type": "string", "type": "string",
"default": "0.0.0.0", "default": "0.0.0.0",

View File

@@ -9,7 +9,7 @@
"items": { "items": {
"type": "string", "type": "string",
"format": "regex", "format": "regex",
"pattern": "^(person|group)_(\\d+)$" "pattern": "^(person|group)_.*$"
}, },
"default": [] "default": []
}, },
@@ -52,7 +52,7 @@
} }
}, },
"patternProperties": { "patternProperties": {
"^(person|group)_(\\d+)$": { "^(person|group)_.*$": {
"type": "integer" "type": "integer"
} }
} }