跳到正文
DEV Community· Ahmad Al Dibo·· 3 小時前AI 評分49

使用 ArcLM 建立可重現的本機智能體工具(無需 GPU)

Build a Reproducible Local Agent Tool with ArcLM (No GPU Required)

AI 導讀

教程示範如何使用 ArcLM 建立可重現的本機計算器智能體,無需 GPU 或外部 LLM API。內容涵蓋以具型別的 Python 函數註冊工具、驗證參數、執行有限步數的智能體循環及檢視追蹤。範例以確定性的假模型適配器展示工具呼叫流程,並説明權限與敏感文字遮蓋;假模型不展示 LLM 的推理能力。

正文

Build a Reproducible Local Agent Tool with ArcLM (No GPU Required)

An AI agent becomes useful when it can do more than generate text. It needs a way to call functions, validate their inputs, execute operations, and return structured results.

But giving an agent access to tools introduces engineering questions:

  • What arguments can a tool accept?
  • How do you prevent an agent from running indefinitely?
  • How can you inspect which tools were called?
  • What happens when a tool needs permission to modify a file?
  • How do you avoid exposing sensitive information in diagnostic output?

In this tutorial, we'll build a small calculator agent with ArcLM.

We'll define a typed Python function, register it as a tool, execute a bounded agent loop, inspect its trace, and examine how permissions and redaction fit into the design.

No GPU or external LLM API is required. We'll use a deterministic fake model adapter so that the same example can be reproduced without API credentials or the variability of a real language model.

The fake model is only a test adapter. It demonstrates the tool-calling pipeline, not the reasoning capabilities of an LLM.

1. Install ArcLM

Create and activate a Python virtual environment, then install the lightweight ArcLM package:

python -m venv .venv

On Windows:

.venv\Scripts\activate

On macOS or Linux:

source .venv/bin/activate

Install ArcLM:

python -m pip install arclm

The agent runtime does not require PyTorch for this example. You don't need a GPU, a downloaded language model, or an API key.

For the implementation details, see the ArcLM agent tools example.

2. Define a Typed Tool

Let's start with a simple function that adds two integers.

from arclm.agents import (
    SideEffectLevel,
    ToolPermission,
    tool,
)


@tool(
    category="utilities",
    permission=ToolPermission.PURE,
    side_effect=SideEffectLevel.NONE,
)
def add_numbers(left: int, right: int) -> int:
    """Add two integers."""
    return left + right

There are only a few lines of implementation, but the metadata matters.

The @tool decorator exposes the function as an ArcLM tool. Its type hints describe the expected arguments and return type, while the docstring supplies a description.

The category field groups the tool with related functionality.

The permission and side-effect declarations describe its operational behavior:

  • ToolPermission.PURE identifies an operation that doesn't require access to external resources.
  • SideEffectLevel.NONE declares that the operation has no side effects.

The function itself remains ordinary Python. ArcLM supplies the tool abstraction around it.

This distinction is useful when building larger systems: the business logic can remain independent of the agent runtime.

3. Register the Tool

ArcLM provides ToolRegistry for managing tools by name.

Create a registry and register the function:

from arclm.agents import ToolRegistry

registry = ToolRegistry()
registry.register_tool(add_numbers)

registered_tool = registry.get_tool("add_numbers")

print(registered_tool.name)
print(registered_tool.description)
print(registered_tool.input_schema)

The registry gives you a consistent way to retrieve registered tools and inspect their metadata.

The input_schema describes the expected arguments. An agent adapter can use this information when deciding how to formulate a tool call.

This is more explicit than simply exposing arbitrary Python functions to a model.

A schema can describe what a function accepts, but it doesn't prove that a tool is safe. You still need to validate arguments and control access to operations with side effects.

ArcLM's tool abstraction includes argument validation before invoking the underlying function. See src/arclm/agents/tool.py.

4. Build a Deterministic Agent

A real language model would decide when to call add_numbers and what arguments to provide. That introduces an external dependency and potentially nondeterministic behavior.

For this tutorial, we'll remove that variable by implementing a minimal fake model.

