@@ -55,11 +55,10 @@ def _joined(value: Any) -> str:
5555 return "" .join (value ) if isinstance (value , list ) else value
5656
5757
58- class AgentModelFake :
58+ class AgentFake :
5959 """Registers a fixed, ordered list of replies as ``agent_cls``'s chat
6060 model for the duration of a ``with`` block (or a decorated function).
6161
62- Unlike the old pattern-matching stand-in, this swaps only the model —
6362 ``prompt()``/``stream()`` still run the real message-building, pipeline,
6463 and tool-execution path; see ``Ai.fake()``.
6564 """
@@ -111,7 +110,7 @@ def __repr__(self) -> str:
111110 return f"ToolCallView(name={ self .name !r} , args={ self .args !r} )"
112111
113112
114- class RecordingAgent (_Recorder ):
113+ class AgentRecordFake (_Recorder ):
115114 """Bound as ``agent`` by ``with Agent.record(cassette) as agent:``.
116115
117116 Fluent testing handle around a record-and-replay session: ``prompt()``
@@ -124,6 +123,11 @@ class RecordingAgent(_Recorder):
124123 cached to disk (keyed by the conversation history so far, plus the new
125124 message, so two sessions with different histories but the same latest
126125 message text don't collide). On a hit, it's replayed with no live call.
126+
127+ Entering the ``with`` block also binds this handle into the container
128+ under the agent class's name, so any other instance of that class
129+ created during the block (e.g. by application code under test) is
130+ routed through the same recording session.
127131 """
128132
129133 def __init__ (self , real : Agent , cassette : str | None = None , messages : list | None = None ) -> None :
@@ -159,7 +163,7 @@ def _key(self, message: str, attachments: list[Document] | None) -> str:
159163
160164 def _load (self ) -> tuple [Path , dict ]:
161165 cassette = self .cassette
162- assert cassette is not None , "RecordingAgent has no cassette resolved"
166+ assert cassette is not None , "AgentRecordFake has no cassette resolved"
163167 return cassette , (json .loads (cassette .read_text ()) if cassette .exists () else {})
164168
165169 def _save (self , cassette : Path , store : dict , key : str , value : Any ) -> None :
@@ -276,34 +280,25 @@ async def _judge_live(self, model: str, expectation: str, content: str, provider
276280 judge .provider = provider
277281 return await judge .judge (expectation , content )
278282
279-
280- class AgentBinding :
281- def __init__ (self , agent_cls : type [Agent ], stand_in : Any ) -> None :
282- self ._agent_cls = agent_cls
283- self ._stand_in = stand_in
284-
285283 def _resolve_cassette (self , filename : str , qualname : str ) -> None :
286- stand_in = self ._stand_in
287- if not isinstance (stand_in , RecordingAgent ):
288- return
289284 here = Path (filename ).parent
290- if stand_in .cassette is None :
291- stand_in .cassette = here / "cassettes" / f"{ qualname .replace ('.' , '_' )} .json"
292- elif not stand_in .cassette .is_absolute ():
293- stand_in .cassette = here / stand_in .cassette
285+ if self .cassette is None :
286+ self .cassette = here / "cassettes" / f"{ qualname .replace ('.' , '_' )} .json"
287+ elif not self .cassette .is_absolute ():
288+ self .cassette = here / self .cassette
294289
295- def __enter__ (self ) -> Any :
290+ def __enter__ (self ) -> "AgentRecordFake" :
296291 from fastapi_startkit .application import app
297292
298293 caller = sys ._getframe (1 ).f_code
299294 self ._resolve_cassette (caller .co_filename , caller .co_qualname )
300- app ().bind (self ._agent_cls .__name__ , self . _stand_in )
301- return self . _stand_in
295+ app ().bind (type ( self ._real ) .__name__ , self )
296+ return self
302297
303298 def __exit__ (self , * _exc : Any ) -> bool :
304299 from fastapi_startkit .application import app
305300
306- app ().unbind (self ._agent_cls .__name__ )
301+ app ().unbind (type ( self ._real ) .__name__ )
307302 return False
308303
309304 def __call__ (self , func : Callable ) -> Callable :
0 commit comments