524 lines
12 KiB
Go
524 lines
12 KiB
Go
// 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)
|
||
}
|