Skip to content

Commit c0b4510

Browse files
authored
Merge pull request #381 from LlmKira/dev
Hook
2 parents c1b621d + cd7a09d commit c0b4510

33 files changed

+674
-213
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,4 @@ config_dir/*.secret/
148148
/.pdm-python
149149
/.montydb/
150150
/.snapshot/
151+
/.tutorial.db

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,18 @@ The model adheres to the Openai Schema, other models are not supported. Please a
4848
|-----------------------------------|
4949
| ![sticker](./docs/chain_chat.gif) |
5050

51-
## 🍔 Roadmap
51+
## 🔨 Roadmap
5252

5353
- [x] Removal of legacy code
5454
- [x] Deletion of metric system
5555
- [x] Deletion of model selection system, unified to OpenAI Schema
5656
- [x] Implementation of a more robust plugin system
5757
- [x] Project structure simplification
5858
- [x] Elimination of the Provider system
59+
- [x] Hook support.
60+
- [x] Access to TTS.
61+
- [ ] Add LLM reference support to the plugin environment. (extract && search in text)
62+
- [ ] Add standalone support for Openai's new Schema. (vision)
5963

6064
## 📦 Features
6165

@@ -71,9 +75,9 @@ The model adheres to the Openai Schema, other models are not supported. Please a
7175

7276
### 🍔 Login Modes
7377

74-
- `Login via url`: Use `/login token#https://provider.com` to Login. The program posts the token to the interface to
78+
- `Login via url`: Use `/login token$https://provider.com` to Login. The program posts the token to the interface to
7579
retrieve configuration information
76-
- `Login`: Use `/login https://api.com/v1#key#model` to login
80+
- `Login`: Use `/login https://api.com/v1$key$model` to login
7781

7882
### 🧀 Plugin Previews
7983

@@ -168,6 +172,19 @@ env - Environment variables of the function
168172
Refer to the example plugins in the `plugins` directory and
169173
the [🧀 Plugin Development Document](https://llmkira.github.io/Docs/dev/basic) for plugin development documentation.
170174

175+
### Hooks
176+
177+
Hooks control the EventMessage in sender and receiver. For example, we have `voice_hook` in built-in hooks.
178+
179+
you can enable it by setting `VOICE_REPLY_ME=true` in `.env`.
180+
181+
```shell
182+
/env VOICE_REPLY_ME=true
183+
/env REECHO_VOICE_KEY=<key in dev.reecho.ai>
184+
```
185+
186+
check the source code in `llmkira/extra/voice_hook.py`, learn to write your own hooks.
187+
171188
## 🧀 Sponsor
172189

173190
[![sponsor](./.github/sponsor_ohmygpt.png)](https://www.ohmygpt.com)

app/middleware/llm_task.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from app.components.credential import Credential
1313
from app.components.user_manager import record_cost
1414
from llmkira.kv_manager.instruction import InstructionManager
15+
from llmkira.kv_manager.time import TimeFeelManager
1516
from llmkira.kv_manager.tool_call import GLOBAL_TOOLCALL_CACHE_HANDLER
1617
from llmkira.memory import global_message_runtime
1718
from llmkira.openai.cell import (
@@ -125,10 +126,9 @@ async def remember(self, *, message: Optional[Message] = None):
125126
if message:
126127
await self.message_history.append(messages=[message])
127128

128-
async def build_message(self, remember=True):
129+
async def build_history_messages(self):
129130
"""
130131
从任务会话和历史消息中构建消息
131-
:param remember: 是否写入历史消息
132132
:return: None
133133
"""
134134
system_prompt = await InstructionManager(
@@ -147,6 +147,15 @@ async def build_message(self, remember=True):
147147
continue
148148
else:
149149
message_run.append(msg)
150+
return message_run
151+
152+
async def build_task_messages(self, remember=True):
153+
"""
154+
从任务会话和历史消息中构建消息
155+
:param remember: 是否写入历史消息
156+
:return: None
157+
"""
158+
message_run = []
150159
# 处理 人类 发送的消息
151160
task_message = self.task.message
152161
task_message: List[EventMessage]
@@ -171,7 +180,14 @@ async def request_openai(
171180
:param disable_tool: 禁用函数
172181
:param credential: 凭证
173182
:return: OpenaiResult 返回结果
174-
:raise RuntimeError: 无法处理消息
183+
:raise RuntimeError: # Feel time leave
184+
time_feel = await TimeFeelManager(self.session_uid).get_leave()
185+
if time_feel:
186+
await self.remember(
187+
message=SystemMessage(
188+
content=f"statu:[After {time_feel} leave, user is back]"
189+
)
190+
) 无法处理消息
175191
:raise AssertionError: 无法处理消息
176192
:raise OpenaiError: Openai错误
177193
"""
@@ -182,7 +198,24 @@ async def request_openai(
182198
logger.warning(f"llm_task:Tool is not unique {self.tools}")
183199
if isinstance(self.task.task_sign.instruction, str):
184200
messages.append(SystemMessage(content=self.task.task_sign.instruction))
185-
messages.extend(await self.build_message(remember=remember))
201+
# 先读取历史消息才能操作
202+
message_head = await self.build_history_messages()
203+
messages.extend(message_head) # 历史消息
204+
# 操作先写入一个状态
205+
time_feel = await TimeFeelManager(self.session_uid).get_leave()
206+
if time_feel:
207+
messages.append(
208+
SystemMessage(content=f"statu:[After {time_feel} leave, user is back]")
209+
) # 插入消息
210+
await self.remember(
211+
message=SystemMessage(
212+
content=f"statu:[After {time_feel} leave, user is back]"
213+
)
214+
)
215+
# 同步状态到历史消息
216+
message_body = await self.build_task_messages(remember=remember)
217+
messages.extend(message_body)
218+
186219
# TODO:实现消息时序切片
187220
# 日志
188221
logger.info(

app/receiver/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ async def _main(_func):
5151
# 导入插件
5252
load_plugins("llmkira/extra/plugins")
5353
load_from_entrypoint("llmkira.extra.plugin")
54+
import llmkira.extra.voice_hook # noqa
5455

5556
loaded_message = "\n >>".join(get_entrypoint_plugins())
5657
logger.success(

app/receiver/discord/__init__.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,18 @@ async def reply(
109109
"""
110110
模型直转发,Message是Openai的类型
111111
"""
112-
for item in messages:
113-
# raw_message = await self.loop_turn_from_openai(platform_name=__receiver__, message=item, locate=receiver)
114-
event_message = EventMessage.from_openai_message(
115-
message=item, locate=receiver
116-
)
117-
await self.file_forward(receiver=receiver, file_list=event_message.files)
118-
if not event_message.text:
112+
event_message = [
113+
EventMessage.from_openai_message(message=item, locate=receiver)
114+
for item in messages
115+
]
116+
# 转析器
117+
_, event_message, receiver = await self.hook(
118+
platform_name=__receiver__, messages=event_message, locate=receiver
119+
)
120+
event_message: list
121+
for event in event_message:
122+
await self.file_forward(receiver=receiver, file_list=event.files)
123+
if not event.text:
119124
continue
120125
async with self.bot as client:
121126
client: hikari.impl.RESTClientImpl
@@ -129,7 +134,7 @@ async def reply(
129134
)
130135
await client.create_message(
131136
channel=int(receiver.thread_id),
132-
content=event_message.text,
137+
content=event.text,
133138
reply=_reply,
134139
)
135140
return logger.trace("reply message")

app/receiver/kook/__init__.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,25 @@ async def reply(
118118
"""
119119
模型直转发,Message是Openai的类型
120120
"""
121-
for item in messages:
122-
# raw_message = await self.loop_turn_from_openai(platform_name=__receiver__, message=item, locate=receiver)
123-
event_message = EventMessage.from_openai_message(
124-
message=item, locate=receiver
125-
)
126-
await self.file_forward(receiver=receiver, file_list=event_message.files)
127-
if not event_message.text:
121+
event_message = [
122+
EventMessage.from_openai_message(message=item, locate=receiver)
123+
for item in messages
124+
]
125+
# 转析器
126+
_, event_message, receiver = await self.hook(
127+
platform_name=__receiver__, messages=event_message, locate=receiver
128+
)
129+
event_message: list
130+
for event in event_message:
131+
await self.file_forward(receiver=receiver, file_list=event.files)
132+
if not event.text:
128133
continue
129134
await self.send_message(
130135
channel_id=receiver.thread_id,
131136
user_id=receiver.user_id,
132137
dm=receiver.thread_id == receiver.chat_id,
133138
message_type=MessageTypes.KMD,
134-
content=event_message.text,
139+
content=event.text,
135140
)
136141
return logger.trace("reply message")
137142

app/receiver/receiver_client.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import shortuuid
1616
from aio_pika.abc import AbstractIncomingMessage
17-
from deprecated import deprecated
1817
from loguru import logger
1918
from telebot import formatting
2019

@@ -26,7 +25,7 @@
2625
from llmkira.openai.cell import ToolCall, Message, Tool
2726
from llmkira.openai.request import OpenAIResult
2827
from llmkira.openapi.fuse import get_error_plugin
29-
from llmkira.openapi.transducer import LoopRunner
28+
from llmkira.openapi.hook import run_hook, Trigger
3029
from llmkira.sdk.tools import ToolRegister
3130
from llmkira.task import Task, TaskHeader
3231
from llmkira.task.schema import Location, EventMessage, Router
@@ -116,30 +115,23 @@ async def reorganize_tools(task: TaskHeader, error_times_limit: int = 10) -> Lis
116115

117116
class BaseSender(object, metaclass=ABCMeta):
118117
@staticmethod
119-
@deprecated(reason="use another function")
120-
async def loop_turn_from_openai(platform_name, message, locate):
118+
async def hook(platform_name, messages: List[EventMessage], locate: Location):
121119
"""
122-
# FIXME 删除这个函数
123-
将 Openai 消息传入 Receiver Loop 进行修饰
124-
此过程将忽略掉其他属性。只留下 content
120+
:param platform_name: 平台名称
121+
:param messages: 消息
122+
:param locate: 位置
123+
:return: 平台名称,消息,位置
125124
"""
126-
loop_runner = LoopRunner()
127-
trans_loop = loop_runner.get_receiver_loop(platform_name=platform_name)
128-
_raw_message = EventMessage.from_openai_message(message=message, locate=locate)
129-
await loop_runner.exec_loop(
130-
pipe=trans_loop,
131-
pipe_arg={
132-
"message": _raw_message,
133-
},
125+
arg, kwarg = await run_hook(
126+
Trigger.RECEIVER,
127+
platform=platform_name,
128+
messages=messages,
129+
locate=locate,
134130
)
135-
arg: dict = loop_runner.result_pipe_arg
136-
if not arg.get("message"):
137-
logger.error("Message Loop Lose Message")
138-
raw_message: EventMessage = arg.get("message", _raw_message)
139-
assert isinstance(
140-
raw_message, EventMessage
141-
), f"message type error {type(raw_message)}"
142-
return raw_message
131+
platform_name = kwarg.get("platform", platform_name)
132+
messages = kwarg.get("messages", messages)
133+
locate = kwarg.get("locate", locate)
134+
return platform_name, messages, locate
143135

144136
@abstractmethod
145137
async def file_forward(self, receiver: Location, file_list: list):
@@ -265,7 +257,10 @@ async def _flash(
265257
)
266258
return exc
267259
except AssertionError as exc:
268-
await self.sender.error(receiver=task.receiver, text=str(exc))
260+
logger.exception(exc)
261+
await self.sender.error(
262+
receiver=task.receiver, text=f"Assert {str(exc)}"
263+
)
269264
return exc
270265
except Exception as exc:
271266
logger.exception(exc)

app/receiver/slack/__init__.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,25 +91,30 @@ async def reply(
9191
"""
9292
模型直转发,Message是Openai的类型
9393
"""
94-
for item in messages:
95-
# raw_message = await self.loop_turn_from_openai(platform_name=__receiver__, message=item, locate=receiver)
96-
event_message = EventMessage.from_openai_message(
97-
message=item, locate=receiver
98-
)
99-
await self.file_forward(receiver=receiver, file_list=event_message.files)
100-
if not event_message.text:
94+
event_message = [
95+
EventMessage.from_openai_message(message=item, locate=receiver)
96+
for item in messages
97+
]
98+
# 转析器
99+
_, event_message, receiver = await self.hook(
100+
platform_name=__receiver__, messages=event_message, locate=receiver
101+
)
102+
event_message: list
103+
for event in event_message:
104+
await self.file_forward(receiver=receiver, file_list=event.files)
105+
if not event.text:
101106
continue
102107
_message = (
103108
ChatMessageCreator(
104109
channel=receiver.chat_id, thread_ts=receiver.message_id
105110
)
106-
.update_content(message_text=event_message.text)
107-
.get_message_payload(message_text=event_message.text)
111+
.update_content(message_text=event.text)
112+
.get_message_payload(message_text=event.text)
108113
)
109114
await self.bot.chat_postMessage(
110115
channel=receiver.chat_id,
111116
thread_ts=receiver.message_id,
112-
text=event_message.text,
117+
text=event.text,
113118
)
114119
return logger.trace("reply message")
115120

app/receiver/telegram/__init__.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,23 @@ async def reply(
118118
:param messages: OPENAI Format Message
119119
:param reply_to_message: 是否回复消息
120120
"""
121-
for message in messages:
122-
event_message = EventMessage.from_openai_message(
123-
message=message, locate=receiver
124-
)
125-
await self.file_forward(receiver=receiver, file_list=event_message.files)
126-
if not event_message.text:
121+
event_message = [
122+
EventMessage.from_openai_message(message=item, locate=receiver)
123+
for item in messages
124+
]
125+
# 转析器
126+
_, event_message, receiver = await self.hook(
127+
platform_name=__receiver__, messages=event_message, locate=receiver
128+
)
129+
event_message: list
130+
for event in event_message:
131+
await self.file_forward(receiver=receiver, file_list=event.files)
132+
if not event.text:
127133
continue
128134
try:
129135
await self.bot.send_message(
130136
chat_id=receiver.chat_id,
131-
text=convert(event_message.text),
137+
text=convert(event.text),
132138
reply_to_message_id=receiver.message_id
133139
if reply_to_message
134140
else None,
@@ -137,7 +143,7 @@ async def reply(
137143
except telebot.apihelper.ApiTelegramException as e:
138144
if "message to reply not found" in str(e):
139145
await self.bot.send_message(
140-
chat_id=receiver.chat_id, text=convert(event_message.text)
146+
chat_id=receiver.chat_id, text=convert(event.text)
141147
)
142148
else:
143149
logger.error(f"User {receiver.user_id} send message error")

0 commit comments

Comments
 (0)