Spaces:
Sleeping
Sleeping
KevinIsInCoding Claude Sonnet 4.6 commited on
perf: add prompt caching to intake and research agents (#22)
Browse filesWraps system prompts and tool lists with cache_control: ephemeral at all
five API call sites. Cached tokens are billed at 10% of normal input rate,
saving ~90% on repeated turns within a session.
Adds cached_system() and cached_tools() helpers to llm.py so the pattern
stays consistent and easy to update.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- agents/intake.py +7 -6
- agents/research.py +5 -4
- llm.py +12 -0
agents/intake.py
CHANGED
|
@@ -11,6 +11,7 @@ from rich.text import Text
|
|
| 11 |
|
| 12 |
from beacon_logging import get_logger
|
| 13 |
from config import INTAKE_MODEL
|
|
|
|
| 14 |
from models import PatientProfile, geocode_zip
|
| 15 |
from prompts import INTAKE_SYSTEM, lookup_disease_profile
|
| 16 |
from tools import INTAKE_TOOLS
|
|
@@ -55,8 +56,8 @@ def run_intake_agent(client: anthropic.Anthropic) -> PatientProfile:
|
|
| 55 |
response = client.messages.create(
|
| 56 |
model=INTAKE_MODEL,
|
| 57 |
max_tokens=1024,
|
| 58 |
-
system=f"Today's date is {today}.\n\n" + INTAKE_SYSTEM,
|
| 59 |
-
tools=INTAKE_TOOLS,
|
| 60 |
messages=messages,
|
| 61 |
)
|
| 62 |
|
|
@@ -132,8 +133,8 @@ def intake_greeting(client: anthropic.Anthropic, lang: str = "en") -> tuple[str,
|
|
| 132 |
response = client.messages.create(
|
| 133 |
model=INTAKE_MODEL,
|
| 134 |
max_tokens=1024,
|
| 135 |
-
system=system,
|
| 136 |
-
tools=INTAKE_TOOLS,
|
| 137 |
messages=messages,
|
| 138 |
)
|
| 139 |
text = next((b.text for b in response.content if b.type == "text"), "")
|
|
@@ -163,8 +164,8 @@ def stream_intake_turn(
|
|
| 163 |
with client.messages.stream(
|
| 164 |
model=INTAKE_MODEL,
|
| 165 |
max_tokens=1024,
|
| 166 |
-
system=system,
|
| 167 |
-
tools=INTAKE_TOOLS,
|
| 168 |
messages=new_msgs,
|
| 169 |
) as stream:
|
| 170 |
for chunk in stream.text_stream:
|
|
|
|
| 11 |
|
| 12 |
from beacon_logging import get_logger
|
| 13 |
from config import INTAKE_MODEL
|
| 14 |
+
from llm import cached_system, cached_tools
|
| 15 |
from models import PatientProfile, geocode_zip
|
| 16 |
from prompts import INTAKE_SYSTEM, lookup_disease_profile
|
| 17 |
from tools import INTAKE_TOOLS
|
|
|
|
| 56 |
response = client.messages.create(
|
| 57 |
model=INTAKE_MODEL,
|
| 58 |
max_tokens=1024,
|
| 59 |
+
system=cached_system(f"Today's date is {today}.\n\n" + INTAKE_SYSTEM),
|
| 60 |
+
tools=cached_tools(INTAKE_TOOLS),
|
| 61 |
messages=messages,
|
| 62 |
)
|
| 63 |
|
|
|
|
| 133 |
response = client.messages.create(
|
| 134 |
model=INTAKE_MODEL,
|
| 135 |
max_tokens=1024,
|
| 136 |
+
system=cached_system(system),
|
| 137 |
+
tools=cached_tools(INTAKE_TOOLS),
|
| 138 |
messages=messages,
|
| 139 |
)
|
| 140 |
text = next((b.text for b in response.content if b.type == "text"), "")
|
|
|
|
| 164 |
with client.messages.stream(
|
| 165 |
model=INTAKE_MODEL,
|
| 166 |
max_tokens=1024,
|
| 167 |
+
system=cached_system(system),
|
| 168 |
+
tools=cached_tools(INTAKE_TOOLS),
|
| 169 |
messages=new_msgs,
|
| 170 |
) as stream:
|
| 171 |
for chunk in stream.text_stream:
|
agents/research.py
CHANGED
|
@@ -8,6 +8,7 @@ import anthropic
|
|
| 8 |
from agents.eligibility import bulk_parse_and_strip
|
| 9 |
from beacon_logging import get_logger
|
| 10 |
from config import RESEARCH_MODEL
|
|
|
|
| 11 |
from models import PatientProfile
|
| 12 |
from prompts import RESEARCH_SYSTEM
|
| 13 |
from tools import RESEARCH_TOOLS
|
|
@@ -33,8 +34,8 @@ def run_research_agent(client: anthropic.Anthropic, profile: PatientProfile) ->
|
|
| 33 |
response = client.messages.create(
|
| 34 |
model=RESEARCH_MODEL,
|
| 35 |
max_tokens=8096,
|
| 36 |
-
system=RESEARCH_SYSTEM,
|
| 37 |
-
tools=RESEARCH_TOOLS,
|
| 38 |
messages=messages,
|
| 39 |
)
|
| 40 |
|
|
@@ -113,8 +114,8 @@ def stream_research_agent(
|
|
| 113 |
with client.messages.stream(
|
| 114 |
model=RESEARCH_MODEL,
|
| 115 |
max_tokens=8096,
|
| 116 |
-
system=LANGUAGE_DIRECTIVE[profile.lang] + RESEARCH_SYSTEM,
|
| 117 |
-
tools=RESEARCH_TOOLS,
|
| 118 |
messages=messages,
|
| 119 |
) as stream:
|
| 120 |
for chunk in stream.text_stream:
|
|
|
|
| 8 |
from agents.eligibility import bulk_parse_and_strip
|
| 9 |
from beacon_logging import get_logger
|
| 10 |
from config import RESEARCH_MODEL
|
| 11 |
+
from llm import cached_system, cached_tools
|
| 12 |
from models import PatientProfile
|
| 13 |
from prompts import RESEARCH_SYSTEM
|
| 14 |
from tools import RESEARCH_TOOLS
|
|
|
|
| 34 |
response = client.messages.create(
|
| 35 |
model=RESEARCH_MODEL,
|
| 36 |
max_tokens=8096,
|
| 37 |
+
system=cached_system(RESEARCH_SYSTEM),
|
| 38 |
+
tools=cached_tools(RESEARCH_TOOLS),
|
| 39 |
messages=messages,
|
| 40 |
)
|
| 41 |
|
|
|
|
| 114 |
with client.messages.stream(
|
| 115 |
model=RESEARCH_MODEL,
|
| 116 |
max_tokens=8096,
|
| 117 |
+
system=cached_system(LANGUAGE_DIRECTIVE[profile.lang] + RESEARCH_SYSTEM),
|
| 118 |
+
tools=cached_tools(RESEARCH_TOOLS),
|
| 119 |
messages=messages,
|
| 120 |
) as stream:
|
| 121 |
for chunk in stream.text_stream:
|
llm.py
CHANGED
|
@@ -5,6 +5,18 @@ import anthropic
|
|
| 5 |
import openai
|
| 6 |
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
def _normalize_messages(messages: list) -> list[dict]:
|
| 9 |
"""Convert LangGraph/LangChain message objects or dicts to {role, content} dicts."""
|
| 10 |
result = []
|
|
|
|
| 5 |
import openai
|
| 6 |
|
| 7 |
|
| 8 |
+
def cached_system(text: str) -> list[dict]:
|
| 9 |
+
"""Wrap a system prompt string for Anthropic prompt caching."""
|
| 10 |
+
return [{"type": "text", "text": text, "cache_control": {"type": "ephemeral"}}]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def cached_tools(tools: list[dict]) -> list[dict]:
|
| 14 |
+
"""Mark the last tool with cache_control so the full tool list is cached."""
|
| 15 |
+
if not tools:
|
| 16 |
+
return tools
|
| 17 |
+
return [*tools[:-1], {**tools[-1], "cache_control": {"type": "ephemeral"}}]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
def _normalize_messages(messages: list) -> list[dict]:
|
| 21 |
"""Convert LangGraph/LangChain message objects or dicts to {role, content} dicts."""
|
| 22 |
result = []
|