Single LLM call with animated progress and debug logging.
(
model: str, messages: list[dict], step_name: str, raise_on_truncation: bool = False, **kwargs
)
| 398 | |
| 399 | |
| 400 | def _llm_call( |
| 401 | model: str, messages: list[dict], step_name: str, raise_on_truncation: bool = False, **kwargs |
| 402 | ) -> str: |
| 403 | """Single LLM call with animated progress and debug logging.""" |
| 404 | messages = _prepare_messages(model, messages) |
| 405 | extra_headers = get_extra_headers() |
| 406 | if extra_headers: |
| 407 | kwargs.setdefault("extra_headers", extra_headers) |
| 408 | timeout = get_timeout() |
| 409 | if timeout is not None: |
| 410 | kwargs.setdefault("timeout", timeout) |
| 411 | logger.debug("LLM request [%s]:\n%s", step_name, _fmt_messages(messages)) |
| 412 | if kwargs: |
| 413 | logger.debug("LLM kwargs [%s]: %s", step_name, kwargs) |
| 414 | |
| 415 | spinner = _Spinner(step_name) |
| 416 | spinner.start() |
| 417 | t0 = time.time() |
| 418 | |
| 419 | response = litellm.completion(model=model, messages=messages, **kwargs) |
| 420 | content = response.choices[0].message.content or "" |
| 421 | truncated = _warn_if_truncated(response, step_name, kwargs.get("max_tokens")) |
| 422 | |
| 423 | spinner.stop(_format_usage(time.time() - t0, response.usage)) |
| 424 | logger.debug( |
| 425 | "LLM response [%s]:\n%s", step_name, content[:500] + ("..." if len(content) > 500 else "") |
| 426 | ) |
| 427 | if raise_on_truncation and truncated: |
| 428 | raise TruncatedResponseError( |
| 429 | f"LLM [{step_name}] hit the length limit; skipping to avoid a truncated page" |
| 430 | ) |
| 431 | return content.strip() |
| 432 | |
| 433 | |
| 434 | async def _llm_call_async( |