@@ -43,13 +43,18 @@ export class AnthropicModelService implements IModelService {
4343 const input = typeof prompt === 'string' ? prompt : this . applyCache ( toBaseMessages ( prompt ) ) ;
4444 const result = await m . invoke ( input , {
4545 ...( options ?. signal && { signal : options . signal } ) ,
46- ...( this . cacheControl && { cache_control : this . cacheControl } ) ,
4746 } ) ;
4847 return toChatMessage ( result ) ;
4948 }
5049
5150 bindTools ( tools : any [ ] ) : void {
52- this . boundModel = this . model ! . bindTools ( tools ) ;
51+ if ( this . cacheControl && tools . length > 0 ) {
52+ const formatted = ( this . model ! as any ) . formatStructuredToolToAnthropic ( tools ) ;
53+ formatted [ formatted . length - 1 ] . cache_control = this . cacheControl ;
54+ this . boundModel = ( this . model ! as any ) . withConfig ( { tools : formatted } ) ;
55+ } else {
56+ this . boundModel = this . model ! . bindTools ( tools ) ;
57+ }
5358 }
5459
5560 async invokeStructured < T = any > ( schema : any , prompt : string | ChatMessage [ ] , options ?: { signal ?: AbortSignal } ) : Promise < T > {
@@ -62,7 +67,6 @@ export class AnthropicModelService implements IModelService {
6267 const input = typeof messages === 'string' ? messages : this . applyCache ( toBaseMessages ( messages ) ) ;
6368 const lcStream = await m . stream ( input , {
6469 ...( options ?. signal && { signal : options . signal } ) ,
65- ...( this . cacheControl && { cache_control : this . cacheControl } ) ,
6670 } ) ;
6771 return ( async function * ( ) {
6872 let accumulated : AIMessageChunk | undefined ;
@@ -80,24 +84,33 @@ export class AnthropicModelService implements IModelService {
8084 }
8185
8286 private applyCache ( messages : BaseMessage [ ] ) : BaseMessage [ ] {
83- if ( ! this . cacheControl ) return messages ;
87+ if ( ! this . cacheControl || messages . length === 0 ) return messages ;
8488
89+ // breakpoint 1: system message (last block) — covers static + dynamic as a whole
90+ // system is built once per stream() call, so within a ReAct loop it's always identical
8591 for ( const msg of messages ) {
8692 if ( msg instanceof SystemMessage ) {
87- this . addCacheMarker ( msg ) ;
93+ this . addCacheMarker ( msg , 'last' ) ;
8894 break ;
8995 }
9096 }
9197
98+ // breakpoint 2: conversation history tail — next call reuses the entire prefix
99+ const last = messages [ messages . length - 1 ] ;
100+ if ( ! ( last instanceof SystemMessage ) ) {
101+ this . addCacheMarker ( last , 'first' ) ;
102+ }
103+
92104 return messages ;
93105 }
94106
95- private addCacheMarker ( message : BaseMessage ) : void {
107+ private addCacheMarker ( message : BaseMessage , position : 'first' | 'last' = 'first' ) : void {
96108 const content = message . content ;
97109 if ( typeof content === 'string' ) {
98110 message . content = [ { type : "text" , text : content , cache_control : this . cacheControl } ] ;
99111 } else if ( Array . isArray ( content ) && content . length > 0 ) {
100- content [ 0 ] = { ...content [ 0 ] , cache_control : this . cacheControl } ;
112+ const idx = position === 'last' ? content . length - 1 : 0 ;
113+ content [ idx ] = { ...content [ idx ] , cache_control : this . cacheControl } ;
101114 }
102115 }
103116}
0 commit comments