"""테스트 더블 — ScriptedLlm: 정해진 응답을 순서대로 내놓는 BaseLlm. LLM 호출 없이 파이프라인(가드→캐릭터→디렉터→도구→state)을 결정적으로 검증한다. 프로덕션 경로는 LiteLlm(engine/model.py) 그대로이며, 이 더블은 tests/ 전용이다. """ from __future__ import annotations from typing import AsyncGenerator from google.adk.models.base_llm import BaseLlm from google.adk.models.llm_request import LlmRequest from google.adk.models.llm_response import LlmResponse from google.genai import types from pydantic import ConfigDict def text_response(text: str) -> LlmResponse: return LlmResponse(content=types.Content(role="model", parts=[types.Part(text=text)])) def tool_call(name: str, **args) -> types.Part: return types.Part(function_call=types.FunctionCall(name=name, args=args)) def tool_response(*parts: types.Part) -> LlmResponse: return LlmResponse(content=types.Content(role="model", parts=list(parts))) class ScriptedLlm(BaseLlm): model_config = ConfigDict(arbitrary_types_allowed=True) model: str = "scripted" script: list[LlmResponse] = [] requests: list[LlmRequest] = [] # 검증용: 실제로 주입된 instruction 확인 async def generate_content_async( self, llm_request: LlmRequest, stream: bool = False ) -> AsyncGenerator[LlmResponse, None]: self.requests.append(llm_request) if not self.script: yield text_response("(대본 소진)") return yield self.script.pop(0)