It will do two things:

  1. Request a call to add_numbers with the arguments 2 and 5.
  2. Read the structured tool result and return a final response.

Create a file named calculator_agent.py and use the following complete example:

import json

from arclm.agents import (
    Agent,
    AgentRunner,
    ModelResponse,
    SideEffectLevel,
    ToolCall,
    ToolPermission,
    ToolRegistry,
    tool,
)
from arclm.security import redact_sensitive_text


# 1. Define a typed tool.

@tool(
    category="utilities",
    permission=ToolPermission.PURE,
    side_effect=SideEffectLevel.NONE,
)
def add_numbers(left: int, right: int) -> int:
    """Add two integers."""
    return left + right


# 2. Register the tool.

registry = ToolRegistry()
registry.register_tool(add_numbers)


# 3. Create a deterministic fake model adapter.

class DeterministicFakeModel:
    def __init__(self):
        self.calls = 0

    def generate_response(
        self,
        *,
        messages,
        tools,
        instructions,
        session,
    ):
        self.calls += 1

        if self.calls == 1:
            return ModelResponse(
                tool_calls=[
                    ToolCall(
                        id="call_1",
                        name="add_numbers",
                        arguments={
                            "left": 2,
                            "right": 5,
                        },
                    )
                ]
            )

        # Read the actual result returned by the tool.
        for message in reversed(messages):
            if message.role == "tool":
                result = json.loads(message.content)

                if result["success"]:
                    return ModelResponse(
                        content=f"The sum is {result['value']}."
                    )

                return ModelResponse(
                    error_code="tool_failed",
                    error_message="The calculator tool failed.",
                )

        return ModelResponse(
            error_code="missing_tool_result",
            error_message="No calculator result was received.",
        )


# 4. Configure and run the agent.

agent = Agent(
    name="calculator",
    instructions="Use the available calculator tool to add integers.",
    model=DeterministicFakeModel(),
    tools=registry.list_tools(),
)

runner = AgentRunner(max_steps=2)

result = runner.run(
    agent,
    "Add 2 and 5.",
)

if not result.success:
    raise RuntimeError(
        f"Agent run failed: {result.error_code}"
    )


# 5. Inspect the result and execution trace.

report = {
    "success": result.success,
    "output": result.output,
    "tool_calls": [
        call.name for call in result.session.tool_calls
    ],
    "trace_events": [
        event.event_type for event in result.trace.events
    ],
}

print(json.dumps(report, indent=2))


# 6. Demonstrate sensitive-text redaction.

sample = "api_key=demo-secret"
print(redact_sensitive_text(sample))

Run the script:

python calculator_agent.py

This code is intended to produce a successful calculator run followed by a redaction demonstration. Execute it with your installed ArcLM version and capture the actual terminal output before publishing the tutorial.

What happens inside the agent loop?

The execution follows a sequence:

  1. The runner sends the conversation and available tool descriptions to the fake model.
  2. The model returns a structured ToolCall.
  3. The runner checks whether the tool exists and is allowed.
  4. ArcLM validates the arguments and invokes the function.
  5. The tool result is added to the session.
  6. The fake model reads that result and returns a final response.

Notice that the second response derives its answer from the returned tool result rather than hardcoding the number 7.

This makes the example useful for examining the interaction between an agent, a tool, and a model adapter.

5. Why Bound the Agent Loop?

The line below is an important part of the implementation:

runner = AgentRunner(max_steps=2)

An agent that repeatedly requests tools without reaching a final response can consume resources indefinitely unless execution is constrained.

max_steps limits the number of model-response steps in a run. In this example, two steps are sufficient: one tool request and one final response.

The limit is not a guarantee that every run will succeed. The model could request a nonexistent tool, return an invalid response, or fail to produce a final answer.

ArcLM represents execution outcomes through structured result objects, which lets the caller inspect success and failure without relying exclusively on printed text.

You can explore the implementation in src/arclm/agents/runner.py.

6. Add Permission and Side-Effect Controls

A calculator is a convenient first tool because it doesn't modify external state.

