KevinIsInCoding Claude Sonnet 4.6 commited on
Commit
5b5c80a
·
unverified ·
1 Parent(s): a3080f5

perf: add prompt caching to intake and research agents (#22)

Browse files

Wraps 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>

Files changed (3) hide show
  1. agents/intake.py +7 -6
  2. agents/research.py +5 -4
  3. 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 = []