# How to Integrate Custom Code or Models with learn-claude-code

> Integrate custom code or models with learn-claude-code by registering Python callables or replacing the global client. Enhance your LLM SDK effortlessly.

- Repository: [shareAI-Lab/learn-claude-code](https://github.com/shareAI-lab/learn-claude-code)
- Tags: how-to-guide
- Published: 2026-03-08

---

**You can integrate custom code or models with learn-claude-code by registering new Python callables in the `TOOL_HANDLERS` dispatch map and updating the `TOOLS` schema list, or by replacing the global `client` variable with your preferred LLM SDK.**

The **shareAI-lab/learn-claude-code** repository is built as a set of tiny, interchangeable Python modules centered around a single entry point: the `agent_loop(messages: list)` function. All capabilities—including shell commands, file I/O, and background tasks—are provided through tools registered in a central dispatch map, while the language model client is instantiated once at module import time and referenced globally. This design allows you to extend functionality or swap the underlying LLM by touching only two integration points without breaking the core execution flow.

## Understanding the Modular Architecture

The framework separates concerns into three layers that remain stable regardless of your customizations:

- **`agent_loop` in [`agents/s01_agent_loop.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s01_agent_loop.py)** – The invariant core loop that processes messages and dispatches tool calls. It consumes the `response` object’s `content` and `stop_reason` fields generically, so it works with any LLM that returns a compatible structure.
- **`TOOL_HANDLERS` in [`agents/s02_tool_use.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s02_tool_use.py)** – A Python dictionary mapping tool names to callables. New capabilities are added here (lines 93-101) without touching the loop logic.
- **Global `client` in [`agents/s_full.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s_full.py)** – The LLM client instance created at import time (lines 48-58). Every agent file references this global variable, making model swaps a single-line change.

Because `agent_loop` never changes, adding new behavior or swapping models never breaks the existing flow.

## Adding Custom Tools

To integrate your own code, implement a Python callable and register it in two places: the dispatch map and the schema list.

### Step 1: Implement the Tool Logic

Create a safe, side-effect-free function in a new file or inline. The following example implements a secure arithmetic evaluator called `calc`:

```python

# agents/custom_tools.py

import ast
import operator

_OPS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.Pow: operator.pow,
}

def safe_eval(expr: str) -> str:
    """Evaluate a simple arithmetic expression without side-effects."""
    node = ast.parse(expr, mode="eval").body

    def _eval(node):
        if isinstance(node, ast.Num):
            return node.n
        if isinstance(node, ast.BinOp):
            left = _eval(node.left)
            right = _eval(node.right)
            op = _OPS[type(node.op)]
            return op(left, right)
        raise ValueError("Unsupported expression")

    try:
        return str(_eval(node))
    except Exception as e:
        return f"Error: {e}"

```

### Step 2: Register in TOOL_HANDLERS

Import your function into the main agent file and add it to the dispatch map. In [`agents/s_full.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s_full.py) (or any `sXX_*.py` you run), update the `TOOL_HANDLERS` dictionary:

```python
from agents.custom_tools import safe_eval

# Extend the existing dispatch map

TOOL_HANDLERS.update({
    "calc": lambda **kw: safe_eval(kw["expr"]),
})

```

### Step 3: Update the TOOLS Schema

Add a JSON schema entry so the LLM knows how to call your tool. In the same file, append to the `TOOLS` list:

```python
TOOLS.append({
    "name": "calc",
    "description": "Evaluate a simple arithmetic expression safely.",
    "input_schema": {
        "type": "object",
        "properties": {
            "expr": {"type": "string", "description": "e.g. '3 * (7 + 2)'"}
        },
        "required": ["expr"]
    },
})

```

Now you can invoke the agent with `/calc 3 * (7 + 2)` and the `agent_loop` will automatically dispatch to your `safe_eval` function.

## Swapping the Language Model

To integrate your own models—whether from OpenAI, Ollama, or a self-hosted endpoint—replace the `Anthropic` client construction with your own SDK.

### Using OpenAI Models

