forked from mttu-developers/konabot
LLM 胜利了!!!!!!
This commit is contained in:
3
konabot/common/ptimeparse/README.md
Normal file
3
konabot/common/ptimeparse/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 已废弃
|
||||
|
||||
坏枪用简单的 LLM + 提示词工程,完成了这 200 块的 `qwen3-coder-plus` 都搞不定的 nb 功能
|
||||
@ -9,7 +9,6 @@ import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .expression import TimeExpression
|
||||
from .err import TokenUnhandledException, MultipleSpecificationException
|
||||
|
||||
|
||||
def parse(text: str, now: Optional[datetime.datetime] = None) -> datetime.datetime:
|
||||
@ -56,19 +55,4 @@ class Parser:
|
||||
TokenUnhandledException: If the input cannot be parsed
|
||||
"""
|
||||
return TimeExpression.parse(text, self.now)
|
||||
|
||||
def digest_chinese_number(self, text: str) -> tuple[str, int]:
|
||||
"""
|
||||
Parse a Chinese number from the beginning of text and return the rest and the parsed number.
|
||||
|
||||
This matches the interface of the original digest_chinese_number method.
|
||||
|
||||
Args:
|
||||
text: Text that may start with a Chinese number
|
||||
|
||||
Returns:
|
||||
Tuple of (remaining_text, parsed_number)
|
||||
"""
|
||||
from .chinese_number import ChineseNumberParser
|
||||
parser = ChineseNumberParser()
|
||||
return parser.digest(text)
|
||||
|
||||
|
||||
@ -2,10 +2,9 @@
|
||||
Abstract Syntax Tree (AST) nodes for the time expression parser.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, List
|
||||
from abc import ABC
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -69,4 +68,4 @@ class TimeExpressionNode(ASTNode):
|
||||
time: Optional[TimeNode] = None
|
||||
relative_date: Optional[RelativeDateNode] = None
|
||||
relative_time: Optional[RelativeTimeNode] = None
|
||||
weekday: Optional[WeekdayNode] = None
|
||||
weekday: Optional[WeekdayNode] = None
|
||||
|
||||
@ -10,7 +10,7 @@ from .ptime_ast import (
|
||||
TimeExpressionNode, DateNode, TimeNode,
|
||||
RelativeDateNode, RelativeTimeNode, WeekdayNode, NumberNode
|
||||
)
|
||||
from .err import TokenUnhandledException, MultipleSpecificationException
|
||||
from .err import TokenUnhandledException
|
||||
|
||||
|
||||
class SemanticAnalyzer:
|
||||
@ -366,4 +366,4 @@ class SemanticAnalyzer:
|
||||
smart_time = self.infer_smart_time(time.hour, time.minute, time.second, base_time=result)
|
||||
result = smart_time
|
||||
|
||||
return result
|
||||
return result
|
||||
|
||||
@ -6,34 +6,25 @@ from nonebot_plugin_alconna import Reference, Reply, UniMsg
|
||||
from nonebot.adapters import Event
|
||||
|
||||
|
||||
matcher_fix = on_message()
|
||||
|
||||
pattern = (
|
||||
r"^(?:(?:av|cv)\d+|BV[a-zA-Z0-9]{10})|"
|
||||
r"(?:b23\.tv|bili(?:22|23|33|2233)\.cn|\.bilibili\.com|QQ小程序(?:]|]|\])哔哩哔哩).{0,500}"
|
||||
)
|
||||
|
||||
|
||||
@matcher_fix.handle()
|
||||
async def _(msg: UniMsg, event: Event):
|
||||
def _rule(msg: UniMsg):
|
||||
to_search = msg.exclude(Reply, Reference).dump(json=True)
|
||||
to_search2 = msg.exclude(Reply, Reference).extract_plain_text()
|
||||
if not re.search(pattern, to_search) and not re.search(pattern, to_search2):
|
||||
return
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
matcher_fix = on_message(rule=_rule)
|
||||
|
||||
@matcher_fix.handle()
|
||||
async def _(event: Event):
|
||||
from nonebot_plugin_analysis_bilibili import handle_analysis
|
||||
|
||||
await handle_analysis(event)
|
||||
|
||||
# b_url: str
|
||||
# b_page: str | None
|
||||
# b_time: str | None
|
||||
#
|
||||
# from nonebot_plugin_analysis_bilibili.analysis_bilibili import extract as bilibili_extract
|
||||
#
|
||||
# b_url, b_page, b_time = bilibili_extract(to_search)
|
||||
# if b_url is None:
|
||||
# return
|
||||
#
|
||||
# await matcher_fix.send(await UniMessage().text(b_url).export())
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
from nonebot import on_message
|
||||
from nonebot_plugin_alconna import UniMessage, UniMsg
|
||||
from nonebot_plugin_alconna import UniMessage
|
||||
|
||||
evt = on_message()
|
||||
from konabot.common.nb.match_keyword import match_keyword
|
||||
|
||||
evt = on_message(rule=match_keyword("喵"))
|
||||
|
||||
@evt.handle()
|
||||
async def _(msg: UniMsg):
|
||||
if msg.extract_plain_text() == "喵":
|
||||
await evt.send(await UniMessage().text("喵").export())
|
||||
async def _():
|
||||
await evt.send(await UniMessage().text("喵").export())
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import re
|
||||
import aiohttp
|
||||
import asyncio as asynkio
|
||||
from math import ceil
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import datetime
|
||||
|
||||
import nanoid
|
||||
import nonebot
|
||||
@ -14,9 +14,10 @@ from nonebot_plugin_alconna import Alconna, Args, Subcommand, UniMessage, UniMsg
|
||||
from pydantic import BaseModel
|
||||
|
||||
from konabot.common.longtask import DepLongTaskTarget, LongTask, create_longtask, handle_long_task, longtask_data
|
||||
from konabot.common.ptimeparse import parse
|
||||
from konabot.common.nb.match_keyword import match_keyword
|
||||
from konabot.plugins.simple_notify.ask_llm import ask_ai
|
||||
|
||||
evt = on_message()
|
||||
evt = on_message(rule=match_keyword(re.compile("^.+提醒我.+$")))
|
||||
|
||||
(Path(__file__).parent.parent.parent.parent / "data").mkdir(exist_ok=True)
|
||||
DATA_FILE_PATH = Path(__file__).parent.parent.parent.parent / "data" / "notify.json"
|
||||
@ -76,21 +77,12 @@ async def _(msg: UniMsg, mEvt: Event, target: DepLongTaskTarget):
|
||||
return
|
||||
|
||||
text = msg.extract_plain_text()
|
||||
if "提醒我" not in text:
|
||||
return
|
||||
|
||||
segments = text.split("提醒我", maxsplit=1)
|
||||
if len(segments) != 2:
|
||||
return
|
||||
|
||||
notify_time, notify_text = segments
|
||||
try:
|
||||
target_time = parse(notify_time)
|
||||
logger.info(f"从 {notify_time} 解析出了时间:{target_time}")
|
||||
except Exception:
|
||||
logger.info(f"无法从 {notify_time} 中解析出时间")
|
||||
return
|
||||
if not notify_text:
|
||||
target_time, notify_text = await ask_ai(text)
|
||||
if target_time is None:
|
||||
return
|
||||
|
||||
await create_longtask(
|
||||
|
||||
120
konabot/plugins/simple_notify/ask_llm.py
Normal file
120
konabot/plugins/simple_notify/ask_llm.py
Normal file
@ -0,0 +1,120 @@
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from konabot.common.llm import get_llm
|
||||
|
||||
|
||||
SYSTEM_PROMPT = """你是一个专门解析提醒请求的助手。请分析用户输入,识别其中是否包含提醒信息,并输出标准化的JSON格式结果。
|
||||
|
||||
输入格式通常是:"现在是zzzz;xxxx提醒我yyyy",其中:
|
||||
- zzzz 是系统将发给你的当前时间
|
||||
- xxxx 是用户提供的时间信息
|
||||
- yyyy 是提醒内容
|
||||
|
||||
输出要求:
|
||||
- 必须是有效的JSON对象
|
||||
- 包含以下字段:
|
||||
* datetime: 如果是绝对时间,填入ISO 8601格式的日期时间字符串;否则为null
|
||||
* datetime_delta: 如果是相对时间,填入ISO 8601持续时间格式;否则为null
|
||||
* datetime_delta_minus: 如果时间偏移量是负数,则此项为 true,否则为 false
|
||||
* content: 提醒内容的字符串
|
||||
* is_notice: 布尔值,表示这是否是真正的提醒请求
|
||||
|
||||
时间处理规则:
|
||||
- 绝对时间示例:如果 xxxx 输入了非常明确的时间点,如"2024年12月25日" → 转换为具体datetime
|
||||
- 相对时间示例:如果 xxxx 没有输入非常明确的时间点,如"10分钟后"、"2小时后"、"3天后" → 转换为datetime_delta
|
||||
- 如果用户输入了需要计算的时间,你需要计算出正确的结果,如"10分钟后的8分钟前" → 转换为 “PT2M”
|
||||
- zzzz 是系统提供的时间,每句话肯定都有,这不是你判断相对或绝对时间的依据,需严格按照 xxx 来判断
|
||||
- datetime和datetime_delta有且仅有一个不为null
|
||||
|
||||
时间格式要求:
|
||||
- datetime: "YYYY-MM-DDTHH:MM:SS" (ISO 8601)
|
||||
- datetime_delta: "PTxHxMxS" 格式 (如"PT1H30M"表示1小时30分钟)
|
||||
|
||||
判断标准:
|
||||
- is_notice=true: 明确包含时间+提醒内容的请求
|
||||
- is_notice=false: 闲聊、疑问句、或不符合提醒格式的内容
|
||||
|
||||
示例:
|
||||
用户:"明天下午2点提醒我开会"
|
||||
输出:{"datetime": "2024-01-16T14:00:00", "datetime_delta": null,
|
||||
"datetime_delta_minus": false, "content": "开会", "is_notice": true}
|
||||
|
||||
用户:"5分钟后提醒我关火"
|
||||
输出:{"datetime": null, "datetime_delta": "PT5M", "datetime_delta_minus": false, "content": "关火", "is_notice": true}
|
||||
|
||||
用户:"5分钟前提醒我关火"
|
||||
输出:{"datetime": null, "datetime_delta": "PT5M", "datetime_delta_minus": true, "content": "关火", "is_notice": true}
|
||||
|
||||
用户:"昨天提醒我关火"
|
||||
输出:{"datetime": null, "datetime_delta": "PT1D", "datetime_delta_minus": true, "content": "关火", "is_notice": true}
|
||||
|
||||
用户:"什么是提醒功能?"
|
||||
输出:{"datetime": null, "datetime_delta": null, "datetime_delta_minus": false, "content": "", "is_notice": false}
|
||||
|
||||
请严格按照上述格式输出JSON,不要添加任何其他文字说明。现在是 DATETIME"""
|
||||
|
||||
pt_pattern = re.compile(
|
||||
r"^PT"
|
||||
r"((?P<day>\d+)D)?"
|
||||
r"((?P<hour>\d+)H)?"
|
||||
r"((?P<minute>\d+)M)?"
|
||||
r"((?P<second>\d+)S)?$"
|
||||
)
|
||||
|
||||
|
||||
def tryint(s: str | None):
|
||||
if s:
|
||||
if re.match(r"^\d+$", s):
|
||||
return int(s)
|
||||
return 0
|
||||
|
||||
|
||||
async def ask_ai(expression: str, now: datetime.datetime | None = None) -> tuple[datetime.datetime | None, str]:
|
||||
if now is None:
|
||||
now = datetime.datetime.now()
|
||||
prompt = SYSTEM_PROMPT.replace("DATETIME", str(now))
|
||||
|
||||
llm = get_llm()
|
||||
message = await llm.chat([
|
||||
{ "role": "system", "content": prompt },
|
||||
{ "role": "user", "content": expression },
|
||||
])
|
||||
result = message.content
|
||||
if result is None:
|
||||
return (None, "")
|
||||
|
||||
try:
|
||||
data = json.loads(result)
|
||||
except json.JSONDecodeError:
|
||||
logger.info(f"提醒功能:解析 AI 返回值时出现问题 raw={result}")
|
||||
return (None, "")
|
||||
|
||||
datetime_absolute = data.get("datetime", None)
|
||||
datetime_delta = data.get("datetime_delta", None)
|
||||
content = data.get("content", "")
|
||||
is_notice = data.get("is_notice", False)
|
||||
|
||||
if not is_notice:
|
||||
return (None, "")
|
||||
if datetime_absolute:
|
||||
try:
|
||||
return (datetime.datetime.strptime(datetime_absolute, "%Y-%m-%dT%H:%M:%S"), content)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if datetime_delta and (match := pt_pattern.match(datetime_delta)):
|
||||
days = tryint(match.group("day"))
|
||||
hours = tryint(match.group("hour"))
|
||||
minutes = tryint(match.group("minute"))
|
||||
seconds = tryint(match.group("second"))
|
||||
|
||||
dt = datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
|
||||
return (now + dt, content)
|
||||
|
||||
logger.warning(f"提醒功能:解析 AI 返回值时没有找到解析方法 raw={result}")
|
||||
return (None, "")
|
||||
|
||||
Reference in New Issue
Block a user