# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

### Development
- Install core dependencies: `pip install -e .`
- Install dev dependencies: `pip install -e ".[dev]"`
- Install specific feature groups:
  - Document chat features: `pip install -e ".[doc-chat]"`
  - Database features: `pip install -e ".[db]"`
  - HuggingFace embeddings: `pip install -e ".[hf-embeddings]"`
  - All features: `pip install -e ".[all]"`
- Run linting and type checking: `make check`
- Format code: `make lint`

### Testing
- Run all tests: `pytest tests/`
- Run specific test: `pytest tests/main/test_file.py::test_function`
- Run tests with coverage: `pytest --cov=langroid tests/`
- Run only main tests: `make tests` (uses `pytest tests/main`)

### Linting and Type Checking
- Lint code: `make check` (runs black, ruff check, mypy)
- Format only: `make lint` (runs black and ruff fix)
- Type check only: `make type-check`
- Always use `make check` to run lints + mypy before trying to commit changes

### Version and Release Management
- Bump version: `./bump_version.sh [patch|minor|major]`
- Or use make commands:
  - `make all-patch` - Bump patch version, build, push, release
  - `make all-minor` - Bump minor version, build, push, release
  - `make all-major` - Bump major version, build, push, release

## Architecture

Langroid is a framework for building LLM-powered agents that can use tools and collaborate with each other.

### Core Components:

1. **Agents** (`langroid/agent/`):
   - `chat_agent.py` - Base ChatAgent that can converse and use tools
   - `task.py` - Handles execution flow for agents
   - `special/` - Domain-specific agents (doc chat, table chat, SQL chat, etc.)
   - `openai_assistant.py` - Integration with OpenAI Assistant API

2. **Tools** (`langroid/agent/tools/`):
   - Tool system for agents to interact with external systems
   - `tool_message.py` - Protocol for tool messages
   - Various search tools (Google, DuckDuckGo, Tavily, Exa, etc.)

3. **Language Models** (`langroid/language_models/`):
   - Abstract interfaces for different LLM providers
   - Implementations for OpenAI, Azure, local models, etc.
   - Support for hundreds of LLMs via LiteLLM

4. **Vector Stores** (`langroid/vector_store/`):
   - Abstract interface and implementations for different vector databases
   - Includes support for Qdrant, Chroma, LanceDB, Pinecone, PGVector, Weaviate

5. **Document Processing** (`langroid/parsing/`):
   - Parse and process documents from various formats
   - Chunk text for embedding and retrieval
   - Support for PDF, DOCX, images, and more

6. **Embedding Models** (`langroid/embedding_models/`):
   - Abstract interface for embedding generation
   - Support for OpenAI, HuggingFace, and custom embeddings

### Key Multi-Agent Patterns:

- **Task Delegation**: Agents can delegate tasks to other agents through hierarchical task structures
- **Message Passing**: Agents communicate by transforming and passing messages
- **Collaboration**: Multiple agents can work together on complex tasks

### Key Security Features:

- The `full_eval` flag in both `TableChatAgentConfig` and `VectorStoreConfig` controls code injection protection
- Defaults to `False` for security, set to `True` only in trusted environments

## Documentation

- Main documentation is in the `docs/` directory
- Examples in the `examples/` directory demonstrate usage patterns
- Quick start examples available in `examples/quick-start/`

## MCP (Model Context Protocol) Tools Integration

Langroid provides comprehensive support for MCP tools through the `langroid.agent.tools.mcp` module. Here are the key patterns and approaches:

### MCP Tool Creation Methods

#### 1. Using the `@mcp_tool` Decorator (Module Level)
```python
from langroid.agent.tools.mcp import mcp_tool
from fastmcp.client.transports import StdioTransport

transport = StdioTransport(command="...", args=[...])

@mcp_tool(transport, "tool_name")
class MyTool(lr.ToolMessage):
    async def handle_async(self):
        result = await self.call_tool_async()
        # custom processing
        return result
```

**Important**: The decorator creates the transport connection at module import time, so it must be used at module level (not inside async functions).

#### 2. Using `get_tool_async` (Inside Async Functions)
```python
from langroid.agent.tools.mcp.fastmcp_client import get_tool_async

async def main():
    transport = StdioTransport(command="...", args=[...])
    BaseTool = await get_tool_async(transport, "tool_name")
    
    class MyTool(BaseTool):
        async def handle_async(self):
            result = await self.call_tool_async()
            # custom processing
            return result
```

**Use this approach when**:
- Creating tools inside async functions
- Need to avoid event loop conflicts
- Want to delay transport creation until runtime

### Transport Types and Event Loop Considerations

- **StdioTransport**: Creates subprocess immediately, can cause "event loop closed" errors if created at module level in certain contexts
- **SSETransport**: HTTP-based, generally safer for module-level creation
- **Best Practice**: Create transports inside async functions when possible, use `asyncio.run()` wrapper for Fire CLI integration

### Tool Message Request Field and Agent Handlers

When you get an MCP tool named "my_tool", Langroid automatically:

1. **Sets the `request` field**: The dynamically created ToolMessage subclass has `request = "my_tool"`
2. **Enables custom agent handlers**: Agents can define these methods:
   - `my_tool()` - synchronous handler
   - `my_tool_async()` - async handler

The agent's message routing system automatically calls these handlers when the tool is used.

