@@ -77,6 +77,7 @@ from jwebot_official_extensions.player_games import (
append_game_coin_event ,
append_game_record ,
blackjack_achievement_lines ,
card_label ,
hand_value ,
lottery_achievement_lines ,
next_game_index ,
@@ -108,6 +109,8 @@ MAX_CONTEXT_TURNS = 40
CONTEXT_CANDIDATE_LIMIT = 240
CONTEXT_RECENT_FALLBACK_TURNS = 10
CONTEXT_TRUSTED_SENDER_CONFIDENCE = 0.80
ACTIVE_THREAD_TTL_SECONDS = 10 * 60
ACTIVE_THREAD_SHORT_LIMIT = 24
MULTI_REPLY_DELAY_MIN_SECONDS = 0.5
MULTI_REPLY_DELAY_MAX_SECONDS = 1.0
OCR_EVENT_SOURCES = frozenset ( { " ocr " , " rapidocr-local " } )
@@ -494,6 +497,25 @@ CONTEXT_SELF_REFERENCE = re.compile(
r " 我那(?:把|局|个)|我这(?:把|局|个)) " ,
re . IGNORECASE ,
)
ACTIVE_THREAD_REPLY_HINT = re . compile (
r " (?:还真|真是|确实|也是|那确实|这么|这也|也太|有点|太|胆子|敢| "
r " 离谱|逆天|抽象|难评|牛|绷|笑|哈哈|可以|行吧|草|艹|绝了|夸张) " ,
re . IGNORECASE ,
)
LOCAL_PLAY_STATUS_QUESTION = re . compile (
r " ^ \ s*(?P<subject>[ \ w \ u4e00- \ u9fff.-] { 1,32}) \ s* "
r " (?:(?:现在|当前|刚才|刚刚|还)?(?:在|正?在)?)? \ s* "
r " (?P<ask>玩(?:什么|啥|哪(?:个|款)?(?:游戏)?)?| "
r " 什么游戏|啥游戏|游戏状态|状态|咋样|怎么样|啥情况) "
r " (?:吗|呢|了|呀|啊|? | \ ?)? \ s*$ " ,
re . IGNORECASE ,
)
LOCAL_PLAY_LIST_QUESTION = re . compile (
r " ^ \ s*(?:谁|哪个|哪些人|有人) \ s*(?:在|正?在)? \ s* "
r " (?:玩|打|进行) \ s*(?:什么|啥|游戏|21点|黑杰克|本地游戏)? "
r " (?:吗|呢|呀|啊|? | \ ?)? \ s*$ " ,
re . IGNORECASE ,
)
def apply_private_context ( value : Mapping [ str , Any ] ) - > None :
@@ -812,6 +834,60 @@ def context_terms(value: object) -> set[str]:
return profile_fact_terms ( text )
def short_thread_reply ( value : object ) - > bool :
text = normalize_text ( value )
if not text or local_command_kind ( text ) :
return False
compact = re . sub ( r " [ \ s, ,。.!! ?? ~~…]+ " , " " , text )
return (
1 < = len ( compact ) < = ACTIVE_THREAD_SHORT_LIMIT
and bool ( ACTIVE_THREAD_REPLY_HINT . search ( text ) )
)
def blackjack_hand_label ( cards : object ) - > str :
if not isinstance ( cards , list ) :
return " 未知手牌 "
normalized : list [ int ] = [ ]
for card in cards :
try :
normalized . append ( int ( card ) )
except ( TypeError , ValueError ) :
return " 未知手牌 "
if not normalized :
return " 未发牌 "
labels = " " . join ( card_label ( card ) for card in normalized )
return f " { labels } ( { hand_value ( normalized ) } 点) "
def visible_reply_text ( value : object ) - > str :
text = str ( value or " " )
parts = unpack_reply_parts ( text )
return " \n " . join ( parts ) if parts is not None else text
def thread_topic_label ( route : str , reason : str ) - > str :
if " blackjack " in reason :
return " 21点 "
if " trivia " in reason :
return " 冷门问答 "
if " dice " in reason :
return " 骰子 "
if " coin " in reason :
return " 硬币 "
if " lottery " in reason :
return " 彩票 "
if " draw " in reason :
return " 抽奖 "
if " steam " in reason :
return " Steam 状态 "
if route == " broadcast " :
return " Taffy 主动发言 "
if route == " chat " :
return " 群聊 "
return reason . removeprefix ( " local_ " ) . replace ( " _ " , " " ) or route
def automatic_facts_are_near_duplicates (
left : dict [ str , Any ] ,
right : dict [ str , Any ] ,
@@ -1335,6 +1411,20 @@ class Ledger:
updated_at TEXT NOT NULL ,
PRIMARY KEY ( group_fingerprint , game_key )
) ;
CREATE TABLE IF NOT EXISTS active_threads (
group_fingerprint TEXT NOT NULL ,
thread_key TEXT NOT NULL ,
topic TEXT NOT NULL ,
actor_person_key TEXT NOT NULL DEFAULT ' ' ,
actor_display_name TEXT NOT NULL DEFAULT ' ' ,
source_event_id TEXT NOT NULL DEFAULT ' ' ,
source_kind TEXT NOT NULL DEFAULT ' ' ,
summary TEXT NOT NULL DEFAULT ' ' ,
created_at TEXT NOT NULL ,
updated_at TEXT NOT NULL ,
expires_at TEXT NOT NULL ,
PRIMARY KEY ( group_fingerprint , thread_key )
) ;
CREATE TABLE IF NOT EXISTS person_observations (
event_id TEXT PRIMARY KEY ,
group_fingerprint TEXT NOT NULL ,
@@ -1599,6 +1689,8 @@ class Ledger:
ON outbox ( state , next_attempt_at ) ;
CREATE INDEX IF NOT EXISTS direct_chat_outbox_due
ON direct_chat_outbox ( state , next_attempt_at ) ;
CREATE INDEX IF NOT EXISTS active_threads_group_expiry
ON active_threads ( group_fingerprint , expires_at ) ;
CREATE INDEX IF NOT EXISTS observations_scope_recent
ON person_observations ( group_fingerprint , person_key , observed_at ) ;
CREATE INDEX IF NOT EXISTS aliases_scope_match
@@ -1842,8 +1934,7 @@ class Ledger:
return cursor . rowcount == 1
def _record_taffy_context ( self , event_id : str , response_text : str ) - > None :
parts = unpack_reply_parts ( response_text )
visible_text = " \n " . join ( parts ) if parts is not None else response_text
visible_text = visible_reply_text ( response_text )
if not visible_text :
return
with self . lock :
@@ -1869,6 +1960,46 @@ class Ledger:
replace = True ,
)
def _activate_thread_from_event ( self , event_id : str , response_text : str ) - > None :
summary = bounded_text ( visible_reply_text ( response_text ) , limit = 700 )
if not summary or summary == FAST_ACKNOWLEDGEMENT :
return
with self . lock :
row = self . connection . execute (
"""
SELECT group_fingerprint , sender , sender_confidence , route ,
route_reason , status
FROM events
WHERE event_id = ?
""" ,
( event_id , ) ,
) . fetchone ( )
if row is None :
return
reason = str ( row [ " route_reason " ] )
if str ( row [ " route " ] ) == " local " and reason in LOCAL_CONTEXT_EXCLUDED_REASONS :
return
actor_person_key = " "
actor_display_name = " "
sender = str ( row [ " sender " ] )
sender_confidence = confidence_value ( row [ " sender_confidence " ] , default = 1.0 )
group = str ( row [ " group_fingerprint " ] )
if trusted_sender_value ( sender , sender_confidence ) :
person = self . resolve_person ( group , sender )
actor_person_key = str ( person [ " person_key " ] )
actor_display_name = person_display_label ( person [ " display_name " ] )
elif sender and sender != " unknown " :
actor_display_name = bounded_text ( sender , limit = 120 )
self . save_active_thread (
group_fingerprint = group ,
topic = thread_topic_label ( str ( row [ " route " ] ) , reason ) ,
summary = summary ,
actor_person_key = actor_person_key ,
actor_display_name = actor_display_name ,
source_event_id = event_id ,
source_kind = f " { row [ ' route ' ] } : { reason } " ,
)
def set_event (
self ,
event_id : str ,
@@ -1893,6 +2024,7 @@ class Ledger:
)
if response_text is not None and status in TRACKED_TAFFY_RESPONSE_STATUSES :
self . _record_taffy_context ( event_id , response_text )
self . _activate_thread_from_event ( event_id , response_text )
def mentioned_person_names (
self ,
@@ -1947,7 +2079,9 @@ class Ledger:
current_question = bounded_text ( question , limit = MAX_EVENT_TEXT )
question_terms = context_terms ( current_question )
followup = bool ( CONTEXT_FOLLOWUP_HINT . search ( current_question ) )
thread_followup = followup or short_thread_reply ( current_question )
self_reference = bool ( CONTEXT_SELF_REFERENCE . search ( current_question ) )
active_thread = self . active_thread ( group ) if thread_followup else None
current_sender_trusted = trusted_sender_value ( sender , sender_confidence )
current_display = (
str ( self . resolve_person ( group , sender ) [ " display_name " ] )
@@ -1997,8 +2131,22 @@ class Ledger:
""" ,
( group , min ( 80 , CONTEXT_CANDIDATE_LIMIT / / 3 ) ) ,
) . fetchall ( )
history_rows = self . connection . execute (
"""
SELECT event_id , display_name , text , observed_at ,
sender_confidence , body_confidence , source
FROM context_history
WHERE group_fingerprint = ?
AND event_id != ?
AND source = ' taffy_response '
ORDER BY observed_at DESC , event_id DESC
LIMIT ?
""" ,
( group , exclude_event_id , min ( 80 , CONTEXT_CANDIDATE_LIMIT / / 3 ) ) ,
) . fetchall ( )
candidates : list [ dict [ str , Any ] ] = [ ]
fallback_recent : list [ dict [ str , Any ] ] = [ ]
event_row_ids = { str ( row [ " event_id " ] ) for row in rows }
for index , row in enumerate ( rows ) :
if (
row [ " route " ] == " local "
@@ -2046,6 +2194,12 @@ class Ledger:
if overlap :
score + = min ( 40 , overlap * 8 )
tags . append ( " term_overlap " )
if (
active_thread is not None
and str ( active_thread . get ( " source_event_id " ) ) == str ( row [ " event_id " ] )
) :
score + = 140
tags . append ( " active_thread " )
if str ( row [ " route " ] ) == " local " :
score + = 18
tags . append ( " local_result " )
@@ -2086,6 +2240,13 @@ class Ledger:
if overlap :
score + = min ( 30 , overlap * 8 )
tags . append ( " term_overlap " )
if (
active_thread is not None
and str ( active_thread . get ( " source_event_id " ) )
== f " clock: { row [ ' slot_key ' ] } "
) :
score + = 140
tags . append ( " active_thread " )
if followup :
score + = 8
candidates . append (
@@ -2105,6 +2266,96 @@ class Ledger:
" tags " : tuple ( tags ) ,
}
)
if not self_reference :
for index , row in enumerate ( history_rows ) :
history_event_id = str ( row [ " event_id " ] )
origin_event_id = (
history_event_id . removeprefix ( " taffy: " )
if history_event_id . startswith ( " taffy: " )
else history_event_id
)
if origin_event_id in event_row_ids :
continue
text = str ( row [ " text " ] )
overlap = len ( question_terms & context_terms ( text ) )
score = max ( 0 , 20 - index )
tags : list [ str ] = [ " context_history " , " taffy_response " ]
if overlap :
score + = min ( 30 , overlap * 8 )
tags . append ( " term_overlap " )
if (
active_thread is not None
and str ( active_thread . get ( " source_event_id " ) ) in {
history_event_id ,
origin_event_id ,
}
) :
score + = 140
tags . append ( " active_thread " )
if followup :
score + = 8
if score < 30 and " active_thread " not in tags :
continue
candidates . append (
{
" source_event_id " : history_event_id ,
" speaker " : (
bounded_text ( row [ " display_name " ] , limit = 80 ) or " Taffy "
) ,
" speaker_trusted " : True ,
" speaker_confidence " : confidence_value (
row [ " sender_confidence " ] ,
default = 1.0 ,
) ,
" user_text " : " " ,
" taffy_text " : text ,
" route " : " history " ,
" route_reason " : str ( row [ " source " ] ) ,
" status " : " recorded " ,
" observed_at " : str ( row [ " observed_at " ] ) ,
" sort_key " : str ( row [ " observed_at " ] ) ,
" score " : score ,
" tags " : tuple ( tags ) ,
}
)
active_source_event = (
str ( active_thread . get ( " source_event_id " , " " ) )
if active_thread is not None
else " "
)
if (
active_thread is not None
and active_source_event
and not self_reference
and all (
str ( candidate [ " source_event_id " ] ) != active_source_event
for candidate in candidates
)
) :
thread_tags = [ " active_thread " , " thread_summary " ]
source_kind = bounded_text ( active_thread . get ( " source_kind " ) , limit = 120 )
if source_kind :
thread_tags . append ( source_kind . replace ( " : " , " _ " ) )
candidates . append (
{
" source_event_id " : f " active: { active_source_event } " ,
" speaker " : (
bounded_text ( active_thread . get ( " actor_display_name " ) , limit = 80 )
or " Taffy "
) ,
" speaker_trusted " : True ,
" speaker_confidence " : 1.0 ,
" user_text " : " " ,
" taffy_text " : str ( active_thread . get ( " summary " , " " ) ) ,
" route " : " thread " ,
" route_reason " : str ( active_thread . get ( " topic " , " " ) ) ,
" status " : " active " ,
" observed_at " : str ( active_thread . get ( " updated_at " , " " ) ) ,
" sort_key " : str ( active_thread . get ( " updated_at " , " " ) ) ,
" score " : 220 ,
" tags " : tuple ( thread_tags ) ,
}
)
selected : list [ dict [ str , Any ] ] = [ ]
seen : set [ str ] = set ( )
for entry in sorted (
@@ -3521,6 +3772,97 @@ class Ledger:
) . fetchone ( )
return str ( row [ " value " ] ) if row else " "
def save_active_thread (
self ,
* ,
group_fingerprint : str ,
topic : str ,
summary : str ,
actor_person_key : str = " " ,
actor_display_name : str = " " ,
source_event_id : str = " " ,
source_kind : str = " " ,
thread_key : str = " latest " ,
ttl_seconds : int = ACTIVE_THREAD_TTL_SECONDS ,
) - > None :
group = bounded_text ( group_fingerprint , limit = 256 )
thread = bounded_text ( thread_key , limit = 80 ) or " latest "
text = bounded_text ( summary , limit = 700 )
if not group or not text :
return
now = datetime . now ( timezone . utc )
timestamp = now . isoformat ( timespec = " milliseconds " )
expires_at = ( now + timedelta ( seconds = ttl_seconds ) ) . isoformat (
timespec = " milliseconds "
)
with self . lock , self . connection :
self . connection . execute (
"""
INSERT INTO active_threads (
group_fingerprint , thread_key , topic , actor_person_key ,
actor_display_name , source_event_id , source_kind , summary ,
created_at , updated_at , expires_at
) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )
ON CONFLICT ( group_fingerprint , thread_key ) DO UPDATE
SET topic = excluded . topic ,
actor_person_key = excluded . actor_person_key ,
actor_display_name = excluded . actor_display_name ,
source_event_id = excluded . source_event_id ,
source_kind = excluded . source_kind ,
summary = excluded . summary ,
updated_at = excluded . updated_at ,
expires_at = excluded . expires_at
""" ,
(
group ,
thread ,
bounded_text ( topic , limit = 120 ) ,
bounded_text ( actor_person_key , limit = 128 ) ,
bounded_text ( actor_display_name , limit = 120 ) ,
bounded_text ( source_event_id , limit = 256 ) ,
bounded_text ( source_kind , limit = 120 ) ,
text ,
timestamp ,
timestamp ,
expires_at ,
) ,
)
def active_thread (
self ,
group_fingerprint : str ,
* ,
thread_key : str = " latest " ,
now : datetime | None = None ,
) - > dict [ str , Any ] | None :
group = bounded_text ( group_fingerprint , limit = 256 )
thread = bounded_text ( thread_key , limit = 80 ) or " latest "
if not group :
return None
current = now or datetime . now ( timezone . utc )
timestamp = current . astimezone ( timezone . utc ) . isoformat (
timespec = " milliseconds "
)
with self . lock , self . connection :
self . connection . execute (
"""
DELETE FROM active_threads
WHERE group_fingerprint = ? AND expires_at < = ?
""" ,
( group , timestamp ) ,
)
row = self . connection . execute (
"""
SELECT *
FROM active_threads
WHERE group_fingerprint = ? AND thread_key = ?
AND expires_at > ?
LIMIT 1
""" ,
( group , thread , timestamp ) ,
) . fetchone ( )
return dict ( row ) if row else None
def recent_clock_broadcast (
self ,
group_fingerprint : str ,
@@ -3775,6 +4117,14 @@ class Ledger:
source = f " clock_broadcast: { kind [ : 48 ] } " ,
replace = True ,
)
self . save_active_thread (
group_fingerprint = group_fingerprint ,
topic = thread_topic_label ( " broadcast " , kind ) ,
summary = message ,
actor_display_name = " Taffy " ,
source_event_id = f " clock: { slot_key } " ,
source_kind = f " clock_broadcast: { kind [ : 48 ] } " ,
)
return True
def fail_clock_broadcast (
@@ -4248,6 +4598,230 @@ class Ledger:
) ,
)
def _local_status_subject (
self ,
group : str ,
subject : str ,
* ,
sender : str = " " ,
sender_confidence : float = 1.0 ,
) - > dict [ str , str ] | None :
normalized = normalize_text ( subject )
if not normalized :
return None
if normalized in { " 我 " , " 本人 " , " 自己 " } :
if not trusted_sender_value ( sender , sender_confidence ) :
return None
person = self . resolve_person ( group , sender )
return {
" person_key " : str ( person [ " person_key " ] ) ,
" display_name " : person_display_label ( person [ " display_name " ] ) ,
}
if normalized . casefold ( ) in { " 你 " , " taffy " , " jwebot " , BOT_MENTION . casefold ( ) } :
return None
self . seed_person_aliases ( group )
subject_key = person_alias_key ( normalized )
with self . lock :
rows = self . connection . execute (
"""
SELECT alias_key , match_kind , canonical_name
FROM person_aliases
WHERE group_fingerprint = ?
ORDER BY length ( alias_key ) DESC , match_kind
""" ,
( group , ) ,
) . fetchall ( )
for row in rows :
if not alias_is_mentioned (
subject_key ,
str ( row [ " alias_key " ] ) ,
str ( row [ " match_kind " ] ) ,
) :
continue
canonical = str ( row [ " canonical_name " ] )
return {
" person_key " : person_scope_key ( group , canonical ) ,
" display_name " : person_display_label ( canonical ) ,
}
return None
@staticmethod
def _active_single_blackjack_game ( account : dict [ str , Any ] ) - > dict [ str , Any ] | None :
game = account . get ( " game " )
if not isinstance ( game , dict ) :
return None
try :
updated_at = float ( game . get ( " updated_at " , 0 ) )
except ( TypeError , ValueError ) :
return None
if time . time ( ) - updated_at > = LOCAL_GAME_TIMEOUT_SECONDS :
return None
return game
def _single_blackjack_status (
self ,
group : str ,
person_key : str ,
display_name : str ,
) - > str | None :
with self . lock :
account = self . _load_player_account ( group , person_key )
game = self . _active_single_blackjack_game ( account )
if game is None :
return None
dealer = game . get ( " dealer " , [ ] )
dealer_text = (
f " { card_label ( int ( dealer [ 0 ] ) ) } + 暗牌 "
if isinstance ( dealer , list ) and dealer
else " 未知 "
)
try :
index = int ( game . get ( " index " , 0 ) )
bet = int ( game . get ( " bet " , 0 ) )
except ( TypeError , ValueError ) :
index = 0
bet = 0
suffix = f " # { index } " if index > 0 else " "
return (
f " { display_name } 在玩单人21点 { suffix } : "
f " 手牌 { blackjack_hand_label ( game . get ( ' player ' ) ) } ; "
f " 庄家 { dealer_text } ;下注 { bet } ; "
" 可 /21点 要牌 或 /21点 停牌 "
)
def _multiplayer_blackjack_status (
self ,
group : str ,
person_key : str ,
display_name : str ,
) - > str | None :
with self . lock :
table = self . _open_group_blackjack ( group )
if table is None :
return None
players = self . _group_blackjack_players ( group , int ( table [ " table_number " ] ) )
player = self . _blackjack_entry ( players , person_key )
if player is None :
return None
state = str ( table [ " state " ] )
table_number = int ( table [ " table_number " ] )
prompt = self . _blackjack_entry ( players , str ( table [ " turn_person_key " ] ) )
prompt_text = f " ;当前轮到 { prompt [ ' display_name ' ] } " if prompt else " "
if state == " joining " :
names = " 、 " . join ( str ( item [ " display_name " ] ) for item in players )
return (
f " { display_name } 在多人21点第 { table_number } 桌,正在等人加入; "
f " 当前玩家: { names } "
)
if state == " betting " :
bet = int ( player [ " bet " ] )
bet_text = f " 已放 { bet } " if bet > 0 else " 还没放筹码 "
return (
f " { display_name } 在多人21点第 { table_number } 桌,下注阶段; "
f " { display_name } { bet_text } { prompt_text } "
)
if state == " playing " :
return (
f " { display_name } 在多人21点第 { table_number } 桌,出牌阶段; "
f " 手牌 { blackjack_hand_label ( player . get ( ' hand ' ) ) } ; "
f " 下注 { int ( player [ ' bet ' ] ) } ;状态 { player [ ' state ' ] } { prompt_text } "
)
return None
def _active_local_game_lines ( self , group : str ) - > list [ str ] :
directory = {
str ( person [ " person_key " ] ) : str ( person [ " display_name " ] )
for person in self . person_directory ( group )
}
lines : list [ str ] = [ ]
with self . lock :
account_rows = self . connection . execute (
"""
SELECT person_key , state_json
FROM player_accounts
WHERE group_fingerprint = ?
ORDER BY updated_at DESC
LIMIT 80
""" ,
( group , ) ,
) . fetchall ( )
table = self . _open_group_blackjack ( group )
players = (
self . _group_blackjack_players ( group , int ( table [ " table_number " ] ) )
if table is not None
else [ ]
)
for row in account_rows :
try :
account = normalize_player_account (
json . loads ( str ( row [ " state_json " ] ) )
)
except ( json . JSONDecodeError , TypeError , ValueError ) :
continue
game = self . _active_single_blackjack_game ( account )
if game is None :
continue
person_key = str ( row [ " person_key " ] )
display_name = directory . get ( person_key , " 有人 " )
index = int ( game . get ( " index " , 0 ) or 0 )
index_text = f " # { index } " if index > 0 else " "
lines . append (
f " { display_name } 单人21点 { index_text } , "
f " 手牌 { blackjack_hand_label ( game . get ( ' player ' ) ) } "
)
if table is not None and players :
state = str ( table [ " state " ] )
state_text = {
" joining " : " 等人加入 " ,
" betting " : " 下注阶段 " ,
" playing " : " 出牌阶段 " ,
} . get ( state , state )
names = " 、 " . join ( str ( player [ " display_name " ] ) for player in players )
lines . append (
f " 多人21点第 { int ( table [ ' table_number ' ] ) } 桌 { state_text } , "
f " 玩家: { names } "
)
return lines
def local_game_status_reply (
self ,
group : str ,
question : str ,
* ,
sender : str = " " ,
sender_confidence : float = 1.0 ,
) - > str | None :
current = bounded_text ( question , limit = MAX_EVENT_TEXT )
if LOCAL_PLAY_LIST_QUESTION . fullmatch ( current ) :
lines = self . _active_local_game_lines ( group )
if not lines :
return None
return clean_reply ( " 现在本地游戏里: " + " ; " . join ( lines ) )
match = LOCAL_PLAY_STATUS_QUESTION . fullmatch ( current )
if match is None :
return None
person = self . _local_status_subject (
group ,
match . group ( " subject " ) ,
sender = sender ,
sender_confidence = sender_confidence ,
)
if person is None :
return None
person_key = str ( person [ " person_key " ] )
display_name = str ( person [ " display_name " ] )
lines = [
line
for line in (
self . _single_blackjack_status ( group , person_key , display_name ) ,
self . _multiplayer_blackjack_status ( group , person_key , display_name ) ,
)
if line
]
if not lines :
return None
return clean_reply ( " ; " . join ( lines ) )
def _store_player_action (
self ,
group : str ,
@@ -7714,7 +8288,10 @@ class DeepSeekClient:
" STRUCTURED_RECENT_GROUP_CONTEXT 是多人群聊记录,speaker 不同就不是 "
" 同一个人;遇到“我刚才、我的、我上一把”这类自指问题,只引用 "
" CURRENT_GROUP_SENDER_LABEL 对应的上下文。点名别人时才引用被点名者。 "
" 上下文不足或指代不清时,短句追问“你说哪件?” "
" 标记为 local_result 或 active_thread 的内容是本地账本或当前线程事实, "
" 必须优先采用,不要再回答“不知道”“去问他”或给出相反结论。 "
" 用户只发短句评价时,优先接 active_thread 继续聊。 "
" 只有多个候选明显冲突且无法判断时,才短句追问“你说哪件?” "
) ,
}
]
@@ -8760,6 +9337,37 @@ class EdgeApplication:
if parts is not None :
response [ " texts " ] = parts
return response
local_fact_reply = self . ledger . local_game_status_reply (
str ( row [ " group_fingerprint " ] ) ,
str ( row [ " question " ] ) ,
sender = str ( row [ " sender " ] ) ,
sender_confidence = confidence_value (
row . get ( " sender_confidence " ) ,
default = 1.0 ,
) ,
)
if local_fact_reply :
self . ledger . set_event (
str ( row [ " event_id " ] ) ,
status = " chat_completed " ,
response_text = local_fact_reply ,
)
self . telemetry . elapsed (
" edge.local_fact.complete " ,
started ,
event_id = event_id ,
group_id = group_id ,
route = route ,
reason = " local_game_status " ,
execution_path = ExecutionPath . LOCAL_COMMAND . value ,
chars = len ( local_fact_reply ) ,
)
return {
" status " : " reply " ,
" route " : route ,
" execution_path " : ExecutionPath . LOCAL_COMMAND . value ,
" text " : local_fact_reply ,
}
alias_reply = self . ledger . local_alias_identity_reply (
str ( row [ " group_fingerprint " ] ) ,
str ( row [ " question " ] ) ,