This project has been created as part of the 42 curriculum by ayfadli.
An LLM function-calling tool powered by constrained decoding
Small language models are often unreliable at producing structured output such as JSON. They can add commentary, break syntax, or hallucinate keys when asked to generate a function call.
Call Me Maybe solves that problem by constraining generation token by token. The project loads function schemas, guides the model with a structured prompt, and uses a custom decoding filter to keep output aligned with the expected JSON shape, ensuring near-perfect reliability even with a small 0.6B parameter model.
- Python 3.10 or later
uv(for package and environment management)
Install the project dependencies with one of the following commands:
make installuv syncThe core project depends on numpy and pydantic. The provided llm_sdk uses torch and transformers internally to interact with the LLM.
Run the main module with the default input and output directories:
uv run python -m srcBy default, the program will read input files from the data/input/ directory and write output to the data/output/ directory.
You can also override the input, function-definition, and output paths:
uv run python -m src \
--functions_definition data/input/functions_definition.json \
--input data/input/function_calling_tests.json \
--output data/output/function_calling_results.jsonTypical workflow:
# 1. Install dependencies
make install
# 2. Run the program with default paths
uv run python -m src
# 3. Check the structured output
cat data/output/function_calling_results.jsonThe main decoding logic lives in src/vocab_parser.py. Instead of letting the model freely generate text, the program masks token probabilities and only keeps tokens that fit the current point in the JSON structure.
The process is split into phases:
- The model is forced to emit the fixed prefix
{"name":". - Only function names from the input schema are allowed while the name is being generated.
- After the function name, the decoder forces the bridge
","parameters":{. - The parameter phase restricts tokens to allowable types (string, number, boolean) according to the schema constraints.
- Generation stops once the closing braces are produced or the token limit is reached.
- The modified logits ensure the chosen token maintains structural and semantic JSON validity by setting invalid tokens' logits to negative infinity before sampling.
- CLI and I/O Parsing:
src/__main__.pyhandles CLI parsing, file operations, schema validation, and result serialization. - Constrained Decoding:
src/vocab_parser.pyfocuses entirely on filtering tokens based on the current JSON tree state. - Prompt Injection: Function definitions are loaded from JSON and injected into the initial prompt context.
- Validation: Pydantic models validate both the expected input schemas and the final extracted JSON results, strictly adhering to the specified types.
- Accuracy: Constrained decoding guarantees 100% syntactically valid JSON and schema-compliant outputs, yielding 90%+ correct function selection and argument extraction.
- Speed: By aggressively narrowing the search space through negative infinity logits for invalid tokens, the decoder maintains reasonable processing speeds. The program can process all test prompts in under 5 minutes on standard hardware.
- Reliability: Invalid token paths are pruned before sampling. The output is further verified to ensure it can be safely ingested by other systems.
- Tokenization Idiosyncrasies: Tokenizers preserve whitespaces and punctuation in surprising ways. Finding exact prefix matches required careful alignment between the current generated string and token string representations.
- JSON Syntax Overlaps: Tokens may contain multiple structural characters (e.g.,
": "). The logic required strict prefix checks to guarantee the model didn't skip necessary JSON keys. - Free-Form Parameters: While schema keys are strict, parameters are free-form strings, numbers, or booleans. Safely broadening the allowed token mask during parameter extraction without breaking JSON structure was a major technical hurdle.
The implementation was robustly tested against:
- Assorted argument types: string parameters with spaces, numeric arguments, and booleans.
- Various edge cases: empty strings, large numbers, and special characters inside parameters.
- Functions requiring multiple parameters.
- Invalid, malformed, or ambiguous prompts, verifying that the program gracefully handles errors without crashing and provides clear error messages.
- Edge cases involving missing input files or malformed input JSON.
- Pydantic documentation
- NumPy documentation
- JSON and constrained decoding concepts drawn from the project subject and related LLM function-calling materials.
- The
llm_sdkmodule to interface directly with the Qwen3-0.6B LLM viaget_logits_from_input_ids.
AI was used as a writing and review aid for documentation, especially to check wording, structure, and completeness against the subject requirements.