Files
synapse/internal/components/speculum/speculum.go
2026-06-28 03:24:26 +08:00

524 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package speculum 灵鉴 - 共识与裁决层
//
// 古有"以史为鉴",今有"以智为鉴"。
// 此层不是粗暴的少数服从多数,而是让各方 Agent
// 将推理过程"照"进公共镜面,通过对比逻辑链的
// 严密性来达成共识,如青天明镜,照破虚妄。
package speculum
import (
"context"
"encoding/json"
"fmt"
"log"
"sync"
"time"
)
// =============================================================================
// Consensus Engine - 共识引擎
// =============================================================================
// ConsensusType 共识类型
type ConsensusType string
const (
ConsensusMajority ConsensusType = "majority" // 多数投票
ConsensusSupermajority ConsensusType = "supermajority" // 绝对多数(2/3)
ConsensusUnanimity ConsensusType = "unanimity" // 一致同意
ConsensusWeighted ConsensusType = "weighted" // 加权投票
ConsensusRanked ConsensusType = "ranked" // 排序投票
)
// VoteOption 投票选项
type VoteOption struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// Vote 投票
type Vote struct {
VoteID string
Question string
Options []VoteOption
Type ConsensusType
Initiator string
Participants []string
Votes map[string]string // voter_id -> option_id
Weights map[string]float64 // voter_id -> 权重
Status VoteStatus
Deadline time.Time
Result *VoteResult
Reasoning map[string]string // voter_id -> 推理理由
}
// VoteStatus 投票状态
type VoteStatus string
const (
VotePending VoteStatus = "pending"
VoteActive VoteStatus = "active"
VoteClosed VoteStatus = "closed"
VoteTied VoteStatus = "tied"
)
// VoteResult 投票结果
type VoteResult struct {
Winner string
WinOption VoteOption
VoteCounts map[string]int
Passed bool
Margin float64
}
// ConsensusEngine 共识引擎
type ConsensusEngine struct {
mu sync.RWMutex
votes map[string]*Vote
config *ConsensusConfig
}
// ConsensusConfig 共识配置
type ConsensusConfig struct {
DefaultType ConsensusType
DefaultTimeout time.Duration
QuorumSize int
}
// DefaultConsensusConfig 返回默认共识配置
func DefaultConsensusConfig() *ConsensusConfig {
return &ConsensusConfig{
DefaultType: ConsensusMajority,
DefaultTimeout: 5 * time.Minute,
QuorumSize: 1,
}
}
// NewConsensusEngine 创建共识引擎
func NewConsensusEngine(config *ConsensusConfig) *ConsensusEngine {
if config == nil {
config = DefaultConsensusConfig()
}
return &ConsensusEngine{
votes: make(map[string]*Vote),
config: config,
}
}
// InitiateVote 发起投票
func (ce *ConsensusEngine) InitiateVote(
voteID, question string,
options []VoteOption,
initiator string,
participants []string,
consensusType ConsensusType,
deadline time.Time,
) (*Vote, error) {
if voteID == "" {
voteID = fmt.Sprintf("vote-%d", time.Now().UnixNano())
}
if consensusType == "" {
consensusType = ce.config.DefaultType
}
if deadline.IsZero() {
deadline = time.Now().Add(ce.config.DefaultTimeout)
}
ce.mu.Lock()
defer ce.mu.Unlock()
vote := &Vote{
VoteID: voteID,
Question: question,
Options: options,
Type: consensusType,
Initiator: initiator,
Participants: participants,
Votes: make(map[string]string),
Weights: make(map[string]float64),
Status: VoteActive,
Deadline: deadline,
Reasoning: make(map[string]string),
}
// 默认权重相同
for _, p := range participants {
vote.Weights[p] = 1.0
}
ce.votes[voteID] = vote
log.Printf("[灵鉴] 发起投票: %s (发起者: %s, 类型: %s, 参与者: %d人)",
voteID, initiator, consensusType, len(participants))
return vote, nil
}
// SubmitVote 提交投票
func (ce *ConsensusEngine) SubmitVote(voteID, voterID, optionID, reasoning string) error {
ce.mu.Lock()
defer ce.mu.Unlock()
vote, ok := ce.votes[voteID]
if !ok {
return fmt.Errorf("vote %s not found", voteID)
}
if vote.Status != VoteActive {
return fmt.Errorf("vote is not active (status: %s)", vote.Status)
}
// 检查是否是参与者
isParticipant := false
for _, p := range vote.Participants {
if p == voterID {
isParticipant = true
break
}
}
if !isParticipant {
return fmt.Errorf("voter %s is not a participant", voterID)
}
vote.Votes[voterID] = optionID
if reasoning != "" {
vote.Reasoning[voterID] = reasoning
}
log.Printf("[灵鉴] %s 投票: %s -> %s", voterID, voteID, optionID)
// 检查是否所有参与者都已投票,可以提前结束
if len(vote.Votes) >= len(vote.Participants) {
ce.countVotesLocked(vote)
}
return nil
}
// CloseVote 关闭投票并统计结果
func (ce *ConsensusEngine) CloseVote(voteID string) (*VoteResult, error) {
ce.mu.Lock()
defer ce.mu.Unlock()
vote, ok := ce.votes[voteID]
if !ok {
return nil, fmt.Errorf("vote %s not found", voteID)
}
if vote.Status != VoteActive {
return vote.Result, nil
}
return ce.countVotesLocked(vote), nil
}
// countVotesLocked 统计投票(调用者需持有锁)
func (ce *ConsensusEngine) countVotesLocked(vote *Vote) *VoteResult {
vote.Status = VoteClosed
// 统计票数
voteCounts := make(map[string]int)
for _, optID := range vote.Votes {
voteCounts[optID]++
}
// 找出获胜者
var winner string
maxVotes := 0
for optID, count := range voteCounts {
if count > maxVotes {
maxVotes = count
winner = optID
}
}
// 计算是否通过
totalVotes := len(vote.Votes)
required := ce.getRequiredVotes(vote, totalVotes)
passed := maxVotes >= required && totalVotes >= ce.config.QuorumSize
// 查找获胜选项
var winOption VoteOption
for _, opt := range vote.Options {
if opt.ID == winner {
winOption = opt
break
}
}
result := &VoteResult{
Winner: winner,
WinOption: winOption,
VoteCounts: voteCounts,
Passed: passed,
Margin: float64(maxVotes) / float64(totalVotes),
}
vote.Result = result
log.Printf("[灵鉴] 投票结果: %s -> %s (通过: %v, 得票率: %.1f%%)",
vote.VoteID, winner, passed, result.Margin*100)
return result
}
// getRequiredVotes 获取所需票数
func (ce *ConsensusEngine) getRequiredVotes(vote *Vote, total int) int {
switch vote.Type {
case ConsensusUnanimity:
return total
case ConsensusSupermajority:
return (total*2)/3 + 1
case ConsensusMajority:
return total/2 + 1
default:
return total/2 + 1
}
}
// GetVote 获取投票信息
func (ce *ConsensusEngine) GetVote(voteID string) (*Vote, error) {
ce.mu.RLock()
defer ce.mu.RUnlock()
vote, ok := ce.votes[voteID]
if !ok {
return nil, fmt.Errorf("vote %s not found", voteID)
}
return vote, nil
}
// GetAllVotes 获取所有投票
func (ce *ConsensusEngine) GetAllVotes() []*Vote {
ce.mu.RLock()
defer ce.mu.RUnlock()
votes := make([]*Vote, 0, len(ce.votes))
for _, v := range ce.votes {
votes = append(votes, v)
}
return votes
}
// =============================================================================
// Arbitration - 仲裁机制
// =============================================================================
// Arbitrator 仲裁者
type Arbitrator struct {
consensusEngine *ConsensusEngine
rules []ArbitrationRule
mu sync.RWMutex
}
// ArbitrationRule 仲裁规则
type ArbitrationRule struct {
ID string
Name string
Description string
Priority int
Evaluate func(ctx context.Context, dispute *Dispute) (string, error)
}
// Dispute 争议
type Dispute struct {
DisputeID string
Topic string
Description string
Parties []string
Evidence map[string][]byte
SubmittedAt time.Time
}
// ArbitrationResult 仲裁结果
type ArbitrationResult struct {
DisputeID string
Winner string
Reason string
Evidence []string
DecidedAt time.Time
}
// NewArbitrator 创建仲裁者
func NewArbitrator(consensusEngine *ConsensusEngine) *Arbitrator {
return &Arbitrator{
consensusEngine: consensusEngine,
rules: make([]ArbitrationRule, 0),
}
}
// AddRule 添加仲裁规则
func (a *Arbitrator) AddRule(rule ArbitrationRule) {
a.mu.Lock()
defer a.mu.Unlock()
a.rules = append(a.rules, rule)
}
// Arbitrate 仲裁
func (a *Arbitrator) Arbitrate(ctx context.Context, dispute *Dispute) (*ArbitrationResult, error) {
a.mu.RLock()
defer a.mu.RUnlock()
log.Printf("[灵鉴] 开始仲裁: %s (%s)", dispute.DisputeID, dispute.Topic)
// 按优先级执行规则
for _, rule := range a.rules {
result, err := rule.Evaluate(ctx, dispute)
if err != nil {
log.Printf("[灵鉴] 规则 %s 执行失败: %v", rule.Name, err)
continue
}
if result != "" {
return &ArbitrationResult{
DisputeID: dispute.DisputeID,
Winner: result,
Reason: fmt.Sprintf("规则 %s 判定", rule.Name),
DecidedAt: time.Now(),
}, nil
}
}
// 如果没有规则匹配,使用投票
return a.arbitrateByVote(ctx, dispute)
}
// arbitrateByVote 通过投票仲裁
func (a *Arbitrator) arbitrateByVote(ctx context.Context, dispute *Dispute) (*ArbitrationResult, error) {
options := make([]VoteOption, len(dispute.Parties))
for i, p := range dispute.Parties {
options[i] = VoteOption{
ID: p,
Title: p,
}
}
vote, err := a.consensusEngine.InitiateVote(
fmt.Sprintf("arbitration-%s", dispute.DisputeID),
dispute.Topic,
options,
"arbitrator",
dispute.Parties,
ConsensusMajority,
time.Now().Add(5*time.Minute),
)
if err != nil {
return nil, err
}
result, err := a.consensusEngine.CloseVote(vote.VoteID)
if err != nil {
return nil, err
}
return &ArbitrationResult{
DisputeID: dispute.DisputeID,
Winner: result.Winner,
Reason: "投票裁决",
DecidedAt: time.Now(),
}, nil
}
// =============================================================================
// Reasoning Mirror - 推理镜面
// =============================================================================
// ReasoningMirror 推理镜面
// 让各方 Agent 将推理过程"照"进公共镜面,
// 通过对比逻辑链的严密性来达成共识
type ReasoningMirror struct {
mu sync.RWMutex
reasonings map[string]*ReasoningChain // chain_id -> 推理链
}
// ReasoningChain 推理链
type ReasoningChain struct {
ChainID string
AgentID string
Topic string
Steps []ReasoningStep
Score float64
SubmittedAt time.Time
}
// ReasoningStep 推理步骤
type ReasoningStep struct {
StepID string
Description string
Evidence []string
Confidence float64
Next []string // 下一步的ID
}
// NewReasoningMirror 创建推理镜面
func NewReasoningMirror() *ReasoningMirror {
return &ReasoningMirror{
reasonings: make(map[string]*ReasoningChain),
}
}
// SubmitReasoning 提交推理链
func (rm *ReasoningMirror) SubmitReasoning(chain *ReasoningChain) error {
if chain == nil {
return fmt.Errorf("reasoning chain is nil")
}
rm.mu.Lock()
defer rm.mu.Unlock()
rm.reasonings[chain.ChainID] = chain
log.Printf("[灵鉴] %s 提交推理链: %s (步骤: %d)", chain.AgentID, chain.Topic, len(chain.Steps))
return nil
}
// CompareReasonings 比较推理链的严密性
func (rm *ReasoningMirror) CompareReasonings(chainIDs []string) (string, error) {
rm.mu.RLock()
defer rm.mu.RUnlock()
var bestChain string
bestScore := -1.0
for _, id := range chainIDs {
chain, ok := rm.reasonings[id]
if !ok {
continue
}
// 计算推理链得分(简化:平均置信度 * 步骤数权重)
if chain.Score > bestScore {
bestScore = chain.Score
bestChain = id
}
}
if bestChain == "" {
return "", fmt.Errorf("no valid reasoning chains found")
}
log.Printf("[灵鉴] 推理比较胜出: %s (得分: %.2f)", bestChain, bestScore)
return bestChain, nil
}
// GetReasoning 获取推理链
func (rm *ReasoningMirror) GetReasoning(chainID string) (*ReasoningChain, error) {
rm.mu.RLock()
defer rm.mu.RUnlock()
chain, ok := rm.reasonings[chainID]
if !ok {
return nil, fmt.Errorf("reasoning chain %s not found", chainID)
}
return chain, nil
}
// ToJSON 导出为 JSON
func (v *Vote) ToJSON() string {
data, _ := json.MarshalIndent(v, "", " ")
return string(data)
}