ðš 08 - æ¶æ¯å€çæµçš â
æ¬ç« 沿çäžæ¡æ¶æ¯ççåœåšæè¯Žæå€çé»èŸïŒèŠçæ¥æ¶ãè·¯ç±ãåœä»€è§£æãäŒè¯ç®¡çååéæµçšã
TIP
ææ¥æ¶æ¯äžå倿¶ïŒå¯ä»¥å ç 9ïžâ£ 宿Žå€çæµçšåŸïŒååå°å¯¹åºç« èæŸç»èã
ð ç®åœ â
- æ¶æ¯å€çæ»è§
- æ¶æ¯æ¥æ¶äžè§£æ
- è§Šåæ¡ä»¶å€æ
- åœä»€è·¯ç±äžåæ°æå
- äŒè¯ç®¡ç
- é²èå€ç
- é鳿ºå¶
- å¹¶åæ§å¶äžæ¶æ¯éå
- 宿Žå€çæµçšåŸ
1ïžâ£ æ¶æ¯å€çæ»è§ â
XiaoQing çæ¶æ¯å€çç±ä»¥äžæ žå¿æš¡ååäœå®æã
| æš¡å | æä»¶ | è莣 |
|---|---|---|
| Dispatcher | core/dispatcher.py | æ¶æ¯åååšïŒåè°æŽäžªå€çæµçš |
| CommandRouter | core/router.py | åœä»€è·¯ç±ïŒå¹é è§Šåè¯å¹¶æååæ° |
| SessionManager | core/session.py | äŒè¯ç®¡çïŒæ¯æå€èœ®å¯¹è¯ |
| message | core/message.py | æ¶æ¯è§£æå·¥å ·åœæ° |
æ¶æ¯éŸè·¯çæ žå¿ååæ¯âæ¡æ¶å€æå ¥å£ïŒæä»¶å€æäžå¡âãæ¡æ¶èŽèŽ£æ OneBot äºä»¶è§èåã倿æ¯åŠè¿å ¥ Handler éŸãå¹é åœä»€ãæ¢å€äŒè¯åè°çš smalltalk providerïŒæä»¶èŽèŽ£èªå·±çäžå¡è¯ä¹ãå žåäŸååŠäžã
pendoçæ¥çšãåŸ åã莊æ¬ãWeb åæéé»èŸå šéšåšæä»¶å å®æïŒæ¡æ¶åªèŽèŽ£æ/pendo ...å宿¶ä»»å¡è°è¿å»ãxiaoqing_chatäœäžºsmalltalk_provideræ¶ïŒæ¡æ¶æçŸ€èæ¶æ¯äº€ç»æä»¶è§å¯ïŒæä»¶å éšåçš attention gateã颿§ãplannerãLLM å reply checker å³å®æ¯åŠåå€ã
1.1 å€çæµçšæŠè¿° â
OneBot äºä»¶
â
Dispatcher.handle_event()
â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â 1. äºä»¶ç±»åæ£æ¥ïŒä»
å€ç message ç±»åïŒ â
â 2. æ¶æ¯è§£æïŒæåææ¬ãuser_idãgroup_idïŒ â
â 3. URL æ£æµïŒå
šå±çå¬ïŒå¯éïŒ â
â 4. å³çå€æïŒæ¯åŠéèŠå€çïŒ â
â 5. Handler éŸåŒå€çïŒæäŒå
çº§äŸæ¬¡å°è¯ïŒ â
â - BotNameHandler â ä»
æºåšäººåå â
â - CommandHandler â åœä»€å¹é
â
â - SessionHandler â æŽ»è·äŒè¯ â
â - SmalltalkHandler â é²è â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â
è¿å OneBot æ¶æ¯æ®µå衚1.2 Handler éŸåŒæ¶æ â
XiaoQing éçš èŽ£ä»»éŸæš¡åŒ æ¥å€çæ¶æ¯ïŒæ¯äžª Handler æé¡ºåºå°è¯å€çæ¶æ¯ïŒåŠæå€çæååè¿åïŒåŠåäŒ éç»äžäžäžª Handlerã
Handler éŸé¡ºåº â
self._handlers: tuple[MessageHandler, ...] = (
BotNameHandler(self), # 1. å€çä»
æåæºåšäººååçæ¶æ¯
CommandHandler(self), # 2. å¹é
å¹¶æ§è¡åœä»€
SessionHandler(self), # 3. å€ç掻è·äŒè¯
SmalltalkHandler(self), # 4. å€çé²è
)å Handler è莣 â
| Handler | å€çåºæ¯ | è¿åæ¡ä»¶ |
|---|---|---|
| BotNameHandler | çšæ·ä» åéæºåšäººååïŒåŠ "å°é"ïŒ | ææ¬ä» å å« bot_name æå ¶åäœ |
| CommandHandler | çšæ·åéåœä»€ïŒåŠ "/help"ïŒ | åœä»€è·¯ç±å¹é æå |
| SessionHandler | çšæ·å€äºæŽ»è·äŒè¯äž | ååšæŽ»è·äŒè¯ |
| SmalltalkHandler | å ¶ä»æ åµïŒé²èïŒ | smalltalk_mode 䞺 True |
å ³é®ç¹æ§ â
- çè·¯æºå¶ïŒäžæŠæäžª Handler å€çæåïŒåç» Handler äžäŒæ§è¡
- äŒå 级æç¡®ïŒåœä»€äŒå äºäŒè¯ïŒäŒè¯äŒå äºé²è
- ç¬ç«å³çïŒæ¯äžª Handler ç¬ç«å³å®æ¯åŠå€çïŒäºäžåœ±å
xiaoqing_chat ç¹æ®å€ç â
åœ smalltalk_provider 讟眮䞺 xiaoqing_chat æ¶ïŒå³çé»èŸç¹æ®ïŒ
if self._get_smalltalk_provider() == "xiaoqing_chat":
return ProcessDecision(True, True) # æææ¶æ¯éœå
讞è¿å
¥ SmalltalkHandler该è¡äžºåžŠæ¥ä»¥äžç»æã
random_reply_rateé 眮倱æ - ææçŸ€èæ¶æ¯éœäŒè¿å ¥ xiaoqing_chat- æä»¶èªäž»æ§å¶ - xiaoqing_chat æä»¶å éšæ attention gateãç¡¬é¢æ§ãæ®éæè¯æŠçãheartflow å PFC planner
- directed attention å±äºåŒºå¶åå€ - è¿å
¥æä»¶åïŒ
/xcãç§èã被@ãçŽæ¥å«bot_nameãåªååååç远é®ãreply åŒçšå°éã以åè¿æäžäžæéå®å°éçâ她/taâå ±æå¬å€ïŒäŒæ 记䞺 forcedïŒè·³è¿æ®éå倿Šç倿 - è¿å空å衚äžåå€ - æä»¶å³å®äžå倿¶è¿å
[]
è¿ç§è®Ÿè®¡è®© LLM æš¡åæ ¹æ®äžäžæå€æå倿¶æºïŒæ¯åºå®éæºæŠçæŽèŽŽè¿å®é 对è¯ã
2ïžâ£ æ¶æ¯æ¥æ¶äžè§£æ â
2.1 äºä»¶æ ŒåŒ â
XiaoQing æ¥æ¶ OneBot æ åæ ŒåŒçæ¶æ¯äºä»¶ïŒ
{
"post_type": "message",
"message_type": "group",
"user_id": 123456789,
"group_id": 987654321,
"message": [
{"type": "text", "data": {"text": "/help æ¥çåž®å©"}}
]
}2.2 æ¶æ¯è§£æ â
normalize_message() åœæ°ä»äºä»¶äžæåå
³é®ä¿¡æ¯ïŒ
text, user_id, group_id = normalize_message(event)
# text: "/help æ¥çåž®å©"
# user_id: 123456789
# group_id: 987654321 (ç§èæ¶äžº None)2.3 ææ¬æå â
extract_text() åœæ°ä» OneBot æ¶æ¯æ®µäžæåçº¯ææ¬ïŒ
- åç¬Šäž²æ¶æ¯: çŽæ¥è¿å
- æ¶æ¯æ®µæ°ç»: æåææ
type: "text"æ®µçææ¬å¹¶æŒæ¥ - å ¶ä»ç±»åïŒåŸçã@çïŒ: äžåäžåœä»€ææ¬æå
诎æïŒåœä»€è·¯ç±ä»ç¶åªççº¯ææ¬ïŒäœåœ
smalltalk_provider = "xiaoqing_chat"äžæä»¶åªäœèœååŒå¯æ¶ïŒçº¯åŸç/衚æ å æ¶æ¯äžäŒåš parser é¶æ®µè¢«äž¢åŒïŒåç»äŒç±xiaoqing_chatèªå·±è¯»ååå§æ¶æ¯æ®µå¹¶æž²ææ[åŸçïŒ...]/[衚æ å ïŒ...]æ³šå ¥äžäžæã
è¡¥å ïŒè¿å ¥
xiaoqing_chatåïŒæä»¶äŒæåå§æ¶æ¯æ®µé¡ºåºæææ¬ååªäœ marker éæ°æŒåâææçšæ·èŸå ¥âãä¹å°±æ¯è¯ŽïŒæå + åŸç + æåäžäŒåå¡æâæææååšåãææåŸçåšåâã
# èŸå
¥
message = [
{"type": "at", "data": {"qq": "123"}},
{"type": "text", "data": {"text": "äœ å¥œ"}},
{"type": "image", "data": {"file": "abc.jpg"}},
{"type": "text", "data": {"text": "äžç"}}
]
# èŸåº
text = "äœ å¥œäžç"3ïžâ£ å³ç倿 â
3.1 å³çé»èŸ â
_decide_process() æ¹æ³å€ææ¶æ¯æ¯åŠéèŠå€çïŒè¿å ProcessDecision(should_process, smalltalk_mode)ïŒ
| åºæ¯ | should_process | smalltalk_mode | 诎æ |
|---|---|---|---|
| ç§è | â True | â True | ç§èæ¶æ¯å§ç»å€çïŒå¯é²è |
| 矀è + åœä»€åçŒ | â True | â False | åŠ /helpïŒäžè§Šåé²è |
| 矀è + å å« bot_name | â True | â ïž åå³äºéé³ | åŠ å°é äœ å¥œ |
| 矀è + éæºè§Šå | â True | â True | æ random_reply_rate æŠç |
| 矀è + éé³äž | â False | â False | é€éæåœä»€åçŒæ bot_name |
3.2 å³çäž Handler éŸçå ³ç³» â
éèŠçè§£ïŒå³ç倿çç»æäŒåœ±å Handler éŸçæ§è¡ïŒ
should_process = FalseïŒçŽæ¥è¿å[]ïŒææ Handler éœäžäŒæ§è¡should_process = TrueïŒè¿å ¥ Handler éŸïŒæé¡ºåºå°è¯å䞪 Handler
ç¹æ®åºæ¯ïŒ
- 掻è·äŒè¯ååšïŒæ 论
should_processåŠäœïŒSessionHandleréœäŒå€çïŒäŒè¯äŒå 级æé«ïŒ - xiaoqing_chat äœäžº smalltalk_providerïŒå³çé»èŸç¹æ®ïŒææçŸ€èæ¶æ¯éœè¿å
(True, True)ïŒrandom_reply_rate倱æ
3.3 é 眮项 â
{
"bot_name": "å°é",
"command_prefixes": ["/"],
"require_bot_name_in_group": true,
"random_reply_rate": 0.05
}- bot_name: æºåšäººåç§°ïŒçŸ€èäžæåæ¶è§Šå
- command_prefixes: åœä»€åçŒå衚ïŒé垞䞺
["/"] - require_bot_name_in_group: çŸ€èæ¯åŠéèŠ @ ææå bot_name
- random_reply_rate: æ è§Šåæ¡ä»¶æ¶éæºåå€çæŠç (0-1)
3.4 åçŒå¥çŠ» â
parse_text_command_context() åœæ°æç
§ä»¥äžé¡ºåºäž¥æ Œå€çåçŒå¥çŠ»ïŒ
- å»é€ @æºåšäººïŒäŸåŠ
[CQ:at,qq=123]ïŒ - å»é€ bot_nameïŒäŸåŠ
å°éïŒæ¯ææš¡ç³å¹é åå ¶åçæ ç¹ïŒ - å»é€ command_prefixesïŒäŸåŠ
/ïŒ
â ïž éèŠè§£æè§åïŒ
åœçšæ·èŸå
¥ å°éé
眮 æ¶ïŒ
bot_nameïŒå°éïŒéŠå è¢«æ£æµå¹¶ç§»é€ïŒå©äœææ¬å䞺é 眮ã- éåå°è¯ç§»é€åœä»€åçŒïŒåŠ
/ïŒïŒå äžå¹é èè·³è¿ã - æç»äŒ éç» Router çææ¬æ¯
é 眮ã
æä»¶åœä»€è§Šåè¯å®ä¹äžº ["å°éé
眮"] æ¶äŒå¹é
倱莥ïŒå 䞺 Router çå°çæ¯ "é
眮"ã
å»ºè®®åæ³ å»ºè®®åš plugin.json äžå®ä¹è§Šåè¯æ¶ïŒå
å«å¥çŠ» bot ååççæ¬ã
# èŸå
¥: "å°é /help æ¥çåž®å©"
# 1. å¥çŠ» bot_name -> "/help æ¥çåž®å©"
# 2. å¥çŠ» prefix -> "help æ¥çåž®å©"
# ç»æ: å¹é
trigger "help"# èŸå
¥: "å°éé
眮"
# 1. å¥çŠ» bot_name -> "é
眮"
# 2. å¥çŠ» prefix -> "é
眮" (æ åçŒå¯å¥çŠ»)
# ç»æ: éå¹é
trigger "é
眮" (å æ€å»ºè®®åš json äžæ·»å "é
眮" äœäžº trigger)4ïžâ£ åœä»€è·¯ç±äžåæ°æå â
4.1 åœä»€æ³šå â
æ¯äžªæä»¶åš plugin.json äžå£°æåœä»€ïŒ
{
"commands": [
{
"name": "help",
"triggers": ["help", "h", "åž®å©"],
"help": "æ¥çåž®å© | /help [å
³é®è¯]",
"admin_only": false,
"priority": 0
}
]
}4.2 è·¯ç±å¹é â
CommandRouter.resolve() æ¹æ³å¹é
åœä»€ïŒ
resolved = router.resolve("help æ¥çåž®å©")
# resolved = (CommandSpec, args)
# spec.name = "help"
# spec.plugin = "core"
# args = "æ¥çåž®å©"å¹é è§åïŒ
- éåæææ³šåçåœä»€
- æ£æ¥ææ¬æ¯åŠä»¥ä»»æ trigger åŒå€Ž
- æäŒå 级æåºïŒpriority è¶å€§è¶äŒå ïŒ
- åäŒå 级æ¶ïŒtrigger è¶é¿è¶äŒå
4.3 åæ°æå â
å¹é
æååïŒtrigger åé¢çææ¬äœäžº args äŒ éç» handlerïŒ
èŸå
¥ææ¬: "echo äœ å¥œ äžç"
å¹é
trigger: "echo"
args: "äœ å¥œ äžç"æä»¶å¯äœ¿çš core.args æš¡åè¿äžæ¥è§£æåæ°ïŒ
from core.args import parse
parsed = parse("äœ å¥œ äžç -v --name=test")
# parsed.tokens = ["äœ å¥œ", "äžç"]
# parsed.first = "äœ å¥œ"
# parsed.second = "äžç"
# parsed.opt("v") = "true"
# parsed.opt("name") = "test"4.4 Handler è°çš â
async def handle(command: str, args: str, event: dict, context) -> List[dict]:
"""
Args:
command: åœä»€åïŒplugin.json äžç nameïŒ
args: åæ°å笊䞲ïŒtrigger åçéšåïŒ
event: åå§ OneBot äºä»¶
context: æä»¶äžäžæ
Returns:
OneBot æ¶æ¯æ®µå衚
"""5ïžâ£ äŒè¯ç®¡ç â
5.1 äŒè¯è§Šå â
äŒè¯æ¯ Handler éŸç第äžç¯ïŒåšåœä»€å¹é å€±èŽ¥åæ§è¡ïŒ
# SessionHandler å€çé»èŸ
session = await session_manager.get(user_id, group_id)
if session:
# è·¯ç±å°äŒè¯æä»¶ç handle_session()éèŠç¹æ§ïŒ
- äŒå 级ïŒäŒè¯å€çåšåœä»€ä¹åãé²èä¹å
- ç»è¿è§Šåæ¡ä»¶ïŒå³äœ¿
should_process = FalseïŒæŽ»è·äŒè¯ä»äŒå€ç - ç¬ç«å€çïŒäŒè¯å€çäžå
random_reply_rateæbot_name圱å
5.2 äŒè¯å建 â
æä»¶éè¿ context.create_session() å建äŒè¯ïŒ
async def handle(command, args, event, context):
session = await context.create_session(
initial_data={"target": 42},
timeout=180 # 3 åéè¶
æ¶
)
return segments("æžžæåŒå§ïŒ")5.3 äŒè¯å€ç â
åœçšæ·åšäŒè¯äžåéæ¶æ¯æ¶ïŒè°çš handle_session()ïŒ
async def handle_session(text: str, event: dict, context, session) -> List[dict]:
"""
Args:
text: çšæ·èŸå
¥çææ¬
event: åå§äºä»¶
context: æä»¶äžäžæ
session: åœåäŒè¯å¯¹è±¡
"""
guess = int(text)
target = session.get("target")
if guess == target:
await context.end_session()
return segments("æåïŒç对äºïŒ")5.4 éåºåœä»€ â
以äžåœä»€å¯éåºäŒè¯ïŒ
éåºãåæ¶ãexitãquitãq
5.5 äžåå°éåçåºå« â
æ¡æ¶ Session äŒæ¥ç®¡åäžçšæ·åç»æªåœäžåœä»€çæ¶æ¯ïŒéå亀äºåŒè¡šåãæžžæãSSH REPL å /pendo ledger add è¿ç§éæ¥åŒå¯Œãèæ¶åå°ä»»å¡äžåºäžºäºâä¿æäžäžæâèåå»ºæ¡æ¶ Sessionã
codex æä»¶äœ¿çšç¬ç«äžå¡äŒè¯ïŒ/codex create main åªå建 Codex æ çŸåå·¥äœç®åœïŒåç»å¿
é¡»æŸåŒåé /codex main <ä»»å¡>ãä»»å¡è¿å
¥æä»¶èªå·±çéååïŒåœå handler ç«å³è¿åïŒå®æç»æéè¿ context.send_action() äž»åšåéïŒåŸçç»æäŒä»¥ QQ image 段éæåååïŒå æ€äžäŒåœ±åçšæ·ç»§ç»åå
¶ä»åœä»€æé²èã
6ïžâ£ é²èå€ç â
6.1 è§Šåæ¡ä»¶ â
åœä»¥äžæ¡ä»¶éœæ»¡è¶³æ¶è¿å ¥é²èæš¡åŒïŒ
- 没æå¹é å°åœä»€
- æ²¡ææŽ»è·äŒè¯
smalltalk_mode = True
6.2 é²èæäŸè â
éè¿é çœ®éæ©é²èæä»¶ïŒ
{
"plugins": {
"smalltalk_provider": "xiaoqing_chat"
}
}æ¯æçæäŸè ïŒ
- smalltalk: åºäºè§åçç®åé²è
- xiaoqing_chat: åºäº LLM çæºèœå¯¹è¯
6.3 xiaoqing_chat ç¹æ®å€ç â
åœ smalltalk_provider 讟眮䞺 xiaoqing_chat æ¶ïŒ
random_reply_rateäžçæ - ææçŸ€èæ¶æ¯éœäŒè¿å ¥xiaoqing_chatå€ç- æä»¶èªè¡å³å®æ¯åŠåå€ -
xiaoqing_chatæèªå·±ç attention gateãé¢çæ§å¶åæ®éæè¯æŠç倿 - directed attention äŒåŒºå¶åå€ - äžæŠè¿å
¥æä»¶ïŒ
/xcãç§èã@ãçŽæ¥å«bot_nameãåªååååç远é®ãreply åŒçšå°éïŒææè¿æäžäžæéç¹çâ她/taâå ±æå¬å€äŒèµ° forced è·¯åŸ - è¿å空å衚衚瀺äžåå€ - æä»¶å³å®äžå倿¶è¿å
[] - åŸçæ¶æ¯å¯è¿å ¥é²èéŸ - 纯åŸç/衚æ å æ¶æ¯äŒè¢«ä¿çå°æä»¶å±ïŒåå³å®æ¯åŠåå ¥äžäžæ
- æ··ååŸæäŒä¿çé¡ºåº -
xiaoqing_chatäŒåšæä»¶å±æåå§ segment 顺åºé建ææçšæ·èŸå ¥ - åªäœå倿¯åå€çæ¥éª€ - äž»åå€ä»å çæçº¯ææ¬ïŒæ¯åŠåè¡¥æ¬å°åŸç / 衚æ å / QQ 衚æ ç±æä»¶ç¬¬äºé¶æ®µå³å®ïŒæ§åŸåºåæ¡ç®ååšåå°è¡¥ä¿®
è¿æ ·è®Ÿè®¡çåå æ¯ LLM æš¡åå¯ä»¥æ ¹æ®äžäžæå€æå倿¶æºïŒæ¯åºå®éæºæŠçæŽèŽŽè¿å®é 对è¯ã
6.4 å€çåœæ° â
é²èæä»¶éå®ç° handle_smalltalk()ïŒ
async def handle_smalltalk(text: str, event: dict, context) -> List[dict]:
"""
Args:
text: çšæ·èŸå
¥ïŒå·²å»é€åçŒïŒxiaoqing_chat å¯èœåç»å event äžçåŸçæ®µçæææäžäžæïŒ
event: åå§äºä»¶
context: æä»¶äžäžæ
Returns:
å倿¶æ¯æ®µïŒæ None/[] 衚瀺äžåå€
"""7ïžâ£ é鳿ºå¶ â
7.1 éé³åœä»€ â
/éåŽ 30 # éé³ 30 åé
/éåŽ 1h # éé³ 1 å°æ¶
/è¯Žè¯ # è§£é€éé³7.2 éé³åœ±å â
| æ¶æ¯ç±»å | é鳿¶æ¯åŠå€ç |
|---|---|
| 垊åœä»€åçŒçæ¶æ¯ | â å€ç |
| äž»åš @ æºåšäºº | â å€çåœä»€ïŒâ äžé²è |
| éæºåå€ | â äžåå€ |
| 宿¶ä»»å¡ | â äžåéïŒç±æä»¶èªè¡å€æïŒ |
8ïžâ£ å¹¶åæ§å¶äžæ¶æ¯éå â
8.1 æŠè¿° â
XiaoQing 䜿çšå€å±å¹¶åæ§å¶æºå¶æ¥ç®¡çæ¶æ¯å€çïŒç¡®ä¿ç³»ç»çš³å®æ§åååºæ§èœã
8.2 OneBot WebSocket Client å€çæµçš â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â OneBot æå¡åš (NapCatQQ/go-cqhttp) â
âââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââ
â WebSocket æ¶æ¯æšé
â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â OneBotWsClient._listen() â
â æ¥æ¶å¹¶è§£æ WebSocket æ¶æ¯ â
âââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â 第äžå±æ§å¶ïŒ_pending_semaphore â
â æå€ 100 äžªæ¶æ¯çåŸ
ååïŒç¡¬çŒç ïŒäžå¯é
çœ®ïŒ â
â â
â async with self._pending_semaphore: â
â await self._dispatch_event(handler, event) â
âââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â æçšæ·/矀åéå (æºèœè®Ÿè®¡) â
â â
â æ ¹æ® queue_key ååå°äžåéåïŒ â
â - group:123:user:456 â Queue1 [event1, event2] â
â - user:789 â Queue2 [event3] â
â - group:999:user:111 â Queue3 [event4, event5] â
â â
â æ¯äžªéåæç¬ç«ç _drain_queue() åçšäž²è¡å€ç â
â ä¿è¯ïŒåäžçšæ·åšåäžçŸ€çæ¶æ¯æé¡ºåºå€ç â
â å
讞ïŒäžåçšæ·/çŸ€çæ¶æ¯å¹¶è¡å€ç â
âââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â _drain_queue() â handler() â app._process_event() â
âââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â 第äºå±æ§å¶ïŒmax_concurrency (é»è®€ 5) ð¥ â
â â
â Dispatcher.handle_event(): â
â async with self.semaphore: â
â return await self._process_event(event) â
â â
â â
å
šå±å¹¶åæ§å¶çæ žå¿ â
â â
å¯¹æææ¥æ¶æ¹åŒïŒWS Client/InboundïŒéœçæ â
âââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââ
â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â æ§è¡åœä»€/äŒè¯/é²èå€çå¹¶è¿åç»æ â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ8.3 䞀å±å¹¶åæ§å¶æºå¶ â
第äžå±ïŒ_pending_semaphore (max_pending_events = 100) â
- äœçœ®ïŒ
core/onebot.py - äœçšïŒéå¶åæ¶çåŸ ååçäºä»¶æ°é
- æ¯åŠå¯é 眮ïŒâ åŠïŒç¡¬çŒç ïŒ
- 圱åïŒä» 对 OneBot WS Client ææ
第äºå±ïŒmax_concurrency (é»è®€ 5) ð¥ â
- äœçœ®ïŒ
core/dispatcher.py - äœçšïŒéå¶åæ¶æ§è¡å€çé»èŸçæ°éïŒçæ£çå¹¶åæ§å¶ïŒ
- æ¯åŠå¯é
眮ïŒâ
æ¯ïŒ
config.jsonïŒ - 圱åïŒå šå±çæïŒWS ClientãInbound ServerïŒ
8.4 æçšæ·/矀åéå讟计 â
æ žå¿è®Ÿè®¡ïŒæ¯äžª (group_id, user_id) ç»å对åºäžäžªç¬ç«éåã
queue_key çæè§åïŒ
# çŸ€èæ¶æ¯
queue_key = f"group:{group_id}:user:{user_id}"
# ç§èæ¶æ¯
queue_key = f"user:{user_id}"äŒå¿ïŒ
- â ä¿è¯é¡ºåºïŒåäžçšæ·åšåäžçŸ€çæ¶æ¯äž¥æ Œæé¡ºåºå€ç
- â æé«ååïŒäžåçšæ·/çŸ€çæ¶æ¯å¯ä»¥å¹¶è¡å€ç
- â é¿å é»å¡ïŒæäžªçšæ·çæ ¢æäœäžåœ±åå ¶ä»çšæ·
å®é è¿è¡ç€ºäŸïŒ
å讟 max_concurrency = 5ïŒåæ¶æ¶å°åŠäžæ¶æ¯ïŒ
| æ¶éŽ | æ¥æº | queue_key | ç¶æ |
|---|---|---|---|
| T1 | 矀Açšæ·1 | group:A:user:1 | â è·åŸç¬¬1äžªå¹¶åæ§œ |
| T2 | 矀Açšæ·1 | group:A:user:1 | â³ åšéåäžçåŸ ïŒåäžçšæ·äž²è¡ïŒ |
| T3 | 矀Bçšæ·2 | group:B:user:2 | â è·åŸç¬¬2äžªå¹¶åæ§œ |
| T4 | 矀Cçšæ·3 | group:C:user:3 | â è·åŸç¬¬3äžªå¹¶åæ§œ |
| T5 | 矀Dçšæ·4 | group:D:user:4 | â è·åŸç¬¬4äžªå¹¶åæ§œ |
| T6 | 矀Eçšæ·5 | group:E:user:5 | â è·åŸç¬¬5äžªå¹¶åæ§œ |
| T7 | 矀Fçšæ·6 | group:F:user:6 | âžïž çåŸ å¹¶åæ§œéæŸ |
| T8 | 矀Açšæ·1 | group:A:user:1 | â³ åšéåäžçåŸ ïŒæåš T2 åé¢ïŒ |
8.5 Inbound WebSocket Server éåæºå¶ â
å¯¹äº Inbound ServerïŒè¢«å𿥿¶æšéïŒïŒæé¢å€çéåæºå¶ïŒ
WebSocket æ¶æ¯å°èŸŸ
â
æŸå
¥éå (maxsize = ws_queue_size, é»è®€ 200)
â
inbound_ws_max_workers 䞪 worker ä»éååæ¶æ¯ (é»è®€ 8 䞪)
â
éè¿ Semaphore è·åå€çè®žå¯ (max_concurrency, é»è®€ 5)
â
å€çæ¶æ¯ (Dispatcher)é çœ®åæ°ïŒ
| åæ° | é»è®€åŒ | äœçšèåŽ | 诎æ |
|---|---|---|---|
ws_queue_size | 200 | Inbound + OneBot | çåŸ å€ççæ¶æ¯éåé¿åºŠ |
inbound_ws_max_workers | 8 | Inbound Server | å¹¶åå€çéåæ¶æ¯ç worker æ° |
â ïž æ³šæïŒinbound_ws_max_workers ä»
对 Inbound WS Server ææïŒws_queue_size åæ¶åœ±å Inbound WS Server äž OneBot WS Clientã
8.6 æ žå¿é çœ®åæ° â
| åæ° | é»è®€åŒ | éçšèåŽ | 诎æ |
|---|---|---|---|
max_concurrency | 5 | å šå± | ð¥ æéèŠïŒå šå±å¹¶åæ§å¶ |
inbound_ws_max_workers | 8 | Inbound Server | Worker åçšæ° |
ws_queue_size | 200 | Inbound + OneBot | éåé¿åºŠïŒ0 衚瀺äžéå¶ïŒ |
8.7 é 眮建议 â
äœèŽèœœåºæ¯ïŒäžªäººäœ¿çšïŒ1-3 äžªçŸ€ïŒ â
{
"max_concurrency": 5
}äžçèŽèœœïŒå€äžªæŽ»è·çŸ€ç»ïŒ â
{
"max_concurrency": 10,
"ws_queue_size": 300,
"inbound_ws_max_workers": 12
}é«èŽèœœåºæ¯ïŒå€§é矀ç»ïŒé¢ç¹æ¶æ¯ïŒ â
{
"max_concurrency": 20,
"ws_queue_size": 500,
"inbound_ws_max_workers": 24
}äŒåååïŒ
inbound_ws_max_workers >= max_concurrencyïŒé¿å worker 空é²ïŒws_queue_sizeè¶³å€å€§ä»¥åžæ¶çªåæµéïŒæè®Ÿäžº 0 äžéå¶ïŒmax_concurrencyäžèŠè®Ÿçœ®è¿é«ïŒé¿å èµæºèå°œ
8.8 æ§èœçæ§ â
æ¥çè¿è¡ç¶æïŒ
GET /healthåŠæé
çœ®äº inbound_tokenïŒéèŠæºåžŠïŒ
Authorization: Bearer <inbound_token>ååºç€ºäŸïŒ
{
"status": "ok",
"ws_connections": 1,
"plugins_loaded": 29,
"pending_jobs": 3,
"active_sessions": 2
}æ¥çè¯Šç»ææ ïŒ
GET /metrics/metrics è¿å MetricsCollector èåçæä»¶æ§è¡ç»è®¡ïŒ/health äž /metrics éœç± Inbound HTTP æå¡çŽæ¥æäŸã
8.9 åºç«åéè·¯åŸ â
æææä»¶æç»éœèµ°ç»äžç _send_action() åééŸè·¯ã
- äŒå å€çšåœåäºä»¶äžäžæäžç action sinkã
- å°è¯åéå° OneBot WS Clientã
- è¥åœåå¯çšäº Inbound WS äžææŽ»è·å®¢æ·ç«¯ïŒå广æç»æŽ»è·å®¢æ·ç«¯ã
- è¥ä»äžå¯çšïŒååéå° OneBot HTTP senderã
å æ€å¯çšåéééšçœ²æ¶ïŒWS çææåŒäžäŒçŽæ¥å¯ŒèŽæ¶æ¯éé»äž¢å€±ã
åå°ä»»å¡ä¹åºå€çšè¿æ¡åééŸè·¯ãæä»¶å¯ä»¥ä¿åè§Šå任塿¶ç user_id / group_idïŒçä»»å¡å®æåéè¿ context.send_action(build_action(...)) äž»åšåéãåéé¶æ®µä»äŒæ§è¡é¿ææ¬åçãWS/HTTP åéåé误æ¥å¿å€çïŒæä»¶èªèº«äžéèŠäžºäºé¿å
æªæè讟眮åç¬çç»æå笊äžéã
9ïžâ£ 宿Žå€çæµçšåŸ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â OneBot æ¶æ¯äºä»¶å°èŸŸ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â
âŒ
âââââââââââââââââââ
â post_type == â
â "message" â
ââââââââââ¬âââââââââ
â
ââââââââââŽâââââââââ
â â
Yes No ââââââââââââââââââ⺠応ç¥
â
âŒ
âââââââââââââââââââââââââ
â normalize_message â
â æå text, user_id, â
â group_id â
âââââââââââââ¬ââââââââââââ
â
âŒ
âââââââââââââââââââââââââ
â URL æ£æµ âââââ æ URL äžæ åçŒ ââââ⺠url_parser
âââââââââââââ¬ââââââââââââ
â
âŒ
âââââââââââââââââââââââââ
â _decide_process â
â 倿æ¯åŠå€çæ¶æ¯ â
âââââââââââââ¬ââââââââââââ
â
ââââââââââŽâââââââââ
â â
should_process äžå€ç ââââââââââââââââââââââ⺠è¿å []
= True
â
âŒ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â Handler éŸåŒå€ç â
â æäŒå
çº§äŸæ¬¡å°è¯ â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â
ââââââââââŽâââââââââ
â â
BotNameHandler 倱莥
ä»
æºåšäººåå â
â â
ââââââŽâââââ âŒ
Yes No âââââââââââââââââââ
â â â CommandHandler â
⌠â â åœä»€å¹é
â
å€ç宿 â ââââââââââ¬âââââââââ
_handle_bot â â
_name_only â âââââââââŽââââââââ
â å¹é
æå æªå¹é
â â â
â ⌠âŒ
â âââââââââââââ âââââââââââââââââââ
â â æéæ£æ¥ â â SessionHandler â
â âââââââ¬ââââââ â æŽ»è·äŒè¯ â
â â ââââââââââ¬âââââââââ
â ⌠ââââââââââŽâââââââââ
â âââââââââââââ æäŒè¯ æ äŒè¯
â â æ§è¡åœä»€ â â â
â â handler() â ⌠âŒ
â âââââââ¬ââââââ ââââââââââââ âââââââââââââââââââ
â â âhandle_ â â SmalltalkHandlerâ
â â âsession() â â smalltalk_mode â
â â âââââââ¬âââââ ââââââââââ¬âââââââââ
â â â ââââââââââŽâââââââââ
â â â Yes No
â â â â â
â â â ⌠âŒ
â â â âââââââââââââ è¿å []
â â â â_handle_ â
â â â âsmalltalk() â
â â â âââââââ¬ââââââ
â â â â
ââââââââââŽâââââââââââââââŒâââââââââŽâââââââââ
â
âŒ
âââââââââââââââââââ
â è¿åæ¶æ¯æ®µå衚 â
âââââââââââââââââââð éåœïŒå ³é®ä»£ç äœçœ® â
| åèœ | æä»¶ | åœæ°/æ¹æ³ |
|---|---|---|
| æ¶æ¯è§£æ | core/message.py | normalize_message(), extract_text() |
| åçŒå¥çŠ» & äžäžæè§£æ | core/message.py | parse_text_command_context() |
| å³ç倿 | core/dispatcher.py | Dispatcher._decide_process() |
| åœä»€è·¯ç± | core/router.py | CommandRouter.resolve() |
| äŒè¯ç®¡ç | core/session.py | SessionManager |
| é鳿§å¶ | core/dispatcher.py | mute_group(), is_muted() |
| Handler éŸ | core/dispatcher.py | BotNameHandler, CommandHandler, SessionHandler, SmalltalkHandler |