Skip to content

Commit 5227c89

Browse files
committed
feat(p8.3): 1h prompt caching TTL 启用 - ai/llm_client.py 增加 cache_ttl 参数
默认 "5m" (免费,会话内命中);可选 "1h" (cache_write +25% cost,跨会话命中) cache_control 改为条件构建:1h 时加 {"ttl": "1h"} 字段 其他不变 (5m 仍走默认 ephemeral)。 7 个 unit test 覆盖参数验证 (5m / 1h 合法,其他值 ValueError) Why: ADR-009 即将记录 - 重度用户一天内多次生成代码场景下,1h TTL cache_read 跨会话命中,省 ~70% input token (cache write +25% 只算一次)。
1 parent befd894 commit 5227c89

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

src/dbjavagenix/ai/llm_client.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,22 @@ def infer_names_via_llm(
147147
tables: List[Dict[str, Any]],
148148
model: str = "claude-sonnet-4-6",
149149
max_tokens: int = 4096,
150+
cache_ttl: str = "5m",
150151
) -> Optional[LLMResult]:
151152
"""用 Claude API 推断业务命名 (启用 prompt caching)。
152153
153154
Args:
154155
tables: [{name, columns, foreign_keys}] 列表
155156
model: Anthropic 模型 ID
156157
max_tokens: 最大输出 token
158+
cache_ttl: prompt caching TTL,"5m" (默认,免费) 或 "1h" (cache write +25%
159+
但跨会话命中)。1h 适合用户一天内多次生成代码的场景。
157160
158161
Returns:
159162
LLMResult 或 None (key 缺失 / 调用失败时)
160163
"""
164+
if cache_ttl not in ("5m", "1h"):
165+
raise ValueError(f"cache_ttl must be '5m' or '1h', got {cache_ttl!r}")
161166
if not is_llm_available():
162167
logger.info("LLM not available: ANTHROPIC_API_KEY missing or anthropic SDK not installed")
163168
return None
@@ -175,14 +180,17 @@ def infer_names_via_llm(
175180

176181
try:
177182
client = anthropic.Anthropic()
183+
cache_control_block = {"type": "ephemeral"}
184+
if cache_ttl == "1h":
185+
cache_control_block["ttl"] = "1h"
178186
response = client.messages.create(
179187
model=model,
180188
max_tokens=max_tokens,
181189
system=[
182190
{
183191
"type": "text",
184192
"text": NAMING_SYSTEM_PROMPT,
185-
"cache_control": {"type": "ephemeral"},
193+
"cache_control": cache_control_block,
186194
}
187195
],
188196
messages=[

tests/unit/test_llm_cache_ttl.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Unit tests for cache_ttl parameter in llm_client.infer_names_via_llm."""
2+
import pytest
3+
4+
from dbjavagenix.ai.llm_client import infer_names_via_llm
5+
6+
7+
class TestCacheTtlValidation:
8+
"""Tests for the cache_ttl parameter — these don't require ANTHROPIC_API_KEY
9+
since validation happens before the API call."""
10+
11+
def test_default_is_5m(self):
12+
# Default arg = "5m" — calling with no cache_ttl should not raise
13+
# ValueError. It may return None (no API key) but that's fine.
14+
result = infer_names_via_llm(tables=[])
15+
# result is None either because no API key, or because tables is empty
16+
# The point is: no ValueError was raised
17+
assert result is None or result.inferences == []
18+
19+
def test_explicit_5m_valid(self):
20+
result = infer_names_via_llm(tables=[], cache_ttl="5m")
21+
assert result is None or hasattr(result, "inferences")
22+
23+
def test_explicit_1h_valid(self):
24+
result = infer_names_via_llm(tables=[], cache_ttl="1h")
25+
assert result is None or hasattr(result, "inferences")
26+
27+
def test_invalid_ttl_raises(self):
28+
with pytest.raises(ValueError, match="cache_ttl"):
29+
infer_names_via_llm(tables=[], cache_ttl="30m")
30+
31+
def test_invalid_ttl_24h_raises(self):
32+
with pytest.raises(ValueError):
33+
infer_names_via_llm(tables=[], cache_ttl="24h")
34+
35+
def test_invalid_ttl_empty_raises(self):
36+
with pytest.raises(ValueError):
37+
infer_names_via_llm(tables=[], cache_ttl="")
38+
39+
def test_invalid_ttl_none_raises(self):
40+
with pytest.raises(ValueError):
41+
infer_names_via_llm(tables=[], cache_ttl=None)

0 commit comments

Comments
 (0)