spl-agent-engine 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {spl_agent_engine-0.2.0/spl_agent_engine.egg-info → spl_agent_engine-0.3.0}/PKG-INFO +1 -1
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/pyproject.toml +1 -1
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/__init__.py +18 -3
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/core.py +135 -11
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0/spl_agent_engine.egg-info}/PKG-INFO +1 -1
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/LICENSE +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/README.md +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/setup.cfg +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/__init__.py +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/bias.py +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/goal.py +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/identity.py +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/language_style.py +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/value.py +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/world.py +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/py.typed +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine.egg-info/SOURCES.txt +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine.egg-info/dependency_links.txt +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine.egg-info/requires.txt +0 -0
- {spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spl-agent-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Anthropomorphic psychology engine built on SPL Pure Core V8.0 — deterministic emotion, trauma, memory, trust, and defense mechanisms.
|
|
5
5
|
Author-email: NOHN-AI <ai@nohnlins.com>
|
|
6
6
|
Maintainer-email: NOHN-AI <ai@nohnlins.com>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spl-agent-engine"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Anthropomorphic psychology engine built on SPL Pure Core V8.0 — deterministic emotion, trauma, memory, trust, and defense mechanisms."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -19,10 +19,25 @@ Usage:
|
|
|
19
19
|
print(core.fluid)
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from .core import
|
|
22
|
+
from .core import (
|
|
23
|
+
AuditLogger,
|
|
24
|
+
SPLPureCoreV7_3,
|
|
25
|
+
NarrativeMapper,
|
|
26
|
+
TokenUsage,
|
|
27
|
+
LLMAdapter,
|
|
28
|
+
OpenAIAdapter,
|
|
29
|
+
ClaudeAdapter,
|
|
30
|
+
ChainAdapter,
|
|
31
|
+
generate_line_with_llm,
|
|
32
|
+
)
|
|
23
33
|
|
|
24
34
|
# Canonical name for V8.0 (class name kept for backward compatibility)
|
|
25
35
|
SPLPureCore = SPLPureCoreV7_3
|
|
26
36
|
|
|
27
|
-
__version__ = "0.
|
|
28
|
-
__all__ = [
|
|
37
|
+
__version__ = "0.3.0"
|
|
38
|
+
__all__ = [
|
|
39
|
+
"SPLPureCore", "SPLPureCoreV7_3", "NarrativeMapper", "AuditLogger",
|
|
40
|
+
"TokenUsage",
|
|
41
|
+
"LLMAdapter", "OpenAIAdapter", "ClaudeAdapter", "ChainAdapter",
|
|
42
|
+
"generate_line_with_llm",
|
|
43
|
+
]
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import datetime
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Dict, Any, List, Optional
|
|
7
|
+
from typing import Dict, Any, List, Optional, Tuple, Union
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
# ================================================================
|
|
@@ -75,10 +75,54 @@ class AuditLogger:
|
|
|
75
75
|
"memory_count", "expected_count")
|
|
76
76
|
return {k: snap.get(k) for k in keys if k in snap}
|
|
77
77
|
|
|
78
|
+
def log_llm_call(self, model: str, prompt_preview: str,
|
|
79
|
+
usage: Optional["TokenUsage"] = None,
|
|
80
|
+
duration_ms: float = 0.0,
|
|
81
|
+
success: bool = True,
|
|
82
|
+
error: str = "") -> None:
|
|
83
|
+
"""记录一次 LLM 调用(token 用量、耗时、成功/失败)。
|
|
84
|
+
|
|
85
|
+
写入失败静默跳过,绝不阻断引擎核心。
|
|
86
|
+
"""
|
|
87
|
+
if not self.enabled:
|
|
88
|
+
return
|
|
89
|
+
try:
|
|
90
|
+
entry = {
|
|
91
|
+
"seq": self._entry_count,
|
|
92
|
+
"ts": datetime.datetime.now().isoformat(timespec="milliseconds"),
|
|
93
|
+
"engine": self.ENGINE_VERSION,
|
|
94
|
+
"session": self.session_id,
|
|
95
|
+
"event": "llm_call",
|
|
96
|
+
"model": model,
|
|
97
|
+
"prompt_preview": prompt_preview[:200],
|
|
98
|
+
"success": success,
|
|
99
|
+
"duration_ms": round(duration_ms, 2),
|
|
100
|
+
"usage": {
|
|
101
|
+
"prompt_tokens": usage.prompt_tokens if usage else 0,
|
|
102
|
+
"completion_tokens": usage.completion_tokens if usage else 0,
|
|
103
|
+
"total_tokens": usage.total_tokens if usage else 0,
|
|
104
|
+
} if usage else None,
|
|
105
|
+
"error": error or None,
|
|
106
|
+
}
|
|
107
|
+
with open(self.log_file, "a", encoding="utf-8") as f:
|
|
108
|
+
f.write(json.dumps(entry, ensure_ascii=False, default=str) + "\n")
|
|
109
|
+
self._entry_count += 1
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
78
113
|
def export_path(self) -> Optional[str]:
|
|
79
114
|
return self.log_file if os.path.exists(self.log_file) else None
|
|
80
115
|
|
|
81
116
|
|
|
117
|
+
@dataclass
|
|
118
|
+
class TokenUsage:
|
|
119
|
+
"""LLM 调用 token 用量统计。"""
|
|
120
|
+
prompt_tokens: int = 0
|
|
121
|
+
completion_tokens: int = 0
|
|
122
|
+
total_tokens: int = 0
|
|
123
|
+
model: str = ""
|
|
124
|
+
|
|
125
|
+
|
|
82
126
|
# ================================================================
|
|
83
127
|
# 1. 外部叙事映射器("自设:偏见/世界观/价值观"——纯外部策略)
|
|
84
128
|
# 用户可随意替换,无需修改核心代码。
|
|
@@ -1309,7 +1353,9 @@ class LLMAdapter(ABC):
|
|
|
1309
1353
|
style_example: str = "",
|
|
1310
1354
|
system_prompt: str = "",
|
|
1311
1355
|
temperature: float = 0.7,
|
|
1312
|
-
max_tokens: int = 500
|
|
1356
|
+
max_tokens: int = 500,
|
|
1357
|
+
return_usage: bool = False
|
|
1358
|
+
) -> Union[Optional[str], Tuple[Optional[str], Optional["TokenUsage"]]]:
|
|
1313
1359
|
"""
|
|
1314
1360
|
根据 prompt_injection 生成台词。
|
|
1315
1361
|
|
|
@@ -1319,9 +1365,11 @@ class LLMAdapter(ABC):
|
|
|
1319
1365
|
system_prompt: 可选系统提示词,覆盖默认
|
|
1320
1366
|
temperature: 采样温度
|
|
1321
1367
|
max_tokens: 最大生成 token 数
|
|
1368
|
+
return_usage: True 时返回 (台词, TokenUsage) 元组
|
|
1322
1369
|
|
|
1323
1370
|
Returns:
|
|
1324
|
-
生成的台词字符串,失败返回 None
|
|
1371
|
+
return_usage=False: 生成的台词字符串,失败返回 None
|
|
1372
|
+
return_usage=True: (台词, TokenUsage) 元组
|
|
1325
1373
|
"""
|
|
1326
1374
|
...
|
|
1327
1375
|
|
|
@@ -1350,7 +1398,9 @@ class OpenAIAdapter(LLMAdapter):
|
|
|
1350
1398
|
style_example: str = "",
|
|
1351
1399
|
system_prompt: str = "",
|
|
1352
1400
|
temperature: float = 0.7,
|
|
1353
|
-
max_tokens: int = 500
|
|
1401
|
+
max_tokens: int = 500,
|
|
1402
|
+
return_usage: bool = False
|
|
1403
|
+
) -> Union[Optional[str], Tuple[Optional[str], Optional[TokenUsage]]]:
|
|
1354
1404
|
import urllib.request
|
|
1355
1405
|
import urllib.error
|
|
1356
1406
|
|
|
@@ -1383,8 +1433,22 @@ class OpenAIAdapter(LLMAdapter):
|
|
|
1383
1433
|
body = json.loads(resp.read().decode("utf-8"))
|
|
1384
1434
|
choice = body.get("choices", [{}])[0]
|
|
1385
1435
|
content = choice.get("message", {}).get("content", "")
|
|
1386
|
-
|
|
1436
|
+
result = content.strip() if content else None
|
|
1437
|
+
|
|
1438
|
+
usage_data = body.get("usage", {}) or {}
|
|
1439
|
+
usage = TokenUsage(
|
|
1440
|
+
prompt_tokens=usage_data.get("prompt_tokens", 0),
|
|
1441
|
+
completion_tokens=usage_data.get("completion_tokens", 0),
|
|
1442
|
+
total_tokens=usage_data.get("total_tokens", 0),
|
|
1443
|
+
model=body.get("model", self.model),
|
|
1444
|
+
) if usage_data else None
|
|
1445
|
+
|
|
1446
|
+
if return_usage:
|
|
1447
|
+
return result, usage
|
|
1448
|
+
return result
|
|
1387
1449
|
except (urllib.error.URLError, json.JSONDecodeError, KeyError) as e:
|
|
1450
|
+
if return_usage:
|
|
1451
|
+
return None, None
|
|
1388
1452
|
return None
|
|
1389
1453
|
|
|
1390
1454
|
|
|
@@ -1412,7 +1476,9 @@ class ClaudeAdapter(LLMAdapter):
|
|
|
1412
1476
|
style_example: str = "",
|
|
1413
1477
|
system_prompt: str = "",
|
|
1414
1478
|
temperature: float = 0.7,
|
|
1415
|
-
max_tokens: int = 500
|
|
1479
|
+
max_tokens: int = 500,
|
|
1480
|
+
return_usage: bool = False
|
|
1481
|
+
) -> Union[Optional[str], Tuple[Optional[str], Optional[TokenUsage]]]:
|
|
1416
1482
|
import urllib.request
|
|
1417
1483
|
import urllib.error
|
|
1418
1484
|
|
|
@@ -1445,8 +1511,24 @@ class ClaudeAdapter(LLMAdapter):
|
|
|
1445
1511
|
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
1446
1512
|
body = json.loads(resp.read().decode("utf-8"))
|
|
1447
1513
|
content = body.get("content", [{}])[0].get("text", "")
|
|
1448
|
-
|
|
1514
|
+
result = content.strip() if content else None
|
|
1515
|
+
|
|
1516
|
+
usage_data = body.get("usage", {}) or {}
|
|
1517
|
+
prompt_tok = usage_data.get("input_tokens", 0)
|
|
1518
|
+
completion_tok = usage_data.get("output_tokens", 0)
|
|
1519
|
+
usage = TokenUsage(
|
|
1520
|
+
prompt_tokens=prompt_tok,
|
|
1521
|
+
completion_tokens=completion_tok,
|
|
1522
|
+
total_tokens=prompt_tok + completion_tok,
|
|
1523
|
+
model=body.get("model", self.model),
|
|
1524
|
+
) if usage_data else None
|
|
1525
|
+
|
|
1526
|
+
if return_usage:
|
|
1527
|
+
return result, usage
|
|
1528
|
+
return result
|
|
1449
1529
|
except (urllib.error.URLError, json.JSONDecodeError, KeyError) as e:
|
|
1530
|
+
if return_usage:
|
|
1531
|
+
return None, None
|
|
1450
1532
|
return None
|
|
1451
1533
|
|
|
1452
1534
|
|
|
@@ -1472,24 +1554,45 @@ class ChainAdapter(LLMAdapter):
|
|
|
1472
1554
|
style_example: str = "",
|
|
1473
1555
|
system_prompt: str = "",
|
|
1474
1556
|
temperature: float = 0.7,
|
|
1475
|
-
max_tokens: int = 500
|
|
1476
|
-
|
|
1557
|
+
max_tokens: int = 500,
|
|
1558
|
+
return_usage: bool = False
|
|
1559
|
+
) -> Union[Optional[str], Tuple[Optional[str], Optional[TokenUsage]]]:
|
|
1560
|
+
result, usage = self.primary.generate(
|
|
1477
1561
|
prompt_injection=prompt_injection,
|
|
1478
1562
|
style_example=style_example,
|
|
1479
1563
|
system_prompt=system_prompt,
|
|
1480
1564
|
temperature=temperature,
|
|
1481
1565
|
max_tokens=max_tokens,
|
|
1566
|
+
return_usage=True,
|
|
1482
1567
|
)
|
|
1483
1568
|
if result is not None:
|
|
1569
|
+
if return_usage:
|
|
1570
|
+
return result, usage
|
|
1484
1571
|
return result
|
|
1485
1572
|
if self.fallback is not None:
|
|
1486
|
-
|
|
1573
|
+
result2, usage2 = self.fallback.generate(
|
|
1487
1574
|
prompt_injection=prompt_injection,
|
|
1488
1575
|
style_example=style_example,
|
|
1489
1576
|
system_prompt=system_prompt,
|
|
1490
1577
|
temperature=temperature,
|
|
1491
1578
|
max_tokens=max_tokens,
|
|
1579
|
+
return_usage=True,
|
|
1492
1580
|
)
|
|
1581
|
+
if return_usage:
|
|
1582
|
+
# 合并两次调用的 token 用量
|
|
1583
|
+
if usage and usage2:
|
|
1584
|
+
merged = TokenUsage(
|
|
1585
|
+
prompt_tokens=usage.prompt_tokens + usage2.prompt_tokens,
|
|
1586
|
+
completion_tokens=usage.completion_tokens + usage2.completion_tokens,
|
|
1587
|
+
total_tokens=usage.total_tokens + usage2.total_tokens,
|
|
1588
|
+
model=f"{usage.model}->{usage2.model}",
|
|
1589
|
+
)
|
|
1590
|
+
else:
|
|
1591
|
+
merged = usage2 or usage
|
|
1592
|
+
return result2, merged
|
|
1593
|
+
return result2
|
|
1594
|
+
if return_usage:
|
|
1595
|
+
return None, usage
|
|
1493
1596
|
return None
|
|
1494
1597
|
|
|
1495
1598
|
|
|
@@ -1501,6 +1604,7 @@ def generate_line_with_llm(
|
|
|
1501
1604
|
core_snapshot: dict,
|
|
1502
1605
|
expression,
|
|
1503
1606
|
adapter: LLMAdapter,
|
|
1607
|
+
audit_logger: Optional["AuditLogger"] = None,
|
|
1504
1608
|
) -> str:
|
|
1505
1609
|
"""
|
|
1506
1610
|
便利函数:引擎渲染风格 → LLM 生成台词 → 失败回退到确定性生成。
|
|
@@ -1510,18 +1614,38 @@ def generate_line_with_llm(
|
|
|
1510
1614
|
core_snapshot: SPL 核心快照(含 fluid/mood/self_esteem 等)
|
|
1511
1615
|
expression: 表达意图对象(含 expression_mode / emotion_hidden / silence 等)
|
|
1512
1616
|
adapter: LLMAdapter 实例
|
|
1617
|
+
audit_logger: 可选 AuditLogger,传入则记录 LLM 调用与 token 用量
|
|
1513
1618
|
|
|
1514
1619
|
Returns:
|
|
1515
1620
|
台词字符串(LLM 成功=LLM 产出,失败=引擎确定性产出)
|
|
1516
1621
|
"""
|
|
1622
|
+
import time as _time
|
|
1623
|
+
|
|
1517
1624
|
rendered = engine.render_style(core_snapshot, expression)
|
|
1518
1625
|
if rendered.silence_hint:
|
|
1519
1626
|
return rendered.silence_hint
|
|
1520
1627
|
|
|
1521
|
-
|
|
1628
|
+
t0 = _time.perf_counter()
|
|
1629
|
+
line, usage = adapter.generate(
|
|
1522
1630
|
prompt_injection=rendered.prompt_injection,
|
|
1523
1631
|
style_example=rendered.style_example,
|
|
1632
|
+
return_usage=True,
|
|
1524
1633
|
)
|
|
1634
|
+
duration_ms = (_time.perf_counter() - t0) * 1000
|
|
1635
|
+
|
|
1636
|
+
if audit_logger is not None:
|
|
1637
|
+
model = getattr(adapter, "model", "") or (
|
|
1638
|
+
usage.model if usage else ""
|
|
1639
|
+
)
|
|
1640
|
+
audit_logger.log_llm_call(
|
|
1641
|
+
model=model,
|
|
1642
|
+
prompt_preview=rendered.prompt_injection,
|
|
1643
|
+
usage=usage,
|
|
1644
|
+
duration_ms=duration_ms,
|
|
1645
|
+
success=line is not None,
|
|
1646
|
+
error="" if line is not None else "llm_failed",
|
|
1647
|
+
)
|
|
1648
|
+
|
|
1525
1649
|
if line is not None:
|
|
1526
1650
|
return line
|
|
1527
1651
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spl-agent-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Anthropomorphic psychology engine built on SPL Pure Core V8.0 — deterministic emotion, trauma, memory, trust, and defense mechanisms.
|
|
5
5
|
Author-email: NOHN-AI <ai@nohnlins.com>
|
|
6
6
|
Maintainer-email: NOHN-AI <ai@nohnlins.com>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine/feature/language_style.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spl_agent_engine-0.2.0 → spl_agent_engine-0.3.0}/spl_agent_engine.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|