|
20 | 20 | import unittest |
21 | 21 | from unittest import mock |
22 | 22 |
|
23 | | -import langchain.chat_models as chat_models |
24 | 23 | from langchain_core.messages import AIMessage, HumanMessage |
25 | 24 |
|
26 | 25 | from fastapi_startkit.ai.agent import Agent |
@@ -255,86 +254,79 @@ async def test_passes_when_judge_approves(self): |
255 | 254 | self.setup_agent("Hello there, welcome!") |
256 | 255 | with tempfile.TemporaryDirectory() as tmp: |
257 | 256 | with mock.patch.object( |
258 | | - RecordingAgent, "_judge_live", return_value={"passed": True, "reasoning": "greets the user"} |
| 257 | + RecordingAgent, |
| 258 | + "_judge_live", |
| 259 | + mock.AsyncMock(return_value={"passed": True, "reasoning": "greets the user"}), |
259 | 260 | ): |
260 | 261 | with SimpleAgent.record(os.path.join(tmp, "c.json")) as agent: |
261 | 262 | await agent.prompt("hello") |
262 | | - agent.assert_response_judged( |
| 263 | + await agent.assert_response_judged( |
263 | 264 | model="gpt-3.5-turbo", expectation="The llm should respond with greetings" |
264 | 265 | ) |
265 | 266 |
|
266 | 267 | async def test_fails_when_judge_rejects(self): |
267 | 268 | self.setup_agent("Completely unrelated content") |
268 | 269 | with tempfile.TemporaryDirectory() as tmp: |
269 | 270 | with mock.patch.object( |
270 | | - RecordingAgent, "_judge_live", return_value={"passed": False, "reasoning": "not a greeting"} |
| 271 | + RecordingAgent, |
| 272 | + "_judge_live", |
| 273 | + mock.AsyncMock(return_value={"passed": False, "reasoning": "not a greeting"}), |
271 | 274 | ): |
272 | 275 | with SimpleAgent.record(os.path.join(tmp, "c.json")) as agent: |
273 | 276 | await agent.prompt("hello") |
274 | 277 | with self.assertRaises(AssertionError): |
275 | | - agent.assert_response_judged( |
| 278 | + await agent.assert_response_judged( |
276 | 279 | model="gpt-3.5-turbo", expectation="The llm should respond with greetings" |
277 | 280 | ) |
278 | 281 |
|
279 | 282 | async def test_verdict_is_cached_in_the_cassette_and_not_re_judged(self): |
280 | 283 | self.setup_agent("Hello there!") |
281 | | - judge = mock.Mock(return_value={"passed": True, "reasoning": "ok"}) |
| 284 | + judge = mock.AsyncMock(return_value={"passed": True, "reasoning": "ok"}) |
282 | 285 | with tempfile.TemporaryDirectory() as tmp: |
283 | 286 | cassette = os.path.join(tmp, "c.json") |
284 | 287 | with mock.patch.object(RecordingAgent, "_judge_live", judge): |
285 | 288 | with SimpleAgent.record(cassette) as agent: |
286 | 289 | await agent.prompt("hello") |
287 | | - agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
288 | | - agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
| 290 | + await agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
| 291 | + await agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
289 | 292 |
|
290 | 293 | judge.assert_called_once() |
291 | 294 |
|
292 | 295 | async def test_verdict_persists_to_disk_for_a_later_replay(self): |
293 | 296 | self.setup_agent("Hello there!") |
294 | 297 | with tempfile.TemporaryDirectory() as tmp: |
295 | 298 | cassette = os.path.join(tmp, "c.json") |
296 | | - with mock.patch.object(RecordingAgent, "_judge_live", return_value={"passed": True, "reasoning": "ok"}): |
| 299 | + with mock.patch.object( |
| 300 | + RecordingAgent, "_judge_live", mock.AsyncMock(return_value={"passed": True, "reasoning": "ok"}) |
| 301 | + ): |
297 | 302 | with SimpleAgent.record(cassette) as agent: |
298 | 303 | await agent.prompt("hello") |
299 | | - agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
| 304 | + await agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
300 | 305 |
|
301 | | - judge = mock.Mock(side_effect=AssertionError("must not be called on replay")) |
| 306 | + judge = mock.AsyncMock(side_effect=AssertionError("must not be called on replay")) |
302 | 307 | with mock.patch.object(RecordingAgent, "_judge_live", judge): |
303 | 308 | with SimpleAgent.record(cassette) as agent: |
304 | 309 | await agent.prompt("hello") |
305 | | - agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
| 310 | + await agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
306 | 311 |
|
307 | 312 | judge.assert_not_called() |
308 | 313 |
|
309 | 314 | async def test_fails_when_no_prompt_has_been_made(self): |
310 | 315 | with tempfile.TemporaryDirectory() as tmp: |
311 | 316 | with SimpleAgent.record(os.path.join(tmp, "c.json")) as agent: |
312 | 317 | with self.assertRaises(AssertionError): |
313 | | - agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
314 | | - |
315 | | - |
316 | | -class TestJudgeLiveModelCall(unittest.TestCase): |
317 | | - def test_calls_init_chat_model_and_parses_json_verdict(self): |
318 | | - captured = {} |
319 | | - |
320 | | - class FakeResult: |
321 | | - content = '{"passed": true, "reasoning": "Greets the user politely."}' |
322 | | - |
323 | | - class FakeModel: |
324 | | - def invoke(self, prompt): |
325 | | - captured["prompt"] = prompt |
326 | | - return FakeResult() |
327 | | - |
328 | | - patcher = mock.patch.object(chat_models, "init_chat_model", lambda *a, **k: FakeModel()) |
329 | | - patcher.start() |
330 | | - self.addCleanup(patcher.stop) |
| 318 | + await agent.assert_response_judged(model="gpt-3.5-turbo", expectation="greet") |
331 | 319 |
|
332 | | - agent = RecordingAgent(SimpleAgent()) |
333 | | - verdict = agent._judge_live("gpt-3.5-turbo", "The llm should respond with greetings", "Hello there!") |
| 320 | + async def test_provider_is_forwarded_to_the_judge(self): |
| 321 | + self.setup_agent("Hello there!") |
| 322 | + judge = mock.AsyncMock(return_value={"passed": True, "reasoning": "ok"}) |
| 323 | + with tempfile.TemporaryDirectory() as tmp: |
| 324 | + with mock.patch.object(RecordingAgent, "_judge_live", judge): |
| 325 | + with SimpleAgent.record(os.path.join(tmp, "c.json")) as agent: |
| 326 | + await agent.prompt("hello") |
| 327 | + await agent.assert_response_judged(model="gpt-3.5-turbo", provider="openai", expectation="greet") |
334 | 328 |
|
335 | | - self.assertTrue(verdict["passed"]) |
336 | | - self.assertIn("Greets", verdict["reasoning"]) |
337 | | - self.assertIn("Hello there!", captured["prompt"]) |
| 329 | + judge.assert_called_once_with("gpt-3.5-turbo", "greet", "Hello there!", "openai") |
338 | 330 |
|
339 | 331 |
|
340 | 332 | class TestExistingRecordApiIsUnaffected(unittest.IsolatedAsyncioTestCase): |
|
0 commit comments