Lucas
Add detailed parser trace logging
a1e6f13
Raw
History Blame Contribute Delete
4.55 kB
from functools import lru_cache
import logging
import os
from time import perf_counter
from training_coach.parser import (
PARSER_MODEL,
build_parser_messages,
log_parser_messages,
log_parser_response_text,
parse_model_response,
)
from training_coach.models import ParsedCheckIn
MODEL_CACHE_ENV_VAR = "PARSER_MODEL_CACHE_DIR"
DEFAULT_TRANSFORMERS_MODEL = "Qwen/Qwen3-1.7B"
DEFAULT_MAX_NEW_TOKENS = 384
DEFAULT_ZEROGPU_DURATION_SECONDS = 120
logger = logging.getLogger(__name__)
class ParserRuntimeUnavailableError(RuntimeError):
pass
def _gpu_decorator():
try:
import spaces
except ImportError:
return lambda function: function
duration = int(
os.getenv("ZEROGPU_DURATION_SECONDS", str(DEFAULT_ZEROGPU_DURATION_SECONDS))
)
return spaces.GPU(duration=duration)
def _load_transformers():
try:
from transformers import AutoModelForCausalLM, AutoTokenizer
except ImportError as error:
raise ParserRuntimeUnavailableError(
"Install transformers, torch, and accelerate to run the parser model."
) from error
return AutoModelForCausalLM, AutoTokenizer
@lru_cache(maxsize=1)
def load_parser_model(model_name: str = DEFAULT_TRANSFORMERS_MODEL):
start_time = perf_counter()
logger.info("event=parser_model_load_start model=%s", model_name)
AutoModelForCausalLM, AutoTokenizer = _load_transformers()
cache_dir = os.getenv(MODEL_CACHE_ENV_VAR) or None
tokenizer = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
model = AutoModelForCausalLM.from_pretrained(
model_name,
cache_dir=cache_dir,
device_map="auto",
torch_dtype="auto",
)
logger.info(
"event=parser_model_load_complete model=%s cache_dir_configured=%s elapsed_ms=%s",
model_name,
cache_dir is not None,
round((perf_counter() - start_time) * 1000),
)
return tokenizer, model
@_gpu_decorator()
def generate_parser_response(
raw_text: str,
model_name: str = DEFAULT_TRANSFORMERS_MODEL,
) -> str:
start_time = perf_counter()
logger.info(
"event=parser_generate_start model=%s text_chars=%s",
model_name,
len(raw_text),
)
tokenizer, model = load_parser_model(model_name)
messages = build_parser_messages(raw_text)
log_parser_messages(
backend="transformers",
model_name=model_name,
messages=messages,
)
chat_template_kwargs = {
"tokenize": False,
"add_generation_prompt": True,
"enable_thinking": False,
}
try:
prompt = tokenizer.apply_chat_template(messages, **chat_template_kwargs)
except TypeError:
chat_template_kwargs.pop("enable_thinking")
prompt = tokenizer.apply_chat_template(messages, **chat_template_kwargs)
inputs = tokenizer([prompt], return_tensors="pt").to(model.device)
input_token_count = inputs.input_ids.shape[-1]
output_ids = model.generate(
**inputs,
max_new_tokens=int(os.getenv("PARSER_MAX_NEW_TOKENS", str(DEFAULT_MAX_NEW_TOKENS))),
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
)
generated_ids = output_ids[:, inputs.input_ids.shape[-1] :]
generated_token_count = generated_ids.shape[-1]
response_text = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0].strip()
logger.info(
"event=parser_generate_complete model=%s prompt_chars=%s "
"input_tokens=%s generated_tokens=%s response_chars=%s elapsed_ms=%s",
model_name,
len(prompt),
input_token_count,
generated_token_count,
len(response_text),
round((perf_counter() - start_time) * 1000),
)
log_parser_response_text(
backend="transformers",
model_name=model_name,
response_text=response_text,
)
return response_text
def parse_check_in_with_model(
raw_text: str,
model_name: str | None = None,
) -> ParsedCheckIn:
model_name = model_name or os.getenv("PARSER_MODEL_ID", DEFAULT_TRANSFORMERS_MODEL)
logger.info("event=parser_model_parse_start model=%s", model_name)
response_text = generate_parser_response(raw_text, model_name=model_name)
parsed = parse_model_response(response_text)
logger.info(
"event=parser_model_parse_complete model=%s missing_fields=%s follow_up_questions=%s",
model_name,
len(parsed.missing_fields),
len(parsed.follow_up_questions),
)
return parsed