LLM 胜利了!!!!!!

This commit is contained in:
2025-11-21 16:13:38 +08:00
parent 3e5c1941c8
commit 6f08c22b5b
8 changed files with 149 additions and 59 deletions

View File

@ -0,0 +1,3 @@
# 已废弃
坏枪用简单的 LLM + 提示词工程,完成了这 200 块的 `qwen3-coder-plus` 都搞不定的 nb 功能

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -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())

View File

@ -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(

View 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格式结果。
输入格式通常是:"现在是zzzzxxxx提醒我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, "")