Install the SDK (`pip install openai`) and modify [`agents/s_full.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s_full.py) to override the global `client` and adjust the call signature:

```python
from openai import OpenAI
import os

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
MODEL = "gpt-4o-mini"

def _create_message(**kwargs):
    return client.chat.completions.create(**kwargs)

# Inside agent_loop, replace the Anthropic call with:

response = _create_message(
    model=MODEL,
    messages=messages,
    tools=TOOLS,
    max_tokens=8000,
)

```

The rest of the codebase works unchanged because it only accesses `response.content` and `response.stop_reason`, which OpenAI’s structure also provides.

### Using Self-Hosted Ollama Models

For local inference via Ollama, install the client (`pip install ollama`) and adapt the request format:

```python
from ollama import Client as OllamaClient
import os
import json

client = OllamaClient(host=os.getenv("OLLAMA_HOST", "http://localhost:11434"))
MODEL = "llama3.2"

def _ollama_chat(messages, tools):
    # Flatten messages and embed tool specs via system prompt

    system = "\n".join([json.dumps(t) for t in tools])
    prompt = "\n".join([m["content"] for m in messages if m["role"] == "user"])
    return client.generate(
        model=MODEL,
        prompt=prompt,
        system=system,
        stream=False
    )

# Replace the Anthropic call inside agent_loop:

response = _ollama_chat(messages=messages, tools=TOOLS)

```

The exact JSON format depends on your Ollama version, but the principle remains: you only replace the request/response layer while the core `agent_loop` continues to dispatch tools based on the returned content.

## Loading Domain-Specific Knowledge

Beyond code and models, you can inject domain knowledge through the skill system. The [`agents/s05_skill_loading.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s05_skill_loading.py) file demonstrates how `SkillLoader` fetches markdown assets from the `skills/*/SKILL.md` directory tree. Place your own [`SKILL.md`](https://github.com/shareAI-lab/learn-claude-code/blob/main/SKILL.md) files in `skills/your_domain/` and the agent can load them on-demand via the `load_skill` tool, effectively integrating your documentation without code changes.

## Summary

- **Tool integration** requires updating `TOOL_HANDLERS` and `TOOLS` in [`agents/s_full.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s_full.py) (or any session file) with your Python callable and JSON schema.
- **Model integration** requires replacing the global `client` variable and the request logic in [`agents/s_full.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s_full.py), keeping the `agent_loop` unchanged.
- **Knowledge integration** utilizes the `skills/` directory and `SkillLoader` shown in [`agents/s05_skill_loading.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s05_skill_loading.py) to inject domain-specific documentation.
- The core `agent_loop` in [`agents/s01_agent_loop.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s01_agent_loop.py) remains invariant, ensuring your extensions never break the execution flow.

## Frequently Asked Questions

### Can I add multiple custom tools to learn-claude-code?

Yes. Simply add multiple entries to `TOOL_HANDLERS` and append multiple schemas to the `TOOLS` list in [`agents/s_full.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s_full.py). Each tool is independent, and the `agent_loop` dispatches based on the tool name returned by the LLM.

### Do I need to modify the core agent loop to integrate new models?

No. The `agent_loop` function in [`agents/s01_agent_loop.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s01_agent_loop.py) is designed to remain unchanged. You only replace the global `client` variable and the specific API call that generates the `response` object, as long as the replacement provides `content` and `stop_reason` fields.

### What format does the TOOLS schema require?

The `TOOLS` list expects JSON Schema objects with `name`, `description`, and `input_schema` keys, following the Anthropic tool use specification. The `input_schema` must define the tool’s arguments as an `object` type with `properties` and `required` fields so the LLM can generate valid calls.

### Can I use local models like Llama or Mistral with learn-claude-code?

Yes. As long as your local model is served via an HTTP API (such as Ollama, llama.cpp, or vLLM), you can replace the `client` initialization in [`agents/s_full.py`](https://github.com/shareAI-lab/learn-claude-code/blob/main/agents/s_full.py) with your SDK of choice and map the request/response formats accordingly. The tool dispatch system is agnostic to the specific LLM provider.