- inference.py(/chat/legacy 热路径):kb.match 循环不变量外提(原每步全量重扫+重排序, 最坏 O(steps×rules×patterns));fired 查重 list→set - knowledge.py:Rule patterns 注册侧懒缓存小写副本(原每条规则每次匹配重复 lower); match() 文本只 lower 一次(原逐规则重复);load() 的 yaml 文件名集合提到循环外 - worker.py:本地端点生成器 httpx.AsyncClient 懒建复用(原每步新建/销毁连接, 对齐 ArchitectClient 惯用法;协作循环最多 10 次生成免重复建连) - pipeline.py(协作循环):plan_by_id O(1) 步定义查找;done 集合增量维护 (原每轮重建 progress+archive 扫描);领域只解析一次(原 _artifact_name 每步 全领域 kb.match);_deps_done 支持传入预填集合(保持旧签名兼容) - v2stats.py:回合数分布改增量聚合(sum/max/分桶计数),summary() O(n)→O(1), 不再持有无界 list(修长时运行内存增长) - gateway/agent.py + api.py:AgentService 运行计数 O(1) 化(原 register 全量扫描), 状态迁移收敛到 _transition_state 单一入口(api.py cancel/异常两处绕过点一并接入, 消除计数与状态脱节隐患);21 项 agent 测试全绿(两轮全量 230 passed 复核)
100 lines
4.3 KiB
Python
100 lines
4.3 KiB
Python
"""前向链推理机:知识库规则驱动的工作记忆演化(专家系统推理核心,零依赖)。
|
||
|
||
流程(经典前向链 forward chaining):
|
||
1. 初始化黑板:写入领域/难度/置信度等事实
|
||
2. 循环:在领域内匹配规则(未触发过的)→ 按优先级执行
|
||
- 命中即记录轨迹 rule:<id>@<priority>
|
||
- 规则带 output 模板 → 渲染后写入黑板章节(部分解)
|
||
- 规则带 actions → 执行动作(写事实/写章节)
|
||
3. 终止:无新规则可触发 / 达到步数上限(防死循环)
|
||
|
||
确定性保证:规则匹配基于子串包含,无随机性;同输入 → 同轨迹。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from .knowledge import KnowledgeBase, Rule
|
||
from .memory import WorkingMemory
|
||
|
||
|
||
def render_template(template: str, query: str, facts: Dict[str, Any]) -> str:
|
||
"""渲染输出模板:替换 {query} 与 {facts.<key>} 占位符;缺失以 [未提供] 占位,不抛异常。"""
|
||
out = template.replace("{query}", query)
|
||
for key, value in facts.items():
|
||
out = out.replace(f"{{facts.{key}}}", str(value))
|
||
# 剩余占位符兜底
|
||
while "{" in out and "}" in out:
|
||
start = out.find("{")
|
||
end = out.find("}", start)
|
||
if end == -1:
|
||
break
|
||
out = out[:start] + "[未提供]" + out[end + 1:]
|
||
return out
|
||
|
||
|
||
class InferenceEngine:
|
||
"""前向链推理机。"""
|
||
|
||
def __init__(self, kb: KnowledgeBase, max_steps: int = 20):
|
||
self.kb = kb
|
||
self.max_steps = max_steps
|
||
|
||
# ---------------------------------------------------------------
|
||
def initialize(self, query: str, domain: str, difficulty: str,
|
||
confidence: float, memory: WorkingMemory) -> None:
|
||
"""把分类结果写入黑板(事实初始化)。"""
|
||
memory.write_fact("query", query)
|
||
memory.write_fact("domain", domain)
|
||
memory.write_fact("difficulty", difficulty)
|
||
memory.write_fact("confidence", round(confidence, 4))
|
||
memory.add_trace(f"init:domain={domain},difficulty={difficulty},conf={confidence:.2f}")
|
||
|
||
# ---------------------------------------------------------------
|
||
def run(self, query: str, domain: str, memory: WorkingMemory,
|
||
max_steps: Optional[int] = None) -> List[str]:
|
||
"""前向链主循环。返回触发规则 id 列表(按触发顺序)。
|
||
|
||
循环不变量外提:query/domain 在循环内不变,kb.match 结果只算一次
|
||
(原实现每步全量重扫+重排序,最坏 O(steps × rules × patterns))。
|
||
"""
|
||
steps = max_steps or self.max_steps
|
||
rules = self.kb.match(query, domain=domain)
|
||
fired: List[str] = []
|
||
fired_set: set = set() # O(1) 查重(fired 保持列表维护触发顺序)
|
||
for _ in range(steps):
|
||
# 选第一个"未触发过"的规则
|
||
target: Optional[Rule] = None
|
||
for r in rules:
|
||
if r.id not in fired_set:
|
||
target = r
|
||
break
|
||
if target is None:
|
||
break # 无新规则可触发 → 终止
|
||
fired.append(target.id)
|
||
fired_set.add(target.id)
|
||
self._fire(target, query, memory)
|
||
return fired
|
||
|
||
# ---------------------------------------------------------------
|
||
def _fire(self, rule: Rule, query: str, memory: WorkingMemory) -> None:
|
||
"""执行一条规则:记录轨迹 + 写事实 + 产出章节。"""
|
||
memory.add_trace(f"rule:{rule.id}@{rule.priority}")
|
||
# 规则动作
|
||
for action in rule.actions:
|
||
self._apply_action(action, rule, query, memory)
|
||
# 规则输出模板 → 章节
|
||
if rule.output:
|
||
text = render_template(rule.output, query, memory.facts)
|
||
memory.write_section(rule.id, text)
|
||
|
||
def _apply_action(self, action: str, rule: Rule, query: str,
|
||
memory: WorkingMemory) -> None:
|
||
"""动作格式:write_fact:key=value(value 支持 {query} 占位)。"""
|
||
if action.startswith("write_fact:"):
|
||
kv = action[len("write_fact:"):]
|
||
key, _, value = kv.partition("=")
|
||
value = value.replace("{query}", query)
|
||
memory.write_fact(key.strip(), value.strip(), rule_id=rule.id)
|
||
# 其他动作类型暂不实现(保留扩展位)
|