Deep Learning & LLMAgentsTool calling

Agents and Tools

Give a model access to typed application operations with annotated tool interfaces. Register their implementations, run an agent loop, and manage its context, history and authorization.

Declaring Agent Tools

Tools are ordinary typed Klyn methods. Declare the externally visible contract on an interface with @Tool, describe each model-provided argument with @Param, then keep the implementation focused on deterministic application logic.

The following examples continue from the provider configured in LLM Calls.

import klyn.collections
import klyn.llm

interface InventoryTools:

    @Tool(name="inventory_stock", description="Return the available quantity for one product")
    public stock(
        @Param(name="product", description="Product name")
        product as String
    ) as Int


class InventoryToolsImpl implements InventoryTools:

    private _stock as Map<String, Int> = {
        "keyboard": 12,
        "mouse": 7,
        "monitor": 3
    }

    public override stock(product as String) as Int:
        return this._stock.get(product.toLower(), 0)

Building the Mini Agent

Register the implementation, attach it to a session, and ask a goal-oriented question. The session performs the agent loop: it sends the prompt, executes requested tools, appends their results to history, and asks the model again until a final text response is produced.

tools = ToolRegistry.builder() \
    .add(InventoryTools, InventoryToolsImpl()) \
    .build()

agent = LLMChatSession(provider) \
    .withSystem("""You manage a small inventory.
Always call inventory_stock before answering a stock question.
Never invent quantities. Say that an item is unavailable when the tool returns 0.""") \
    .withTools(tools) \
    .withMaxToolCalls(6)

answer = agent.ask("Can I prepare five keyboard and mouse bundles?")
print(answer)
  1. The provider receives the tool JSON schema generated from the annotated interface.
  2. The model returns one or more ToolCall values.
  3. ToolRegistry validates/coerces arguments and invokes the registered Klyn method.
  4. The session stores each tool result and sends the augmented history back to the provider.
  5. The loop ends when the provider returns an assistant message without tool calls.

Context and Compaction

Long-running agents should configure the real context size of their model. When token usage reaches the selected percentage, the session can summarize older exchanges while preserving the system message and recent context.

agent.withContextWindow(128000, compactAtPercent=90) \
    .withCompactor(
        "Preserve decisions, tool results, constraints, and unresolved work.",
        threshold=0
    )

if agent.consumeCompactionFlag():
    print("Conversation context was compacted")

Replace 128000 with the context window advertised by the selected model. A LLMToolCallObserver can expose tool progress in a UI, and a LLMCompactionObserver can report compaction before it starts.

Resuming Sessions

LLMChatSession.withMessages() restores a typed history snapshot. The list is copied, preserving roles, tool results, call IDs and provider metadata. Last provider usage is reset; the restored context is immediately estimated, including structured tool calls and attached image budgets. Use messages() to obtain a snapshot and withHistoryObserver() to persist checkpoints after messages and compaction. Observer errors propagate instead of silently losing history. Keep the full archive separate from the compacted provider context.

saved = agent.messages()
resumed = LLMChatSession(provider).withMessages(saved)
resumed.withTools(tools)
print(resumed.ask("Continue the previous task."))

Use appendMessage() for user answers or other externally supplied messages: it updates accounting and history checkpoints immediately. contextUsage returns an immutable snapshot with tokens, estimated and estimatedToolTokens. Read it on the session's owner thread and publish the snapshot to a UI; do not read the mutable session concurrently. Tool schemas are estimated when attached, and provider-specific wire data can be counted through LLMTokenEstimator. Image budgets remain model-dependent heuristics, not exact counts or hard bounds. Reported usage describes the last provider request.

resumed.appendMessage(LLMThreadMessage.user("Use the new configuration."))
usage = resumed.contextUsage
print(usage.tokens, usage.estimated)

Applications can assign stable conversation IDs and persist checkpoints outside the working project. Store the complete message history and its provider metadata, and publish checkpoints atomically so an interrupted write does not destroy the previous conversation. Session selection and storage organization belong to the application, not to LLMChatSession.

Production Safety

  • Validate every tool argument again inside the implementation; model output is untrusted input.
  • Expose narrow business operations, not a generic shell, unrestricted SQL, or arbitrary file access.
  • Use withMaxToolCalls() to bound accidental loops and provider cost.
  • Apply authorization in tools. A system prompt is guidance, not an access-control boundary.
  • Keep API keys out of source, logs, prompts, tool results, and exception messages.
  • Catch provider exceptions at the application boundary and offer cancellation for interactive UIs.

Complete Samples

samples/llm/llm.kn demonstrates the low-level request API. samples/llm/Financial.kn is a larger interactive tool-calling agent. samples/llm/RunLLM.kn uses the same session API with a local model. For provider calls, see LLM Calls; for the local model, see Local LLM Inference. All three are listed on the Code Examples page.

Next Step

Continue with Local LLM Inference.