# Tool Learning Lm

> Prove that tool-augmented learning unboundedly scales factual knowledge recall compared to parameter-constrained memorization, enabling efficient knowledge retrieval via external tools

- Skill: `adu2021/tool-learning-lm` (Agent Skill)
- Install (CLI): `npx skillmds@latest add adu2021/tool-learning-lm`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adu2021/tool-learning-lm/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: adu2021 (https://skillmd.com/u/adu2021)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/adu2021/tool-learning-lm

---


# Provable Benefits of In-Tool Learning for LLMs

## Core Concept

This work establishes theoretical and empirical foundations for why language models should retrieve knowledge from external tools rather than memorize facts in parameters. The core insight: a model's ability to memorize facts in weights is fundamentally limited by parameter count, while tool-augmented systems provide unbounded recall. Tool-learning represents a qualitative shift in how models acquire knowledge—from memorization to reasoning about retrieval.

## Architecture Overview

- **Parameter-Constrained Memorization**: Theoretical limit on facts a model can store in weights
- **Tool-Augmented Retrieval**: Unbounded recall via external knowledge systems
- **Circuit Construction**: Efficient mechanisms for integrating tool outputs
- **Training Efficiency**: Teaching tool-use more effective than finetuning facts
- **Hybrid Architecture**: Models become reasoning systems accessing external knowledge

## Implementation Steps

### Stage 1: Formalize Parameter Limits on Memorization

Establish theoretical bounds on how many facts can be memorized.

```python
# Theoretical analysis of parameter constraints
import math

class MemorizationBounds:
    """Analyze parameter limits on factual memorization"""

    def __init__(self, model_dim: int, num_params: int):
        self.model_dim = model_dim  # Hidden dimension
        self.num_params = num_params  # Total parameters
        self.embedding_dim = model_dim  # Typical embedding size

    def max_memorizable_facts(self) -> int:
        """
        Upper bound on distinct facts storable in weights.

        Theory: Each fact requires ~log(num_unique_facts) bits
        Available capacity ~ num_params * log2(precision)
        """
        # Assume 32-bit floats: 32 bits per parameter
        bits_per_param = 32
        total_bits = self.num_params * bits_per_param

        # Each fact requires bits to encode uniquely
        # Conservative estimate: ~log2(dict_size) bits per fact
        dict_size = 100000  # Typical knowledge base size

        bits_per_fact = math.log2(dict_size)

        max_facts = total_bits / bits_per_fact
        return int(max_facts)

    def example_limits(self):
        """Show limits for different model sizes"""
        model_sizes = {
            "7B": (4096, 7e9),
            "13B": (5120, 13e9),
            "70B": (8192, 70e9)
        }

        print("Maximum memorizable facts by model size:")
        for name, (dim, params) in model_sizes.items():
            bounds = MemorizationBounds(dim, params)
            max_facts = bounds.max_memorizable_facts()
            print(f"  {name}: ~{max_facts:,} facts")

        # Compare to knowledge base sizes
        print("\nKnowledge base sizes:")
        print(f"  Wikipedia: ~6.8M articles")
        print(f"  Wikidata: ~100M entities")
        print(f"  Web corpus: >100B documents")
        print("\nConclusion: Models cannot memorize all knowledge")
```

### Stage 2: Design Tool-Augmented Retrieval System

Create architecture for models to retrieve from external tools.

```python
# Tool-augmented retrieval architecture
from typing import List, Dict, Optional
import json

class ToolAugmentedModel:
    """Model with access to external tools for knowledge retrieval"""

    def __init__(self, base_model, tools: Dict[str, callable]):
        self.model = base_model
        self.tools = tools  # {"wikipedia": search_fn, "calculator": calc_fn}
        self.retrieval_history = []

    def generate_with_tools(
        self,
        prompt: str,
        max_tokens: int = 256,
        temperature: float = 0.7
    ) -> str:
        """Generate while dynamically using tools"""

        context = prompt
        num_tool_uses = 0

        while True:
            # Model generates next tokens
            completion = self.model.generate(
                context,
                max_tokens=50,  # Small chunks to check for tool use
                temperature=temperature,
                stop_sequences=["[TOOL:", "\n\n"]
            )

            # Check if model wants to use a tool
            if "[TOOL:" in completion:
                # Parse tool call
                tool_call = self.parse_tool_call(completion)
                tool_name = tool_call["name"]
                tool_args = tool_call["args"]

                # Execute tool
                result = self.tools[tool_name](**tool_args)

                # Add to context
                context += completion
                context += f"\n[RESULT: {result}]\n"

                # Record for analysis
                self.retrieval_history.append({
                    "tool": tool_name,
                    "args": tool_args,
                    "result": result
                })

                num_tool_uses += 1

            else:
                # No tool use, continue generation
                context += completion

                if len(context) - len(prompt) > max_tokens:
                    break

        return context[len(prompt):].strip()

    def parse_tool_call(self, text: str) -> Dict:
        """Parse [TOOL: name(args)] syntax"""
        import re
        match = re.search(r'\[TOOL:\s*(\w+)\((.*?)\)\]', text)
        if match:
            name = match.group(1)
            args_str = match.group(2)
            # Parse args (simplified)
            args = {"query": args_str}
            return {"name": name, "args": args}
        return {"name": None, "args": {}}
```

### Stage 3: Train Model to Learn Tool Use

Teach models to recognize when and how to use tools.

```python
# Training procedure for tool learning
class ToolLearningTrainer:
    """Train models to use tools effectively"""

    def __init__(self, model, tools: Dict[str, callable]):
        self.model = model
        self.tools = tools
        self.optimizer = model.optimizer_class(
            model.parameters(), lr=1e-4
        )

    def prepare_tool_learning_data(
        self,
        facts: List[Dict],
        num_examples: int = 1000
    ) -> List[Dict]:
        """
        Create training data for tool learning.
        Examples show when/how to use tools to answer questions.
        """
        training_data = []

        for fact in facts[:num_examples]:
            # Create examples with and without tools
            question = fact["question"]
            answer = fact["answer"]
            tool_type = fact.get("tool_type", "search")

            # Example 1: Tool-based answer (what we want to teach)
            tool_query = self.extract_query(question)
            tool_result = self.tools[tool_type](tool_query)

            training_data.append({
                "prompt": f"Q: {question}",
                "trajectory": [
                    {"action": "call_tool", "tool": tool_type, "query": tool_query},
                    {"action": "process_result", "result": tool_result},
                    {"action": "generate_answer", "text": answer}
                ],
                "label": "tool_based"  # Preferred approach
            })

            # Example 2: Direct answer (baseline)
            training_data.append({
                "prompt": f"Q: {question}",
                "trajectory": [
                    {"action": "generate_answer", "text": answer}
                ],
                "label": "direct"
            })

        return training_data

    def train_step(self, batch: List[Dict]) -> float:
        """Single training step"""
        total_loss = 0

        for example in batch:
            prompt = example["prompt"]
            trajectory = example["trajectory"]
            is_preferred = example["label"] == "tool_based"

            # Get log probs for trajectory actions
            log_probs = self.model.get_trajectory_log_probs(prompt, trajectory)

            # Use preference learning: upweight tool-based trajectories
            loss = -log_probs if is_preferred else 0.1 * log_probs

            total_loss += loss

        # Backward pass
        self.optimizer.zero_grad()
        (total_loss / len(batch)).backward()
        self.optimizer.step()

        return (total_loss / len(batch)).item()

    def extract_query(self, question: str) -> str:
        """Extract search query from question"""
        # Simple heuristic: remove question words
        question_words = {"what", "who", "when", "where", "why", "how"}
        words = [w for w in question.lower().split()
                 if w not in question_words and len(w) > 2]
        return " ".join(words[:5])
```

### Stage 4: Implement Efficient Tool Integration Circuits

Create lightweight mechanisms for integrating tool outputs.

```python
# Efficient tool integration circuit
import torch
from torch import nn

class ToolIntegrationCircuit(nn.Module):
    """Lightweight mechanism to integrate tool outputs into generation"""

    def __init__(self, hidden_dim: int):
        super().__init__()
        self.hidden_dim = hidden_dim

        # Linear projection to integrate tool results
        self.tool_result_proj = nn.Linear(hidden_dim, hidden_dim)
        self.integration_weight = nn.Linear(hidden_dim, 1)  # How much to use tool?

    def forward(
        self,
        model_hidden: torch.Tensor,  # [batch, seq_len, hidden_dim]
        tool_result: torch.Tensor,   # [batch, result_dim]
    ) -> torch.Tensor:
        """
        Integrate tool result with model hidden state.
        This is the key "circuit" that enables unbounded recall.
        """
        # Project tool result to hidden space
        tool_encoded = self.tool_result_proj(tool_result.unsqueeze(1))

        # Compute how much to integrate
        integration_logit = self.integration_weight(model_hidden)
        integration_weight = torch.sigmoid(integration_logit)

        # Blend: model hidden + tool information
        integrated = (1 - integration_weight) * model_hidden + \
                     integration_weight * tool_encoded

        return integrated


class ToolAugmentedLMHead(nn.Module):
    """LM head that can use tool results to modify logits"""

    def __init__(self, model_dim: int, vocab_size: int):
        super().__init__()
        self.base_head = nn.Linear(model_dim, vocab_size)
        self.tool_logit_adjustment = nn.Linear(model_dim, vocab_size)

    def forward(
        self,
        hidden: torch.Tensor,
        tool_boost: Optional[torch.Tensor] = None
    ) -> torch.Tensor:
        """
        Compute logits, optionally boosted by tool results.

        If tool retrieved the answer, boost its probability.
        """
        logits = self.base_head(hidden)

        if tool_boost is not None:
            # Upweight tokens that match tool results
            adjustment = self.tool_logit_adjustment(tool_boost)
            logits = logits + 0.5 * adjustment

        return logits
```

### Stage 5: Empirical Validation

Demonstrate practical benefits of tool-augmented learning.

```python
# Empirical evaluation framework
class ToolAugmentationEvaluator:
    """Compare tool-augmented vs. parameter-memorization approaches"""

    def __init__(self):
        self.results = {}

    def evaluate_factual_recall(
        self,
        memorization_model,
        tool_augmented_model,
        test_facts: List[Dict],
        num_facts: int = 1000
    ) -> Dict:
        """Compare accuracy on factual questions"""

        print(f"Evaluating on {num_facts} factual questions...")

        memo_correct = 0
        tool_correct = 0

        for fact in test_facts[:num_facts]:
            question = fact["question"]
            ground_truth = fact["answer"]

            # Test memorization model
            memo_answer = memorization_model.generate(question, max_length=50)
            if self.is_correct(memo_answer, ground_truth):
                memo_correct += 1

            # Test tool-augmented model
            tool_answer = tool_augmented_model.generate_with_tools(question)
            if self.is_correct(tool_answer, ground_truth):
                tool_correct += 1

        memo_acc = memo_correct / num_facts
        tool_acc = tool_correct / num_facts

        print(f"Memorization accuracy: {memo_acc:.1%}")
        print(f"Tool-augmented accuracy: {tool_acc:.1%}")
        print(f"Improvement: +{(tool_acc - memo_acc):.1%}")

        return {
            "memorization_accuracy": memo_acc,
            "tool_accuracy": tool_acc,
            "improvement": tool_acc - memo_acc
        }

    def evaluate_training_efficiency(
        self,
        memorization_model,
        tool_augmented_model,
        training_budget: int = 1000  # examples
    ) -> Dict:
        """Compare training efficiency"""

        print(f"Training with {training_budget} examples...")

        # Train memorization model (memorize facts)
        memo_loss = self.train_memorization_model(
            memorization_model,
            training_budget
        )

        # Train tool model (learn to use tools)
        tool_loss = self.train_tool_model(
            tool_augmented_model,
            training_budget
        )

        print(f"Memorization loss: {memo_loss:.3f}")
        print(f"Tool-learning loss: {tool_loss:.3f}")

        return {
            "memorization_loss": memo_loss,
            "tool_learning_loss": tool_loss
        }

    def is_correct(self, prediction: str, ground_truth: str) -> bool:
        """Check if prediction matches ground truth"""
        # Simple substring match (in practice, use BLEU/F1)
        return ground_truth.lower() in prediction.lower()

    def train_memorization_model(self, model, budget: int) -> float:
        """Train model by memorizing facts"""
        # This is harder because facts don't fit in parameters
        losses = []
        for _ in range(budget):
            loss = model.train_step()
            losses.append(loss)
        return sum(losses) / len(losses)

    def train_tool_model(self, model, budget: int) -> float:
        """Train model to use tools"""
        # This converges faster because we're teaching reasoning
        losses = []
        for _ in range(budget):
            loss = model.train_step()
            losses.append(loss)
        return sum(losses) / len(losses)
```

## Practical Guidance

### When to Use Tool Learning

- Models need to handle knowledge beyond parameter capacity
- Factual accuracy is critical (medical, legal, financial domains)
- Knowledge updates frequently (news, current events)
- Training budget is limited (teaching retrieval < memorizing)

### When NOT to Use

- Ultra-low latency requirements (tool calls add overhead)
- Offline scenarios without tool access
- Reasoning requiring only commonsense knowledge
- Real-time systems where tool unavailability is critical

### Integration Guidelines

- **Tool Coverage**: Ensure tools cover 80%+ of needed knowledge
- **Latency Budget**: Tool calls typically add 50-500ms per lookup
- **Fallback Strategy**: Train models to provide best-effort answers when tools unavailable
- **Hybrid Approach**: Use tools for facts, parameters for reasoning patterns

### Theoretical Bounds Summary

- Parameter memorization: O(num_params) facts
- Tool-augmented recall: O(tool_database_size) facts (unbounded)
- Training efficiency: Tool-learning converges in fewer examples than memorization

## Reference

Provable Benefits of In-Tool Learning for LLMs. arXiv:2508.20755
- https://arxiv.org/abs/2508.20755