A file-writing tool is different. It may overwrite data, create files in unintended locations, or expose information to another process.

ArcLM separates permission declarations from side-effect classification.

For example, a tool that writes to a local workspace could declare:

from arclm.agents import (
    SideEffectLevel,
    ToolPermission,
    tool,
)


@tool(
    category="filesystem",
    permission=ToolPermission.WRITE_LOCAL,
    side_effect=SideEffectLevel.REVERSIBLE,
)
def write_report(path: str, content: str) -> str:
    """Write a report to an approved workspace location."""
    # Implement explicit path validation and file-writing policy here.
    ...

This is a classification example, not a file-writing implementation. A production tool also needs to validate its destination, restrict allowed paths, handle errors, and define overwrite behavior.

The distinction between the two metadata fields is important:

  • permission declares the kind of access a tool requires.
  • side_effect describes the operational consequences of the tool.

Declaring a side effect alone does not enforce approval. ArcLM's approval policy uses the tool's permission classification and the configured ToolPolicy.

By default, permissions such as WRITE_LOCAL, READ_NETWORK, EXTERNAL_ACTION, and SENSITIVE require approval. The default approval handler denies operations that require approval unless an appropriate handler is configured.

For a real application, provide a ToolApprovalHandler through AgentRunner(approval_handler=...) and define how approval is requested, granted, denied, and audited.

Never treat a descriptive tool schema as a substitute for access control.

7. Redact Sensitive Information

Tool outputs and execution logs can contain credentials, tokens, or other sensitive values.

ArcLM provides a redaction utility:

from arclm.security import redact_sensitive_text

sample = "api_key=demo-secret"

safe_text = redact_sensitive_text(sample)

print(safe_text)

The intended transformation is:

api_key=[REDACTED]

ArcLM also redacts tool-result payloads when trace redaction is enabled by the agent's security policy.

However, redaction has limits. It detects supported patterns; it cannot guarantee that every secret or item of personal information will be recognized.

It is also important to distinguish tool-result redaction from complete session sanitization. For example, the session records tool-call arguments. You should not pass real credentials as tool arguments simply because result redaction is enabled.

For sensitive workflows:

  • Avoid putting secrets into prompts and tool arguments.
  • Keep credentials in an appropriate secret-management mechanism.
  • Minimize what you include in diagnostic output.
  • Test redaction with representative examples from your application.

The relevant implementation is available in src/arclm/security/redaction.py.

8. Inspect the Trace

A final answer tells you what the agent returned. A trace helps explain how it reached that outcome.

The example collects the trace event names using:

trace_events = [
    event.event_type
    for event in result.trace.events
]

The runner records events such as:

  • run_started
  • model_responded
  • tool_requested
  • tool_started
  • tool_completed
  • step_completed
  • run_completed

These events help answer practical debugging questions.

Did the model request a tool? Did the runner start it? Did it complete? Did the agent reach a final response or exhaust its step limit?

For the current example, inspecting event names is enough. In a larger application, you can also examine the structured trace and result metadata.

Before persisting traces, decide what information should be retained, who can access it, and how sensitive data should be handled.

What This Example Demonstrates

This small example separates several responsibilities that are often mixed together in agent implementations:

Tools define capabilities. A typed function exposes a specific operation with a discoverable schema.

The registry manages available tools. Tools can be registered and selected explicitly.

The adapter defines model interaction. Our deterministic adapter demonstrates the protocol without requiring a provider.

The runner controls execution. It validates tool calls, coordinates results, and limits the number of steps.

Permissions and redaction address different security concerns. Permissions govern access and approval, while redaction reduces exposure of recognized sensitive values.

This is not a production-ready autonomous agent, nor does it demonstrate real language-model reasoning. It is a small, inspectable foundation for understanding how tool-calling systems fit together.

If you want to go further, start with the ArcLM repository and inspect the canonical custom tool agent example.

The next experiment is to replace the fake adapter with a real model adapter while keeping the same tool boundary, step limit, permission policy, and trace inspection.

That separation is the useful part: you can change the model backend without having to redesign the tool's Python implementation.

來源:DEV Community · dev.to