MaruOpsInfrastructure and AI notes

About (Korean)Contact (Korean)

[Local LLM 21] Build an Agent Loop with an Inventory Lookup Tool

Desk with monitor and keyboard

Reference photo · Desk with monitor and keyboard · Photo: Luke Chesser lukechesser / Source / CC0 · Resized for listings; illustrative photo, not the tested environment

Building a Local LLM · 21/30 · Connect with an Agent

Full series and learning sequence · Practice code ZIP

A single inventory lookup tool is enough to show the boundary between a model’s proposal and the program’s execution. This example uses fictional equipment data and follows the request, argument validation and result handling. It does not grant file editing or shell execution.

Prepare a model that supports calling public functions and tools in Part 13. The data to be searched is limited to the list of virtual devices in the code.

Flow of request, inspection, and execution

The model asks for the name and arguments of the tool to use. It is up to the application to decide whether to actually do this. If you return the result as a tool message, the model reads it and continues to respond. This structure repeats Request → Inspection → Execution → Result → Answer.

Even if an enum is added to the schema, program argument checking is necessary. Model output is also treated as external input. The sample only performs permitted dictionary lookups and does not execute eval, arbitrary imports, or shell commands.

Allowed inventory lookup functions

We get 8B from the Qwen3 family, which we use in the official tool invocation example. If memory is insufficient, smaller tool-supported models are tested separately. Also see if the model answers by guessing inventory without calling the tool.

ollama pull qwen3:8b
import json
from llm_http import post

inventory = {"lab-gpu": 2, "lab-cpu": 5}
tools = [{"type": "function", "function": {
    "name": "get_stock", "description": "가상 실습 장비의 재고 수량 조회",
    "parameters": {"type": "object", "properties": {
        "product": {"type": "string", "enum": list(inventory)}},
        "required": ["product"], "additionalProperties": False}
}}]
messages = [{"role": "user", "content": "lab-gpu의 재고를 도구로 조회해서 알려 줘."}]
used = 0
for turn in range(3):
    response = post("/api/chat", {
        "model": "qwen3:8b", "messages": messages, "tools": tools,
        "think": False, "stream": False,
        "options": {"num_ctx": 4096, "num_predict": 256, "temperature": 0}
    })
    msg = response["message"]
    messages.append(msg)
    calls = msg.get("tool_calls") or []
    if not calls:
        print(msg.get("content", ""))
        break
    for call in calls:
        used += 1
        if used > 4:
            raise RuntimeError("Tool call limit exceeded")
        fn = call["function"]
        args = fn.get("arguments", {})
        if isinstance(args, str):
            args = json.loads(args)
        if fn["name"] != "get_stock" or not isinstance(args, dict):
            raise ValueError("Unsupported tool request")
        if set(args) != {"product"} or not isinstance(args["product"], str):
            raise ValueError("Unexpected arguments")
        if args["product"] not in inventory:
            raise ValueError("Unknown product")
        result = {"stock": inventory[args["product"]]}
        print("도구 실행:", args["product"], result)
        messages.append({"role": "tool", "tool_name": "get_stock",
                         "content": json.dumps(result)})
else:
    raise RuntimeError("No final answer within 3 turns")
.\.venv\Scripts\python.exe .\tool_loop.py

Execution log and final answer

Check the tool execution log and the stock=2 return value. Even if there is a 2 in the last sentence, if there is no query log, it is impossible to know whether the actual data was read. Conversely, if the search result is 2 but a different number is used in the answer, it is a problem in the creation stage. Record the two results separately.

Invalid names, missing arguments, and disallowed product names cause exceptions. The operations service adds observability by converting this error into something understandable to the user and not logging the full text.

Writing tools and approval steps

Just because it has been viewed does not immediately lead to file modification or email transmission. External influence tools are designed so that targets and changes are first visible and approved. We also need an identifier and idempotency to prevent duplicate execution on retries.

As the number of tools increases, so does the context for definitions and the scope to choose from. Show only the tools needed for the job and limit the number of turns, number of runs, and overall time. Regardless of which framework you use, the process for checking these boundaries is the same.

reference material

Documentation and model tags checked: October 18, 2026. Primary lab environment: Windows 11 x64.