### Custom `handle_async` Method Override

Both decorator and non-decorator approaches support overriding `handle_async`:

```python
class MyTool(BaseTool):  # or use @mcp_tool decorator
    async def handle_async(self):
        # Get raw result from MCP server
        result = await self.call_tool_async()
        
        # Option 1: Return processed result to LLM (continues conversation)
        return f"<ProcessedResult>{result}</ProcessedResult>"
        
        # Option 2: Return ResultTool to terminate task
        return MyResultTool(answer=result)
```

### Common Async Issues and Solutions

**Problem**: "RuntimeError: asyncio.run() cannot be called from a running event loop"
**Solution**: Use `get_tool_async` instead of `@mcp_tool` decorator when already in async context

**Problem**: "RuntimeError: Event loop is closed"
**Solution**: 
- Move transport creation inside async functions
- Use `asyncio.run()` wrapper for Fire CLI integration:
```python
if __name__ == "__main__":
    import asyncio
    def run_main(**kwargs):
        asyncio.run(main(**kwargs))
    Fire(run_main)
```

### MCP Tool Integration Examples

See `examples/mcp/` for working examples:
- `gitmcp.py` - HTTP-based SSE transport
- `pyodide_code_executor.py` - Subprocess-based stdio transport with proper async handling

## Testing and Tool Message Patterns

### MockLM for Testing Tool Generation
- Use `MockLM` with `response_dict` to simulate LLM responses that include tool messages
- Set `tools=[ToolClass]` or `enable_message=[ToolClass]` on the agent to enable tool handling
- The `try_get_tool_messages()` method can extract tool messages from LLM responses with `all_tools=True`

### Task Termination Control
- `TaskConfig` has `done_if_tool` parameter to terminate tasks when any tool is generated
- `Task.done()` method checks `result.agent_response` for tool content when this flag is set
- Useful for workflows where tool generation signals task completion

### Testing Tool-Based Task Flows
```python
# Example: Test task termination on tool generation
config = TaskConfig(done_if_tool=True)
task = Task(agent, config=config)
response_dict = {"content": '{"request": "my_tool", "param": "value"}'}
```

## Multi-Agent System Development

### Important Patterns and Best Practices

#### 1. Pydantic Imports
**ALWAYS import Pydantic classes from `langroid.pydantic_v1`**, not from `pydantic` directly:
```python
# CORRECT
from langroid.pydantic_v1 import Field, BaseModel

# WRONG - will cause issues
from pydantic import Field, BaseModel
```

#### 2. Tool Name References in System Messages
When referencing tool names in f-strings within system messages, use the `.name()` method:
```python
system_message: str = f"""
Use {MyTool.name()} to perform the action.
"""
```
This works at module level in configs, but be aware that complex initialization at module level can sometimes cause issues.

#### 3. Agent Configuration with LLM
Always specify the LLM configuration explicitly in agent configs:
```python
class MyAgentConfig(lr.ChatAgentConfig):
    name: str = "MyAgent"
    llm: lm.OpenAIGPTConfig = lm.OpenAIGPTConfig(
        chat_model="gpt-4",  # or "gpt-4.1" etc.
    )
    system_message: str = "..."
```

#### 4. Tool Organization in Multi-Agent Systems
When tools delegate to agents:
- Define agent configs and agents BEFORE the tools that use them
- Tools can directly instantiate agents in their `handle()` methods:
```python
class MyTool(lr.ToolMessage):
    def handle(self) -> str:
        agent = MyAgent(MyAgentConfig())
        task = lr.Task(agent, interactive=False)
        result = task.run(prompt)
        return result.content
```

#### 5. Task Termination with Done Sequences
Use `done_sequences` for precise task termination control:
```python
# For a task that should complete after: Tool -> Agent handles -> LLM responds
task = lr.Task(
    agent,
    interactive=False,
    config=lr.TaskConfig(done_sequences=["T,A,L"]),
)
```

Common patterns:
- `"T,A"` - Tool used and handled by agent
- `"T,A,L"` - Tool used, handled, then LLM responds
- `"T[specific_tool],A"` - Specific tool used and handled

See `docs/notes/task-termination.md` for comprehensive documentation.

#### 6. Handling Non-Tool LLM Responses
Use `handle_llm_no_tool` in agent configs to handle cases where the LLM forgets to use a tool:
```python
class MyAgentConfig(lr.ChatAgentConfig):
    handle_llm_no_tool: str = "You FORGOT to use one of your TOOLs!"
```

#### 7. Agent Method Parameters
Note that `ChatAgentConfig` does not have a `use_tools` parameter. Instead, enable tools on the agent after creation:
```python
agent = MyAgent(config)
agent.enable_message([Tool1, Tool2, Tool3])  # Pass list of tool classes
```

## Commit and Pull Request Guidelines

- Never include "co-authored by Claude Code" or "created by Claude" in commit messages or pull request descriptions

## Codecov Badge Fix (June 2025)

- Fixed broken Codecov badge in README by removing the token parameter from the URL
- Changed from `https://codecov.io/gh/langroid/langroid/branch/main/graph/badge.svg?token=H94BX5F0TE` to `https://codecov.io/gh/langroid/langroid/graph/badge.svg`
- Tokens are not needed for public repositories and can cause GitHub rendering issues