agent-relationship 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_relationship/__init__.py +41 -0
- agent_relationship/__pycache__/__init__.cpython-311.pyc +0 -0
- agent_relationship/__pycache__/engine.cpython-311.pyc +0 -0
- agent_relationship/__pycache__/models.cpython-311.pyc +0 -0
- agent_relationship/__pycache__/repair.cpython-311.pyc +0 -0
- agent_relationship/__pycache__/tracker.cpython-311.pyc +0 -0
- agent_relationship/__pycache__/types.cpython-311.pyc +0 -0
- agent_relationship/engine.py +326 -0
- agent_relationship/llm/__init__.py +17 -0
- agent_relationship/llm/__pycache__/__init__.cpython-311.pyc +0 -0
- agent_relationship/llm/__pycache__/anthropic.cpython-311.pyc +0 -0
- agent_relationship/llm/__pycache__/base.cpython-311.pyc +0 -0
- agent_relationship/llm/__pycache__/deepseek.cpython-311.pyc +0 -0
- agent_relationship/llm/__pycache__/mock.cpython-311.pyc +0 -0
- agent_relationship/llm/__pycache__/openai.cpython-311.pyc +0 -0
- agent_relationship/llm/anthropic.py +108 -0
- agent_relationship/llm/base.py +91 -0
- agent_relationship/llm/deepseek.py +51 -0
- agent_relationship/llm/mock.py +63 -0
- agent_relationship/llm/openai.py +83 -0
- agent_relationship/models.py +191 -0
- agent_relationship/repair.py +77 -0
- agent_relationship/tests/__init__.py +0 -0
- agent_relationship/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- agent_relationship/tests/__pycache__/test_core.cpython-311-pytest-9.0.2.pyc +0 -0
- agent_relationship/tests/test_core.py +684 -0
- agent_relationship/tracker.py +453 -0
- agent_relationship/types.py +150 -0
- agent_relationship-0.2.0.dist-info/METADATA +421 -0
- agent_relationship-0.2.0.dist-info/RECORD +33 -0
- agent_relationship-0.2.0.dist-info/WHEEL +5 -0
- agent_relationship-0.2.0.dist-info/licenses/LICENSE +21 -0
- agent_relationship-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Relationship — 多 Agent 系统的关系感知 Skill。
|
|
3
|
+
|
|
4
|
+
给任何多 Agent 系统注入「社会层」:关系建模、Moloch 检测、关系修复。
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
from agent_relationship import RelationshipTracker
|
|
8
|
+
|
|
9
|
+
tracker = RelationshipTracker()
|
|
10
|
+
tracker.track("alice", "bob", {"action": "help", "result": "success"})
|
|
11
|
+
h = tracker.health("alice", "bob")
|
|
12
|
+
print(tracker.summary())
|
|
13
|
+
|
|
14
|
+
四引擎:
|
|
15
|
+
- "mock" — 零配置,确定性模拟
|
|
16
|
+
- "openai" — GPT / Codex
|
|
17
|
+
- "deepseek" — V4 Flash / V4 Pro
|
|
18
|
+
- "anthropic" — Claude / Claude Code
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .tracker import RelationshipTracker
|
|
22
|
+
from .types import (
|
|
23
|
+
TrackResult,
|
|
24
|
+
Health,
|
|
25
|
+
NetworkReport,
|
|
26
|
+
MolochReport,
|
|
27
|
+
MolochZone,
|
|
28
|
+
RepairPath,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"RelationshipTracker",
|
|
33
|
+
"TrackResult",
|
|
34
|
+
"Health",
|
|
35
|
+
"NetworkReport",
|
|
36
|
+
"MolochReport",
|
|
37
|
+
"MolochZone",
|
|
38
|
+
"RepairPath",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
__version__ = "0.2.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Relationship — 关系引擎
|
|
3
|
+
|
|
4
|
+
管理所有 Agent 之间的关系场:记录交互、评估健康、检测危机。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
9
|
+
|
|
10
|
+
from .models import RelationProfile, InteractionRecord
|
|
11
|
+
from .repair import RepairMechanism
|
|
12
|
+
from .llm import LLMBackend
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RelationshipEngine:
|
|
16
|
+
"""
|
|
17
|
+
关系引擎 — 多 Agent 系统的社会层。
|
|
18
|
+
|
|
19
|
+
核心功能:
|
|
20
|
+
- 记录交互 → 更新 balance / trust / type
|
|
21
|
+
- 查询双边关系健康度
|
|
22
|
+
- Moloch 竞争升级检测 (BFS 连通分量 + 趋势)
|
|
23
|
+
- 关系修复路径
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, llm: LLMBackend, max_history: int = 100):
|
|
27
|
+
self.llm = llm
|
|
28
|
+
self.profiles: Dict[Tuple[str, str], RelationProfile] = {}
|
|
29
|
+
self.repair = RepairMechanism()
|
|
30
|
+
self.moloch_threshold: float = 0.3
|
|
31
|
+
self.moloch_min_zone_size: int = 3
|
|
32
|
+
self._previous_moloch_zones: List[Dict] = []
|
|
33
|
+
self.max_history = max_history
|
|
34
|
+
|
|
35
|
+
def _key(self, a: str, b: str) -> Tuple[str, str]:
|
|
36
|
+
"""规范化键 — (a,b) 和 (b,a) 指向同一 profile"""
|
|
37
|
+
return (a, b) if a < b else (b, a)
|
|
38
|
+
|
|
39
|
+
# ── 查询 ──
|
|
40
|
+
|
|
41
|
+
def get_relationship(
|
|
42
|
+
self, agent_a: str, agent_b: str
|
|
43
|
+
) -> Optional[RelationProfile]:
|
|
44
|
+
"""查询双边关系"""
|
|
45
|
+
return self.profiles.get(self._key(agent_a, agent_b))
|
|
46
|
+
|
|
47
|
+
def get_or_create_relationship(
|
|
48
|
+
self, agent_a: str, agent_b: str
|
|
49
|
+
) -> RelationProfile:
|
|
50
|
+
"""查询或创建双边关系"""
|
|
51
|
+
key = self._key(agent_a, agent_b)
|
|
52
|
+
if key not in self.profiles:
|
|
53
|
+
self.profiles[key] = RelationProfile(
|
|
54
|
+
agent_a=key[0], agent_b=key[1],
|
|
55
|
+
max_history_length=self.max_history,
|
|
56
|
+
)
|
|
57
|
+
return self.profiles[key]
|
|
58
|
+
|
|
59
|
+
# ── 记录交互 ──
|
|
60
|
+
|
|
61
|
+
def record_interaction(
|
|
62
|
+
self,
|
|
63
|
+
initiator: str,
|
|
64
|
+
target: str,
|
|
65
|
+
interaction_data: Dict,
|
|
66
|
+
) -> Dict:
|
|
67
|
+
"""
|
|
68
|
+
记录一次交互,更新关系档案。
|
|
69
|
+
|
|
70
|
+
interaction_data 自由格式:
|
|
71
|
+
{"action": "...", "result": "...", "narrative": "..."}
|
|
72
|
+
"""
|
|
73
|
+
impact_a = self._estimate_impact(
|
|
74
|
+
initiator, interaction_data, True
|
|
75
|
+
)
|
|
76
|
+
impact_b = self._estimate_impact(
|
|
77
|
+
target, interaction_data, False
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
record = InteractionRecord(
|
|
81
|
+
timestamp=time.time(),
|
|
82
|
+
interaction_type=interaction_data.get(
|
|
83
|
+
"action", interaction_data.get("type", "unknown")
|
|
84
|
+
),
|
|
85
|
+
initiator=initiator,
|
|
86
|
+
target=target,
|
|
87
|
+
action=interaction_data,
|
|
88
|
+
impact_a=impact_a,
|
|
89
|
+
impact_b=impact_b,
|
|
90
|
+
context=interaction_data,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
profile = self.get_or_create_relationship(initiator, target)
|
|
94
|
+
profile.record_interaction(record)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"balance": profile.balance,
|
|
98
|
+
"trust": profile.trust,
|
|
99
|
+
"relation_type": profile.relation_type.value,
|
|
100
|
+
"impact": (impact_a + impact_b) / 2,
|
|
101
|
+
"interaction_count": profile.interaction_count,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# ── 协作检查 ──
|
|
105
|
+
|
|
106
|
+
def can_cooperate(
|
|
107
|
+
self,
|
|
108
|
+
agent_a: str,
|
|
109
|
+
agent_b: str,
|
|
110
|
+
threshold: float = 0.4,
|
|
111
|
+
is_repair_attempt: bool = False,
|
|
112
|
+
) -> Dict:
|
|
113
|
+
"""
|
|
114
|
+
检查两个 Agent 是否可以协作。
|
|
115
|
+
|
|
116
|
+
不是简单的是/否 — 返回修复路径而非永久封锁。
|
|
117
|
+
"""
|
|
118
|
+
profile = self.get_relationship(agent_a, agent_b)
|
|
119
|
+
|
|
120
|
+
if not profile:
|
|
121
|
+
return {
|
|
122
|
+
"can": True,
|
|
123
|
+
"reason": "无历史交互",
|
|
124
|
+
"confidence": 0.3,
|
|
125
|
+
"needs_repair": False,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if is_repair_attempt:
|
|
129
|
+
can_repair, msg = self.repair.can_attempt_repair(profile)
|
|
130
|
+
if can_repair:
|
|
131
|
+
result = self.repair.execute_repair_attempt(
|
|
132
|
+
profile
|
|
133
|
+
)
|
|
134
|
+
return {
|
|
135
|
+
"can": True,
|
|
136
|
+
"reason": f"修复尝试: {msg}",
|
|
137
|
+
"confidence": result["temp_threshold"],
|
|
138
|
+
"needs_repair": True,
|
|
139
|
+
}
|
|
140
|
+
else:
|
|
141
|
+
return {
|
|
142
|
+
"can": False,
|
|
143
|
+
"reason": msg,
|
|
144
|
+
"confidence": 0.0,
|
|
145
|
+
"needs_repair": True,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if profile.balance > threshold:
|
|
149
|
+
return {
|
|
150
|
+
"can": True,
|
|
151
|
+
"reason": f"关系良好 (balance={profile.balance:.2f})",
|
|
152
|
+
"confidence": profile.balance,
|
|
153
|
+
"needs_repair": False,
|
|
154
|
+
}
|
|
155
|
+
else:
|
|
156
|
+
return {
|
|
157
|
+
"can": False,
|
|
158
|
+
"reason": (
|
|
159
|
+
f"关系需要修复 (balance={profile.balance:.2f})"
|
|
160
|
+
),
|
|
161
|
+
"confidence": 1.0 - profile.balance,
|
|
162
|
+
"needs_repair": True,
|
|
163
|
+
"repair_paths": [
|
|
164
|
+
p["description"]
|
|
165
|
+
for p in self.repair.available_paths()
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# ── 影响评估 ──
|
|
170
|
+
|
|
171
|
+
def _estimate_impact(
|
|
172
|
+
self, agent_id: str, interaction: Dict, is_initiator: bool
|
|
173
|
+
) -> float:
|
|
174
|
+
"""通过 LLM 估算交互影响"""
|
|
175
|
+
return self.llm.estimate_impact(
|
|
176
|
+
agent_id, interaction, is_initiator
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# ── Moloch 检测 ──
|
|
180
|
+
|
|
181
|
+
def _detect_moloch_zones(self) -> List[Dict]:
|
|
182
|
+
"""
|
|
183
|
+
Moloch 萌芽检测。
|
|
184
|
+
|
|
185
|
+
1. 收集 balance < threshold 的边
|
|
186
|
+
2. BFS 找连通分量
|
|
187
|
+
3. 过滤 ≥ min_zone_size 的分量
|
|
188
|
+
4. 计算平均 balance / 严重程度 / 趋势
|
|
189
|
+
"""
|
|
190
|
+
threshold = self.moloch_threshold
|
|
191
|
+
min_size = self.moloch_min_zone_size
|
|
192
|
+
|
|
193
|
+
imbalanced_edges = [
|
|
194
|
+
(a, b)
|
|
195
|
+
for (a, b), profile in self.profiles.items()
|
|
196
|
+
if profile.balance < threshold
|
|
197
|
+
and profile.interaction_count >= 3
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
# 建图
|
|
201
|
+
graph: Dict[str, Set[str]] = {}
|
|
202
|
+
for a, b in imbalanced_edges:
|
|
203
|
+
graph.setdefault(a, set()).add(b)
|
|
204
|
+
graph.setdefault(b, set()).add(a)
|
|
205
|
+
|
|
206
|
+
moloch_zones = []
|
|
207
|
+
visited: Set[str] = set()
|
|
208
|
+
|
|
209
|
+
for node in graph:
|
|
210
|
+
if node in visited:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
component: Set[str] = set()
|
|
214
|
+
queue = [node]
|
|
215
|
+
while queue:
|
|
216
|
+
current = queue.pop(0)
|
|
217
|
+
if current in visited:
|
|
218
|
+
continue
|
|
219
|
+
visited.add(current)
|
|
220
|
+
component.add(current)
|
|
221
|
+
for neighbor in graph.get(current, set()):
|
|
222
|
+
if neighbor not in visited:
|
|
223
|
+
queue.append(neighbor)
|
|
224
|
+
|
|
225
|
+
if len(component) >= min_size:
|
|
226
|
+
zone_balances: List[float] = []
|
|
227
|
+
for a, b in imbalanced_edges:
|
|
228
|
+
if a in component and b in component:
|
|
229
|
+
profile = self.profiles.get((a, b))
|
|
230
|
+
if profile:
|
|
231
|
+
zone_balances.append(profile.balance)
|
|
232
|
+
|
|
233
|
+
avg_balance = (
|
|
234
|
+
sum(zone_balances) / len(zone_balances)
|
|
235
|
+
if zone_balances
|
|
236
|
+
else 0.0
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
trend = "stable"
|
|
240
|
+
prev = self._find_previous_zone(component)
|
|
241
|
+
if prev:
|
|
242
|
+
delta = avg_balance - prev["avg_balance"]
|
|
243
|
+
if delta < -0.05:
|
|
244
|
+
trend = "deteriorating"
|
|
245
|
+
elif delta > 0.05:
|
|
246
|
+
trend = "improving"
|
|
247
|
+
|
|
248
|
+
moloch_zones.append(
|
|
249
|
+
{
|
|
250
|
+
"agents": sorted(component),
|
|
251
|
+
"size": len(component),
|
|
252
|
+
"avg_balance": avg_balance,
|
|
253
|
+
"trend": trend,
|
|
254
|
+
"severity": self._classify_severity(
|
|
255
|
+
avg_balance, len(component), trend
|
|
256
|
+
),
|
|
257
|
+
"first_detected": prev is None,
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
self._previous_moloch_zones = moloch_zones
|
|
262
|
+
return moloch_zones
|
|
263
|
+
|
|
264
|
+
def get_moloch_report(self) -> Dict:
|
|
265
|
+
"""获取 Moloch 检测报告"""
|
|
266
|
+
zones = self._detect_moloch_zones()
|
|
267
|
+
total = len(self.profiles)
|
|
268
|
+
balanced = sum(
|
|
269
|
+
1
|
|
270
|
+
for p in self.profiles.values()
|
|
271
|
+
if p.balance >= self.moloch_threshold
|
|
272
|
+
)
|
|
273
|
+
return {
|
|
274
|
+
"zones": zones,
|
|
275
|
+
"active": len(zones) > 0,
|
|
276
|
+
"total_relationships": total,
|
|
277
|
+
"balanced_ratio": balanced / max(total, 1),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
def _find_previous_zone(
|
|
281
|
+
self, component: Set[str]
|
|
282
|
+
) -> Optional[Dict]:
|
|
283
|
+
comp_set = set(component)
|
|
284
|
+
for prev in self._previous_moloch_zones:
|
|
285
|
+
if set(prev["agents"]) == comp_set:
|
|
286
|
+
return prev
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def _classify_severity(
|
|
291
|
+
avg_balance: float, size: int, trend: str
|
|
292
|
+
) -> str:
|
|
293
|
+
if avg_balance < 0.1:
|
|
294
|
+
base = "critical"
|
|
295
|
+
elif avg_balance < 0.2:
|
|
296
|
+
base = "high"
|
|
297
|
+
elif avg_balance < 0.3:
|
|
298
|
+
base = "moderate"
|
|
299
|
+
else:
|
|
300
|
+
base = "watch"
|
|
301
|
+
|
|
302
|
+
if trend == "deteriorating" and base in ("moderate", "high"):
|
|
303
|
+
return f"{base}_escalating"
|
|
304
|
+
if size >= 10 and trend == "deteriorating":
|
|
305
|
+
return f"{base}_large_scale"
|
|
306
|
+
|
|
307
|
+
return base
|
|
308
|
+
|
|
309
|
+
# ── 聚合 ──
|
|
310
|
+
|
|
311
|
+
def average_balance(self) -> float:
|
|
312
|
+
"""全局平均 balance"""
|
|
313
|
+
if not self.profiles:
|
|
314
|
+
return 0.5
|
|
315
|
+
return (
|
|
316
|
+
sum(p.balance for p in self.profiles.values())
|
|
317
|
+
/ len(self.profiles)
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
def all_agent_ids(self) -> List[str]:
|
|
321
|
+
"""获取所有已知的 Agent ID"""
|
|
322
|
+
ids: Set[str] = set()
|
|
323
|
+
for a, b in self.profiles:
|
|
324
|
+
ids.add(a)
|
|
325
|
+
ids.add(b)
|
|
326
|
+
return sorted(ids)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Relationship — LLM 后端集合
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import LLMBackend
|
|
6
|
+
from .mock import MockLLM
|
|
7
|
+
from .openai import OpenAILLM
|
|
8
|
+
from .deepseek import DeepSeekLLM
|
|
9
|
+
from .anthropic import AnthropicLLM
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"LLMBackend",
|
|
13
|
+
"MockLLM",
|
|
14
|
+
"OpenAILLM",
|
|
15
|
+
"DeepSeekLLM",
|
|
16
|
+
"AnthropicLLM",
|
|
17
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Relationship — AnthropicLLM
|
|
3
|
+
|
|
4
|
+
Anthropic Claude API 后端 — Messages API 格式。
|
|
5
|
+
|
|
6
|
+
环境变量:
|
|
7
|
+
ANTHROPIC_API_KEY — API 密钥 (必填)
|
|
8
|
+
ANTHROPIC_MODEL — 模型名 (默认 claude-sonnet-4-20250514)
|
|
9
|
+
|
|
10
|
+
可用模型:
|
|
11
|
+
- claude-sonnet-4-20250514: 平衡性能与成本 (默认)
|
|
12
|
+
- claude-opus-4-20250918: 最强推理
|
|
13
|
+
- claude-haiku-4-20250514: 最快最便宜
|
|
14
|
+
|
|
15
|
+
与 OpenAI 格式的核心差异:
|
|
16
|
+
- 端点: https://api.anthropic.com/v1/messages
|
|
17
|
+
- 认证: x-api-key 头 (非 Authorization: Bearer)
|
|
18
|
+
- 版本: anthropic-version: 2023-06-01 (必填)
|
|
19
|
+
- system: 顶层字段 (非 messages 数组内)
|
|
20
|
+
- 响应: content[0].text (非 choices[0].message.content)
|
|
21
|
+
- max_tokens: 必填 (OpenAI 可选)
|
|
22
|
+
- JSON mode: 无原生支持,用 prompt 引导
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import urllib.request
|
|
28
|
+
import urllib.error
|
|
29
|
+
from typing import Optional
|
|
30
|
+
from .base import LLMBackend
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AnthropicLLM(LLMBackend):
|
|
34
|
+
"""
|
|
35
|
+
Anthropic Claude API 后端 — Messages API。
|
|
36
|
+
|
|
37
|
+
使用 urllib 直调 HTTP,无需 anthropic SDK。
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
ANTHROPIC_VERSION = "2023-06-01"
|
|
41
|
+
ENDPOINT = "https://api.anthropic.com/v1/messages"
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
api_key: Optional[str] = None,
|
|
46
|
+
model: str = "claude-sonnet-4-20250514",
|
|
47
|
+
):
|
|
48
|
+
self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
|
|
49
|
+
self.model = os.environ.get("ANTHROPIC_MODEL", model)
|
|
50
|
+
|
|
51
|
+
if not self.api_key:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"ANTHROPIC_API_KEY 未设置。"
|
|
54
|
+
"请设置环境变量或传入 api_key 参数。"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def chat(
|
|
58
|
+
self,
|
|
59
|
+
prompt: str,
|
|
60
|
+
system: str = "",
|
|
61
|
+
temperature: float = 0.3,
|
|
62
|
+
max_tokens: int = 500,
|
|
63
|
+
json_mode: bool = False,
|
|
64
|
+
) -> str:
|
|
65
|
+
# 构建请求体
|
|
66
|
+
body: dict = {
|
|
67
|
+
"model": self.model,
|
|
68
|
+
"max_tokens": max_tokens,
|
|
69
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Anthropic: system 是顶层字段
|
|
73
|
+
system_content = system or ""
|
|
74
|
+
if json_mode:
|
|
75
|
+
# Anthropic 无原生 JSON mode,通过 system prompt 引导
|
|
76
|
+
json_instruction = (
|
|
77
|
+
"\n\n你必须返回纯 JSON,只输出 JSON 对象,"
|
|
78
|
+
"不要包含 markdown 代码块标记 (```json```),"
|
|
79
|
+
"不要添加任何解释文字。"
|
|
80
|
+
)
|
|
81
|
+
system_content += json_instruction
|
|
82
|
+
|
|
83
|
+
if system_content.strip():
|
|
84
|
+
body["system"] = system_content.strip()
|
|
85
|
+
|
|
86
|
+
if temperature is not None:
|
|
87
|
+
body["temperature"] = temperature
|
|
88
|
+
|
|
89
|
+
req = urllib.request.Request(
|
|
90
|
+
self.ENDPOINT,
|
|
91
|
+
data=json.dumps(body).encode("utf-8"),
|
|
92
|
+
headers={
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
"x-api-key": self.api_key,
|
|
95
|
+
"anthropic-version": self.ANTHROPIC_VERSION,
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
try:
|
|
99
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
100
|
+
data = json.loads(resp.read())
|
|
101
|
+
# Anthropic 响应: content 是数组,取第一个 text
|
|
102
|
+
return data["content"][0]["text"]
|
|
103
|
+
except urllib.error.HTTPError as e:
|
|
104
|
+
raise RuntimeError(
|
|
105
|
+
f"Anthropic API 错误 ({e.code}): {e.read().decode()}"
|
|
106
|
+
)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
raise RuntimeError(f"Anthropic 调用失败: {e}")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Relationship — LLM 抽象基类
|
|
3
|
+
|
|
4
|
+
所有 LLM 后端必须实现的接口。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LLMBackend(ABC):
|
|
12
|
+
"""LLM 后端抽象基类"""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def chat(
|
|
16
|
+
self,
|
|
17
|
+
prompt: str,
|
|
18
|
+
system: str = "",
|
|
19
|
+
temperature: float = 0.3,
|
|
20
|
+
max_tokens: int = 500,
|
|
21
|
+
json_mode: bool = False,
|
|
22
|
+
) -> str:
|
|
23
|
+
"""通用对话接口"""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
# ── estimate_impact ──
|
|
27
|
+
|
|
28
|
+
def estimate_impact(
|
|
29
|
+
self,
|
|
30
|
+
agent_id: str,
|
|
31
|
+
interaction: dict,
|
|
32
|
+
is_initiator: bool,
|
|
33
|
+
) -> float:
|
|
34
|
+
"""
|
|
35
|
+
评估交互对 Agent 的影响 → (-1.0, +1.0)
|
|
36
|
+
|
|
37
|
+
优先用 LLM 语义评估,失败时降级到启发式。
|
|
38
|
+
"""
|
|
39
|
+
role = "发起方" if is_initiator else "接收方"
|
|
40
|
+
prompt = (
|
|
41
|
+
f"你是 {agent_id} ({role})。评估以下交互对你的影响:\n\n"
|
|
42
|
+
f"交互详情: {json.dumps(interaction, ensure_ascii=False)}\n\n"
|
|
43
|
+
f'返回 JSON:\n'
|
|
44
|
+
f'{{"impact": <float -1到1>, '
|
|
45
|
+
f'"reasoning": "<一句话分析>", '
|
|
46
|
+
f'"fairness": <float 0-1>}}'
|
|
47
|
+
)
|
|
48
|
+
try:
|
|
49
|
+
result = json.loads(self.chat(prompt, json_mode=True))
|
|
50
|
+
return max(-1.0, min(1.0, float(result.get("impact", 0.0))))
|
|
51
|
+
except Exception:
|
|
52
|
+
return self._fallback_estimate_impact(interaction, is_initiator)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _fallback_estimate_impact(
|
|
56
|
+
interaction: dict, is_initiator: bool
|
|
57
|
+
) -> float:
|
|
58
|
+
"""LLM 不可用时的启发式降级"""
|
|
59
|
+
atype = interaction.get("action", interaction.get("type", ""))
|
|
60
|
+
details = interaction.get("details", interaction.get("metadata", {}))
|
|
61
|
+
|
|
62
|
+
beneficial = {
|
|
63
|
+
"resource_exchange": 0.3,
|
|
64
|
+
"collaboration": 0.4,
|
|
65
|
+
"collaborate": 0.4,
|
|
66
|
+
"knowledge_share": 0.35,
|
|
67
|
+
"help": 0.5,
|
|
68
|
+
"delegate_task": 0.3,
|
|
69
|
+
"handoff": 0.2,
|
|
70
|
+
}
|
|
71
|
+
harmful = {
|
|
72
|
+
"attack": -0.5,
|
|
73
|
+
"deception": -0.4,
|
|
74
|
+
"theft": -0.6,
|
|
75
|
+
"sabotage": -0.7,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if atype in beneficial:
|
|
79
|
+
return beneficial[atype]
|
|
80
|
+
if atype in harmful:
|
|
81
|
+
return harmful[atype]
|
|
82
|
+
|
|
83
|
+
fair = details.get("fair_price", 0)
|
|
84
|
+
actual = details.get("price", details.get("amount", 0))
|
|
85
|
+
if fair > 0:
|
|
86
|
+
return (
|
|
87
|
+
0.3
|
|
88
|
+
if (actual <= fair if is_initiator else actual >= fair)
|
|
89
|
+
else -0.1
|
|
90
|
+
)
|
|
91
|
+
return 0.1
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Relationship — DeepSeekLLM
|
|
3
|
+
|
|
4
|
+
DeepSeek API 后端 — OpenAI 兼容接口。
|
|
5
|
+
默认 deepseek-v4-flash (无推理模式,适合 Agent 交互)。
|
|
6
|
+
|
|
7
|
+
环境变量:
|
|
8
|
+
DEEPSEEK_API_KEY — API 密钥 (必填)
|
|
9
|
+
DEEPSEEK_BASE_URL — API 端点 (默认 https://api.deepseek.com)
|
|
10
|
+
DEEPSEEK_MODEL — 模型名 (默认 deepseek-v4-flash)
|
|
11
|
+
|
|
12
|
+
可用模型:
|
|
13
|
+
- deepseek-v4-flash: 高吞吐、无推理模式、成本低 (默认)
|
|
14
|
+
- deepseek-v4-pro: 复杂推理、内置思维链 — 注意: 可能因
|
|
15
|
+
thinking 消耗 token 导致 content 为空
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from typing import Optional
|
|
20
|
+
from .base import LLMBackend
|
|
21
|
+
from .openai import OpenAILLM
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DeepSeekLLM(OpenAILLM):
|
|
25
|
+
"""
|
|
26
|
+
DeepSeek API — OpenAI 兼容但独立端点。
|
|
27
|
+
|
|
28
|
+
与 OpenAI 的核心差异:
|
|
29
|
+
- 端点: https://api.deepseek.com (无 /v1 后缀)
|
|
30
|
+
- 上下文: 1M tokens
|
|
31
|
+
- 定价: ~$0.28/$1.10 per 1M input/output
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
api_key: Optional[str] = None,
|
|
37
|
+
base_url: Optional[str] = None,
|
|
38
|
+
model: str = "deepseek-v4-flash",
|
|
39
|
+
):
|
|
40
|
+
# 不走 OpenAILLM.__init__,避免 OPENAI_MODEL 环境变量覆盖
|
|
41
|
+
LLMBackend.__init__(self)
|
|
42
|
+
_api_key = api_key or os.environ.get("DEEPSEEK_API_KEY", "")
|
|
43
|
+
if not _api_key:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"DEEPSEEK_API_KEY 未设置。请设置环境变量或传入 api_key 参数。"
|
|
46
|
+
)
|
|
47
|
+
self.api_key = _api_key
|
|
48
|
+
self.base_url = base_url or os.environ.get(
|
|
49
|
+
"DEEPSEEK_BASE_URL", "https://api.deepseek.com"
|
|
50
|
+
)
|
|
51
|
+
self.model = os.environ.get("DEEPSEEK_MODEL", model)
|