Skip to main content
This tutorial shows how to build a tool-using agent with Mellea and progressively add reliability layers: output requirements, retry budgets, and Guardian safety checks that detect harmful or off-topic responses before they reach your users. By the end you will have covered:
  • Building a tool-using agent with instruct() and ModelOption.TOOLS
  • Enforcing structured output with requirements and a retry budget
  • Inspecting SamplingResult to understand failures
  • Detecting harmful outputs with guardian.guardian_check
  • Grounding safety checks against retrieved context
Prerequisites: Tutorial 02 and Tutorial 03 complete, pip install mellea, Ollama running locally with granite4.1:3b downloaded.

Step 1: A simple tool-using agent

Start with two tools — a search stub and a calculator — and wire them into an instruct() call:
Sample output
Note: LLM output is non-deterministic. The model may call one or both tools; the answer will reference whichever tool results it received.
ChatContext is required here so tool result messages persist between the first instruct call and the follow-up. Without it, the session uses SimpleContext, which discards history between calls and the model answers as if no tools ran.

Step 2: Adding output requirements

Require the agent to format its answer as a short structured response:
Sample output
Note: With the 50-word requirement enforced, the response will be concise and answer both questions. Wording varies by model and temperature.
Requirements are placed on the synthesis call (the follow-up instruct) rather than the tool-call step — at the tool-call step the model returns structured tool invocations, not the final answer, so applying requirements there would validate the wrong output. The word-count check runs deterministically. The “answer both questions” requirement falls back to LLM-as-a-judge. If either fails, Mellea retries with the failure reason embedded in the repair request.

Step 3: Inspecting failures and handling a retry budget

Use RejectionSamplingStrategy with return_sampling_results=True to observe what happens when requirements fail:
On a successful run (the most common case):
Sample output
If all attempts exceed the word limit:
Sample output
sampling.success is True when at least one attempt satisfied all requirements. sampling.sample_generations gives you every attempt in order — useful for debugging or for choosing the best available output when the budget runs out.

Step 4: Adding Guardian harm detection

Guardian intrinsics evaluate the output against specific risk criteria. Run them after your agent responds to flag outputs before they reach downstream code.
Sample output
Note: Guardian intrinsics load LoRA adapters and require LocalHFBackend. They cannot run against OllamaModelBackend. The main agent and the Guardian checks can use different backends — only the Guardian calls need LocalHFBackend.
Scores are floats between 0.0 (safe) and 1.0 (risk detected); 0.5 is the threshold. The available criteria are: "harm", "jailbreak", "social_bias", "profanity", "violence", "unethical_behavior", "groundedness", "answer_relevance", "context_relevance", and "function_call".
Note: If you were using GuardianRisk.SEXUAL_CONTENT from the old API, there is no direct equivalent key in CRITERIA_BANK. Use a custom free-text criteria string instead — see Custom criteria in the Guardian how-to guide.

Step 5: Running multiple Guardian checks with a shared backend

When running several Guardian criteria checks, instantiate LocalHFBackend once and pass it to each call to avoid reloading the model weights for every check:
Sample output
Note: Guardian scores are deterministic for a given response string, but the agent’s answer varies by model and temperature, so exact scores will differ across runs.
The available criteria are: "harm", "jailbreak", "social_bias", "profanity", "violence", "unethical_behavior", "groundedness", "answer_relevance", "context_relevance", and "function_call".
Note: If you were using GuardianRisk.SEXUAL_CONTENT from the old API, there is no direct equivalent key in CRITERIA_BANK. Use a custom free-text criteria string instead — see Custom criteria in the Guardian how-to guide.

Step 6: Groundedness checks with retrieved context

When your agent retrieves documents before answering, use rag.flag_hallucinated_content to confirm the response is faithful to what was retrieved rather than hallucinated:
Sample output
Tip: Pass the same text as both grounding_context to instruct() and as the Document to flag_hallucinated_content. This ensures the faithfulness check evaluates the response against exactly what the agent was given.

Step 7: A ReACT agent with Guardian checks

For goal-driven agentic loops, combine react() with Guardian validation. The react() function is an async built-in that runs the Reason-Act loop until the goal is reached or the step budget is exhausted:
Sample output
Note: LLM output is non-deterministic. The react() loop runs sequentially — each turn awaits the model before proceeding, so ChatContext is safe to use here despite the async execution. The agent calls web_search to ground its answer, then calls the built-in final_answer tool to signal completion. The Guardian harm check runs after the loop using LocalHFBackend with the guardian-core LoRA adapter. Your exact wording will vary.

Advanced: react() implements the Reason + Act loop: the LLM alternates between producing a reasoning step (“Thought”) and invoking a tool (“Action”) until it determines the goal is satisfied or the step budget runs out. You can inspect the intermediate steps via the second return value (the trace list). For fine-grained control over each reasoning step, build a custom loop using m.instruct() with ModelOption.TOOLS directly.

What you built

A progression from a basic tool-using agent to a safety-validated, grounded agentic system:
See also: The Requirements System | Tools and Agents