Merge pull request #134 from ggozad/chore/a2a-removal
Remove A2A from haiku.rag, turn it into an example
This commit is contained in:
commit
9f68349ea4
41 changed files with 4687 additions and 321 deletions
|
|
@ -5,12 +5,13 @@
|
|||
|
||||
- **Configuration**: All CLI commands now properly support `--config` parameter for specifying custom configuration files
|
||||
- Configuration loading consolidated across CLI, app, and client with consistent resolution order
|
||||
- `HaikuRAGApp`, MCP server, and A2A server now accept `config` parameter for programmatic configuration
|
||||
- `HaikuRAGApp` and MCP server now accept `config` parameter for programmatic configuration
|
||||
- Updated CLI documentation to clarify global vs per-command options
|
||||
- **BREAKING**: Standardized configuration filename to `haiku.rag.yaml` in user directories (was incorrectly using `config.yaml`). Users with existing `config.yaml` in their user directory will need to rename it to `haiku.rag.yaml`
|
||||
|
||||
### Removed
|
||||
|
||||
- **BREAKING**: A2A (Agent-to-Agent) protocol support has been moved to a separate self-contained package in `examples/a2a-server/`. The A2A server is no longer part of the main haiku.rag package. Users who need A2A functionality can install and run it from the examples directory with `cd examples/a2a-server && uv sync`.
|
||||
- **BREAKING**: Removed deprecated `.env`-based configuration system. The `haiku-rag init-config --from-env` command and `load_config_from_env()` function have been removed. All configuration must now be done via YAML files. Environment variables for API keys (e.g., `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`) and service URLs (e.g., `OLLAMA_BASE_URL`) are still supported and can be set via `.env` files.
|
||||
|
||||
## [0.14.1] - 2025-11-06
|
||||
|
|
|
|||
27
README.md
27
README.md
|
|
@ -16,7 +16,6 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
|
|||
- **File monitoring**: Auto-index files when run as server
|
||||
- **40+ file formats**: PDF, DOCX, HTML, Markdown, code files, URLs
|
||||
- **MCP server**: Expose as tools for AI assistants
|
||||
- **A2A agent**: Conversational agent with context and multi-turn dialogue
|
||||
- **CLI & Python API**: Use from command line or Python
|
||||
|
||||
## Installation
|
||||
|
|
@ -29,7 +28,7 @@ Retrieval-Augmented Generation (RAG) library built on LanceDB.
|
|||
uv pip install haiku.rag
|
||||
```
|
||||
|
||||
Includes all features: document processing, all embedding providers, rerankers, and A2A agent support.
|
||||
Includes all features: document processing, all embedding providers, and rerankers.
|
||||
|
||||
### Slim Package (Minimal Dependencies)
|
||||
|
||||
|
|
@ -148,32 +147,13 @@ haiku-rag serve --stdio
|
|||
|
||||
Provides tools for document management and search directly in your AI assistant.
|
||||
|
||||
## A2A Agent
|
||||
|
||||
Run as a conversational agent with the Agent-to-Agent protocol:
|
||||
|
||||
```bash
|
||||
# Start the A2A server
|
||||
haiku-rag serve --a2a
|
||||
|
||||
# Connect with the interactive client (in another terminal)
|
||||
haiku-rag a2aclient
|
||||
```
|
||||
|
||||
The A2A agent provides:
|
||||
|
||||
- Multi-turn dialogue with context
|
||||
- Intelligent multi-search for complex questions
|
||||
- Source citations with titles and URIs
|
||||
- Full document retrieval on request
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples directory](examples/) for working examples:
|
||||
|
||||
- **[Interactive Research Assistant](examples/ag-ui-research/)** - Full-stack research assistant with Pydantic AI and AG-UI featuring human-in-the-loop approval and real-time state synchronization
|
||||
- **[Docker Setup](examples/docker/)** - Complete Docker deployment with file monitoring, MCP server, and A2A agent
|
||||
- **[A2A Security](examples/a2a-security/)** - Authentication examples (API key, OAuth2, GitHub)
|
||||
- **[Docker Setup](examples/docker/)** - Complete Docker deployment with file monitoring and MCP server
|
||||
- **[A2A Server](examples/a2a-server/)** - Self-contained A2A protocol server package with conversational agent interface
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
@ -185,7 +165,6 @@ Full documentation at: https://ggozad.github.io/haiku.rag/
|
|||
- [Python API](https://ggozad.github.io/haiku.rag/python/) - Complete API docs
|
||||
- [Agents](https://ggozad.github.io/haiku.rag/agents/) - QA agent and multi-agent research
|
||||
- [MCP Server](https://ggozad.github.io/haiku.rag/mcp/) - Model Context Protocol integration
|
||||
- [A2A Agent](https://ggozad.github.io/haiku.rag/a2a/) - Agent-to-Agent protocol support
|
||||
- [Benchmarks](https://ggozad.github.io/haiku.rag/benchmarks/) - Performance Benchmarks
|
||||
|
||||
mcp-name: io.github.ggozad/haiku-rag
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ ENV DEFAULT_DATA_DIR=/data
|
|||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Expose ports for MCP and A2A
|
||||
EXPOSE 8000 8001
|
||||
# Expose port for MCP server
|
||||
EXPOSE 8001
|
||||
|
||||
# Run all services (monitoring, MCP, A2A)
|
||||
CMD ["python", "-m", "haiku.rag.cli", "serve", "--monitor", "--mcp", "--mcp-port", "8001", "--a2a", "--a2a-host", "0.0.0.0", "--a2a-port", "8000", "--db", "/data/haiku.rag.lancedb"]
|
||||
# Run all services (monitoring, MCP)
|
||||
CMD ["python", "-m", "haiku.rag.cli", "serve", "--monitor", "--mcp", "--mcp-port", "8001", "--db", "/data/haiku.rag.lancedb"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# haiku.rag Docker Image
|
||||
|
||||
Pre-built images are available at `ghcr.io/ggozad/haiku.rag` with all extras (voyageai, mxbai, a2a).
|
||||
Pre-built images are available at `ghcr.io/ggozad/haiku.rag` with all extras (voyageai, mxbai).
|
||||
|
||||
## Using Pre-built Image
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ See [Configuration docs](https://ggozad.github.io/haiku.rag/configuration/) for
|
|||
Mount your config file and data directory:
|
||||
|
||||
```bash
|
||||
docker run -p 8000:8000 -p 8001:8001 \
|
||||
docker run -p 8001:8001 \
|
||||
-v $(pwd)/haiku.rag.yaml:/app/haiku.rag.yaml \
|
||||
-v $(pwd)/data:/data \
|
||||
ghcr.io/ggozad/haiku.rag:latest
|
||||
|
|
@ -44,7 +44,7 @@ The container will automatically use the mounted `haiku.rag.yaml` configuration
|
|||
For API keys (OpenAI, Anthropic, etc.), pass them as environment variables:
|
||||
|
||||
```bash
|
||||
docker run -p 8000:8000 -p 8001:8001 \
|
||||
docker run -p 8001:8001 \
|
||||
-v $(pwd)/haiku.rag.yaml:/app/haiku.rag.yaml \
|
||||
-v $(pwd)/data:/data \
|
||||
-e OPENAI_API_KEY=your-key-here \
|
||||
|
|
|
|||
31
docs/cli.md
31
docs/cli.md
|
|
@ -171,41 +171,18 @@ haiku-rag serve --mcp
|
|||
# MCP server (stdio transport)
|
||||
haiku-rag serve --mcp --stdio
|
||||
|
||||
# A2A server only
|
||||
haiku-rag serve --a2a
|
||||
|
||||
# File monitoring only
|
||||
haiku-rag serve --monitor
|
||||
|
||||
# All services
|
||||
haiku-rag serve --monitor --mcp --a2a
|
||||
# Both services
|
||||
haiku-rag serve --monitor --mcp
|
||||
|
||||
# Custom ports
|
||||
haiku-rag serve --mcp --mcp-port 9000 --a2a --a2a-port 9001
|
||||
# Custom port
|
||||
haiku-rag serve --mcp --mcp-port 9000
|
||||
```
|
||||
|
||||
See [Server Mode](server.md) for details on available services.
|
||||
|
||||
### A2A Interactive Client
|
||||
|
||||
Connect to and chat with haiku.rag's A2A server:
|
||||
|
||||
```bash
|
||||
# Connect to local server
|
||||
haiku-rag a2aclient
|
||||
|
||||
# Connect to remote server
|
||||
haiku-rag a2aclient --url https://example.com:8000
|
||||
```
|
||||
|
||||
The interactive client provides:
|
||||
- Rich markdown rendering of agent responses
|
||||
- Multi-turn conversation with context
|
||||
- Agent card discovery and display
|
||||
- Compact artifact summaries
|
||||
|
||||
See [A2A documentation](a2a.md) for more details.
|
||||
|
||||
## Settings
|
||||
|
||||
View current configuration settings:
|
||||
|
|
|
|||
|
|
@ -98,9 +98,6 @@ providers:
|
|||
rerank_base_url: ""
|
||||
qa_base_url: ""
|
||||
research_base_url: ""
|
||||
|
||||
a2a:
|
||||
max_contexts: 1000
|
||||
```
|
||||
|
||||
## Programmatic Configuration
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
- **File monitoring**: Automatically index files when run as a server
|
||||
- **Extended file format support**: Parse 40+ file formats including PDF, DOCX, HTML, Markdown, code files and more. Or add a URL!
|
||||
- **MCP server**: Exposes functionality as MCP tools
|
||||
- **A2A agent**: Conversational agent with context and multi-turn dialogue support
|
||||
- **CLI commands**: Access all functionality from your terminal
|
||||
- Add sources from text, files, or URLs, optionally with a human‑readable title
|
||||
- **Python client**: Call `haiku.rag` from your own python applications
|
||||
|
|
@ -59,7 +58,6 @@ haiku-rag ask "Who is the author of haiku.rag?"
|
|||
- [CLI](cli.md) - Command line interface usage
|
||||
- [Server](server.md) - File monitoring and server mode
|
||||
- [MCP](mcp.md) - Model Context Protocol integration
|
||||
- [A2A](a2a.md) - Agent-to-Agent conversational protocol
|
||||
- [Python](python.md) - Python API reference
|
||||
- [Agents](agents.md) - QA agent and multi-agent research
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ The full package includes **all features and extras**:
|
|||
- **Document processing** (Docling) - PDF, DOCX, PPTX, images, and 40+ file formats
|
||||
- **All embedding providers** - VoyageAI
|
||||
- **All rerankers** - MixedBread AI, Cohere, Zero Entropy
|
||||
- **A2A agent** - Agent-to-Agent protocol support
|
||||
|
||||
This is the easiest way to get started with all features enabled.
|
||||
|
||||
|
|
@ -36,7 +35,6 @@ The slim package has minimal dependencies and lets you install only what you nee
|
|||
- `docling` - PDF, DOCX, PPTX, images, and other document formats
|
||||
- `voyageai` - VoyageAI embeddings
|
||||
- `mxbai` - MixedBread AI reranking
|
||||
- `a2a` - Agent-to-Agent protocol support
|
||||
- `cohere` - Cohere reranking
|
||||
- `zeroentropy` - Zero Entropy reranking
|
||||
|
||||
|
|
@ -74,4 +72,4 @@ Run the container with all services:
|
|||
docker run -p 8000:8000 -p 8001:8001 -v $(pwd)/data:/data ghcr.io/ggozad/haiku.rag:latest
|
||||
```
|
||||
|
||||
This starts the MCP server on port 8001 and A2A server on port 8000, with data persisted to `./data`.
|
||||
This starts the MCP server on port 8001, with data persisted to `./data`.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Server Mode
|
||||
|
||||
The server provides automatic file monitoring, MCP functionality, and A2A agent support.
|
||||
The server provides automatic file monitoring and MCP functionality.
|
||||
|
||||
## Starting the Server
|
||||
|
||||
The `serve` command requires at least one service flag. You can enable file monitoring, MCP server, A2A server, or any combination:
|
||||
The `serve` command requires at least one service flag. You can enable file monitoring, MCP server, or both:
|
||||
|
||||
### MCP Server Only
|
||||
|
||||
|
|
@ -17,31 +17,19 @@ Transport options:
|
|||
- `--stdio` - Standard input/output transport
|
||||
- `--mcp-port` - Custom port (default: 8001)
|
||||
|
||||
### A2A Server Only
|
||||
|
||||
```bash
|
||||
haiku-rag serve --a2a
|
||||
```
|
||||
|
||||
Options:
|
||||
- `--a2a-host` - Host to bind to (default: 127.0.0.1)
|
||||
- `--a2a-port` - Port to bind to (default: 8000)
|
||||
|
||||
See [A2A documentation](a2a.md) for details on the conversational agent.
|
||||
|
||||
### File Monitoring Only
|
||||
|
||||
```bash
|
||||
haiku-rag serve --monitor
|
||||
```
|
||||
|
||||
### All Services
|
||||
### Both Services
|
||||
|
||||
```bash
|
||||
haiku-rag serve --monitor --mcp --a2a
|
||||
haiku-rag serve --monitor --mcp
|
||||
```
|
||||
|
||||
This will start file monitoring, MCP server on port 8001, and A2A server on port 8000.
|
||||
This will start file monitoring and MCP server on port 8001.
|
||||
|
||||
## File Monitoring
|
||||
|
||||
|
|
|
|||
|
|
@ -22,44 +22,25 @@ See `ag-ui-research/README.md` for setup instructions.
|
|||
Complete Docker setup for running haiku.rag with all services:
|
||||
- File monitoring for automatic document indexing
|
||||
- MCP server for AI assistant integration
|
||||
- A2A agent for conversational interactions
|
||||
|
||||
See `docker/README.md` for setup instructions.
|
||||
|
||||
## A2A Security Examples
|
||||
## A2A Server
|
||||
|
||||
**Directory:** `a2a-security/`
|
||||
**Directory:** `a2a-server/`
|
||||
|
||||
Three examples showing how to add authentication to haiku.rag's A2A server:
|
||||
Self-contained A2A (Agent-to-Agent) protocol server package that provides a conversational agent interface with its own CLI and dependencies.
|
||||
|
||||
### API Key Authentication
|
||||
Features:
|
||||
- Conversational context with multi-turn dialogue support
|
||||
- Interactive CLI client for testing
|
||||
- Security examples (API key, OAuth2 with GitHub, enterprise OAuth2)
|
||||
- Full documentation and installation instructions
|
||||
|
||||
**File:** `a2a-security/apikey_example.py`
|
||||
|
||||
Simple header-based authentication suitable for internal services and development.
|
||||
See `a2a-server/README.md` for complete setup and usage instructions.
|
||||
|
||||
Install locally:
|
||||
```bash
|
||||
python examples/a2a-security/apikey_example.py /path/to/database.lancedb
|
||||
cd a2a-server
|
||||
uv sync
|
||||
```
|
||||
|
||||
### OAuth2 GitHub Authentication
|
||||
|
||||
**File:** `a2a-security/oauth2_github.py`
|
||||
|
||||
GitHub Personal Access Token authentication for GitHub-integrated services.
|
||||
|
||||
```bash
|
||||
python examples/a2a-security/oauth2_github.py /path/to/database.lancedb
|
||||
```
|
||||
|
||||
### OAuth2 Enterprise Authentication
|
||||
|
||||
**File:** `a2a-security/oauth2_example.py`
|
||||
|
||||
Full OAuth2 with JWT verification for enterprise environments.
|
||||
|
||||
```bash
|
||||
python examples/a2a-security/oauth2_example.py /path/to/database.lancedb
|
||||
```
|
||||
|
||||
See individual files for detailed setup instructions and usage examples.
|
||||
|
|
|
|||
117
examples/a2a-server/README.md
Normal file
117
examples/a2a-server/README.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# haiku-rag-a2a
|
||||
|
||||
A2A (Agent-to-Agent) protocol server for haiku.rag. This package provides a conversational agent interface that maintains conversation history and context across multiple turns.
|
||||
|
||||
## Features
|
||||
|
||||
- **Conversational Context**: Maintains full conversation history including tool calls and results
|
||||
- **Multi-turn Dialogue**: Supports follow-up questions with pronoun resolution ("he", "it", "that document")
|
||||
- **Intelligent Search**: Performs single or multiple searches depending on question complexity
|
||||
- **Source Citations**: Always includes sources with both titles and URIs
|
||||
- **Full Document Retrieval**: Can fetch complete documents on request
|
||||
- **Multiple Skills**: Exposes three distinct skills with appropriate artifacts:
|
||||
- `document-qa`: Conversational question answering (default)
|
||||
- `document-search`: Semantic search with structured results
|
||||
- `document-retrieve`: Fetch complete documents by URI
|
||||
|
||||
## Installation
|
||||
|
||||
This package is not published to PyPI. Install it locally from the haiku.rag repository:
|
||||
|
||||
```bash
|
||||
cd examples/a2a-server
|
||||
uv sync
|
||||
```
|
||||
|
||||
This will install the package and all its dependencies, including `haiku.rag`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Starting the A2A Server
|
||||
|
||||
```bash
|
||||
# Start server with default database location (uses the same default as haiku-rag)
|
||||
uv run haiku-rag-a2a serve
|
||||
|
||||
# Or specify a custom database path
|
||||
uv run haiku-rag-a2a serve --db /path/to/database
|
||||
|
||||
# Start on custom host/port
|
||||
uv run haiku-rag-a2a serve --host 0.0.0.0 --port 8080
|
||||
```
|
||||
|
||||
By default, the server uses the same database location as `haiku-rag`:
|
||||
- Linux: `~/.local/share/haiku.rag`
|
||||
- macOS: `~/Library/Application Support/haiku.rag`
|
||||
- Windows: `C:/Users/<USER>/AppData/Roaming/haiku.rag`
|
||||
|
||||
### Interactive Client
|
||||
|
||||
Test and interact with the A2A server using the built-in interactive client:
|
||||
|
||||
```bash
|
||||
# Connect to local server
|
||||
uv run haiku-rag-a2a client
|
||||
|
||||
# Connect to remote server
|
||||
uv run haiku-rag-a2a client --url https://example.com:8000
|
||||
```
|
||||
|
||||
The interactive client provides:
|
||||
- Rich markdown rendering of agent responses
|
||||
- Conversation context across multiple turns
|
||||
- Agent card discovery and display
|
||||
- Compact artifact summaries
|
||||
|
||||
## Python Usage
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from haiku_rag_a2a.a2a import create_a2a_app
|
||||
import uvicorn
|
||||
|
||||
# Create A2A app
|
||||
app = create_a2a_app(Path("/path/to/database"))
|
||||
|
||||
# Run with uvicorn
|
||||
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||
```
|
||||
|
||||
## Security Examples
|
||||
|
||||
The `security_examples/` directory contains examples for securing the A2A server:
|
||||
|
||||
- `apikey_example.py` - Simple API key authentication
|
||||
- `oauth2_github.py` - GitHub Personal Access Token authentication
|
||||
- `oauth2_example.py` - Full OAuth2 with JWT verification
|
||||
|
||||
## Architecture
|
||||
|
||||
The A2A agent uses:
|
||||
|
||||
- **FastA2A**: Python framework implementing the A2A protocol
|
||||
- **Pydantic AI**: Agent framework with tool support
|
||||
- **In-Memory Storage**: Context and message history storage (persists during server lifetime)
|
||||
- **Conversation State**: Full pydantic-ai message history serialized in A2A context
|
||||
|
||||
## Configuration
|
||||
|
||||
The server uses the same configuration as haiku.rag. You can specify a config file:
|
||||
|
||||
```bash
|
||||
uv run haiku-rag-a2a serve --db /path/to/database --config haiku.rag.yaml
|
||||
```
|
||||
|
||||
You can also control the maximum number of conversation contexts via the `--max-contexts` parameter (defaults to 1000).
|
||||
|
||||
## Documentation
|
||||
|
||||
See [a2a.md](./a2a.md) for detailed documentation including:
|
||||
- API examples
|
||||
- Security configuration
|
||||
- Docker deployment
|
||||
- Artifact specification
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
0
examples/a2a-server/haiku_rag_a2a/__init__.py
Normal file
0
examples/a2a-server/haiku_rag_a2a/__init__.py
Normal file
|
|
@ -9,7 +9,7 @@ from haiku.rag.config import AppConfig, Config
|
|||
from haiku.rag.graph_common import get_model
|
||||
|
||||
from .context import load_message_history, save_message_history
|
||||
from .models import AgentDependencies, SearchResult
|
||||
from .models import A2AConfig, AgentDependencies, SearchResult
|
||||
from .prompts import A2A_SYSTEM_PROMPT
|
||||
from .skills import extract_question_from_task, get_agent_skills
|
||||
from .storage import LRUMemoryStorage
|
||||
|
|
@ -37,12 +37,14 @@ __all__ = [
|
|||
"extract_question_from_task",
|
||||
"get_agent_skills",
|
||||
"LRUMemoryStorage",
|
||||
"A2AConfig",
|
||||
]
|
||||
|
||||
|
||||
def create_a2a_app(
|
||||
db_path: Path,
|
||||
config: AppConfig = Config,
|
||||
max_contexts: int = 1000,
|
||||
security_schemes: dict | None = None,
|
||||
security: list[dict[str, list[str]]] | None = None,
|
||||
):
|
||||
|
|
@ -51,6 +53,7 @@ def create_a2a_app(
|
|||
Args:
|
||||
db_path: Path to the LanceDB database
|
||||
config: App configuration
|
||||
max_contexts: Maximum number of conversations to keep in memory
|
||||
security_schemes: Optional security scheme definitions for the AgentCard
|
||||
security: Optional security requirements for the AgentCard
|
||||
|
||||
|
|
@ -58,9 +61,7 @@ def create_a2a_app(
|
|||
A FastA2A ASGI application
|
||||
"""
|
||||
base_storage = InMemoryStorage()
|
||||
storage = LRUMemoryStorage(
|
||||
storage=base_storage, max_contexts=config.a2a.max_contexts
|
||||
)
|
||||
storage = LRUMemoryStorage(storage=base_storage, max_contexts=max_contexts)
|
||||
broker = InMemoryBroker()
|
||||
|
||||
# Create the agent with native search tool
|
||||
|
|
@ -123,7 +124,7 @@ def create_a2a_app(
|
|||
# Create FastA2A app with custom worker lifecycle
|
||||
@asynccontextmanager
|
||||
async def lifespan(app):
|
||||
logger.info(f"Started A2A server (max contexts: {config.a2a.max_contexts})")
|
||||
logger.info(f"Started A2A server (max contexts: {max_contexts})")
|
||||
async with app.task_manager:
|
||||
async with worker.run():
|
||||
yield
|
||||
|
|
@ -3,6 +3,14 @@ from pydantic import BaseModel, Field
|
|||
from haiku.rag.client import HaikuRAG
|
||||
|
||||
|
||||
class A2AConfig(BaseModel):
|
||||
"""Configuration for A2A (Agent-to-Agent) protocol server."""
|
||||
|
||||
max_contexts: int = Field(
|
||||
default=1000, description="Maximum number of conversations to keep in memory"
|
||||
)
|
||||
|
||||
|
||||
class SearchResult(BaseModel):
|
||||
"""Search result with both title and URI for A2A agent."""
|
||||
|
||||
|
|
@ -5,11 +5,11 @@ from pathlib import Path
|
|||
|
||||
from pydantic_ai import Agent
|
||||
|
||||
from haiku.rag.a2a.context import load_message_history, save_message_history
|
||||
from haiku.rag.a2a.models import AgentDependencies
|
||||
from haiku.rag.a2a.skills import extract_question_from_task
|
||||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.config import AppConfig, Config
|
||||
from haiku_rag_a2a.a2a.context import load_message_history, save_message_history
|
||||
from haiku_rag_a2a.a2a.models import AgentDependencies
|
||||
from haiku_rag_a2a.a2a.skills import extract_question_from_task
|
||||
|
||||
try:
|
||||
from fasta2a import Worker # type: ignore
|
||||
85
examples/a2a-server/haiku_rag_a2a/cli.py
Normal file
85
examples/a2a-server/haiku_rag_a2a/cli.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
import uvicorn
|
||||
|
||||
from haiku.rag.config import AppConfig, Config, load_yaml_config
|
||||
from haiku.rag.utils import get_default_data_dir
|
||||
from haiku_rag_a2a.a2a import create_a2a_app
|
||||
from haiku_rag_a2a.a2a.client import run_interactive_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
cli = typer.Typer(name="haiku-rag-a2a", no_args_is_help=True)
|
||||
|
||||
|
||||
@cli.command("serve", help="Start haiku.rag A2A (Agent-to-Agent) server")
|
||||
def serve(
|
||||
db: Path | None = typer.Option(
|
||||
None,
|
||||
"--db",
|
||||
help="Path to the database directory",
|
||||
),
|
||||
config_file: Path | None = typer.Option(
|
||||
None,
|
||||
"--config",
|
||||
help="Path to the configuration file",
|
||||
),
|
||||
host: str = typer.Option(
|
||||
"127.0.0.1",
|
||||
"--host",
|
||||
help="Host to bind A2A server to",
|
||||
),
|
||||
port: int = typer.Option(
|
||||
8000,
|
||||
"--port",
|
||||
help="Port to bind A2A server to",
|
||||
),
|
||||
max_contexts: int = typer.Option(
|
||||
1000,
|
||||
"--max-contexts",
|
||||
help="Maximum number of conversation contexts to keep in memory",
|
||||
),
|
||||
) -> None:
|
||||
"""Start the A2A server."""
|
||||
config = Config
|
||||
if config_file:
|
||||
yaml_data = load_yaml_config(config_file)
|
||||
config = AppConfig.model_validate(yaml_data)
|
||||
|
||||
if db is None:
|
||||
db = get_default_data_dir()
|
||||
|
||||
if not db.exists():
|
||||
typer.echo(f"Error: Database directory {db} does not exist")
|
||||
raise typer.Exit(1)
|
||||
|
||||
logger.info(f"Starting A2A server on {host}:{port}")
|
||||
|
||||
app = create_a2a_app(db_path=db, config=config, max_contexts=max_contexts)
|
||||
uvicorn_config = uvicorn.Config(
|
||||
app,
|
||||
host=host,
|
||||
port=port,
|
||||
log_level="info",
|
||||
)
|
||||
server = uvicorn.Server(uvicorn_config)
|
||||
asyncio.run(server.serve())
|
||||
|
||||
|
||||
@cli.command("client", help="Run interactive client to chat with A2A server")
|
||||
def client(
|
||||
url: str = typer.Option(
|
||||
"http://localhost:8000",
|
||||
"--url",
|
||||
help="URL of the A2A server",
|
||||
),
|
||||
):
|
||||
"""Run the interactive A2A client."""
|
||||
asyncio.run(run_interactive_client(url))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
27
examples/a2a-server/pyproject.toml
Normal file
27
examples/a2a-server/pyproject.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[project]
|
||||
name = "haiku-rag-a2a"
|
||||
description = "A2A protocol server for haiku.rag - Conversational agent interface"
|
||||
version = "0.1.0"
|
||||
authors = [{ name = "Yiorgis Gozadinos", email = "ggozadinos@gmail.com" }]
|
||||
license = { text = "MIT" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
keywords = ["RAG", "a2a", "agent", "conversational-ai"]
|
||||
|
||||
dependencies = [
|
||||
"haiku.rag>=0.14.0",
|
||||
"fasta2a>=0.1.0",
|
||||
"pydantic-ai-slim[a2a]>=1.11.1",
|
||||
"rich>=14.2.0",
|
||||
"httpx>=0.28.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
haiku-rag-a2a = "haiku_rag_a2a.cli:cli"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["haiku_rag_a2a"]
|
||||
|
|
@ -22,12 +22,11 @@ Usage:
|
|||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from haiku_rag_a2a.a2a import create_a2a_app
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
from haiku.rag.a2a import create_a2a_app
|
||||
|
||||
# API Key Configuration - In production, use environment variables or a secure key store
|
||||
API_KEY_NAME = "X-API-Key"
|
||||
VALID_API_KEY = os.getenv("API_KEY", "demo-key-12345")
|
||||
|
|
@ -35,6 +35,7 @@ Usage:
|
|||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from haiku_rag_a2a.a2a import create_a2a_app
|
||||
from jose import JWTError, jwt
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.responses import JSONResponse
|
||||
|
|
@ -44,8 +45,6 @@ from starlette.status import (
|
|||
HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
from haiku.rag.a2a import create_a2a_app
|
||||
|
||||
# OAuth2 Configuration
|
||||
OAUTH2_TOKEN_URL = os.getenv(
|
||||
"OAUTH2_TOKEN_URL", "https://your-auth.example.com/oauth/token"
|
||||
|
|
@ -24,12 +24,11 @@ import os
|
|||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from haiku_rag_a2a.a2a import create_a2a_app
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
from haiku.rag.a2a import create_a2a_app
|
||||
|
||||
# Configuration
|
||||
GITHUB_API_URL = "https://api.github.com"
|
||||
ALLOWED_TOKENS = (
|
||||
|
|
@ -5,6 +5,13 @@ import pytest
|
|||
pytest.importorskip("fasta2a")
|
||||
|
||||
from fasta2a.schema import Message, TextPart # noqa: E402
|
||||
from haiku_rag_a2a.a2a import (
|
||||
extract_question_from_task,
|
||||
get_agent_skills,
|
||||
load_message_history,
|
||||
save_message_history,
|
||||
)
|
||||
from haiku_rag_a2a.a2a.storage import LRUMemoryStorage
|
||||
from pydantic_ai.messages import ( # noqa: E402
|
||||
ModelMessage,
|
||||
ModelRequest,
|
||||
|
|
@ -16,13 +23,6 @@ from pydantic_ai.messages import (
|
|||
TextPart as AITextPart,
|
||||
)
|
||||
|
||||
from haiku.rag.a2a import (
|
||||
extract_question_from_task,
|
||||
get_agent_skills,
|
||||
load_message_history,
|
||||
save_message_history,
|
||||
)
|
||||
from haiku.rag.a2a.storage import LRUMemoryStorage
|
||||
from haiku.rag.client import HaikuRAG
|
||||
|
||||
|
||||
|
|
@ -209,7 +209,7 @@ async def test_lru_memory_storage_access_order():
|
|||
@pytest.mark.asyncio
|
||||
async def test_a2a_app_creation(temp_db_path):
|
||||
"""Test that A2A app can be created successfully."""
|
||||
from haiku.rag.a2a import create_a2a_app
|
||||
from haiku_rag_a2a.a2a import create_a2a_app
|
||||
|
||||
# Create a test database
|
||||
async with HaikuRAG(temp_db_path) as client:
|
||||
|
|
@ -230,7 +230,7 @@ async def test_a2a_app_creation(temp_db_path):
|
|||
@pytest.mark.asyncio
|
||||
async def test_a2a_app_has_skills(temp_db_path):
|
||||
"""Test that A2A app exposes skills describing its capabilities."""
|
||||
from haiku.rag.a2a import create_a2a_app
|
||||
from haiku_rag_a2a.a2a import create_a2a_app
|
||||
|
||||
# Create a test database
|
||||
async with HaikuRAG(temp_db_path) as client:
|
||||
|
|
@ -291,6 +291,7 @@ def test_get_agent_skills():
|
|||
@pytest.mark.asyncio
|
||||
async def test_build_artifacts_for_search():
|
||||
"""Test that search operations produce structured search artifacts."""
|
||||
from haiku_rag_a2a.a2a.worker import ConversationalWorker
|
||||
from pydantic_ai.messages import (
|
||||
ModelRequest,
|
||||
ModelResponse,
|
||||
|
|
@ -298,8 +299,6 @@ async def test_build_artifacts_for_search():
|
|||
ToolReturnPart,
|
||||
)
|
||||
|
||||
from haiku.rag.a2a.worker import ConversationalWorker
|
||||
|
||||
class MockResult:
|
||||
output = "Found 1 relevant results:\n\n1. *Score: 0.9* | **test**\nresult"
|
||||
|
||||
|
|
@ -350,6 +349,7 @@ async def test_build_artifacts_for_search():
|
|||
@pytest.mark.asyncio
|
||||
async def test_build_artifacts_for_retrieve():
|
||||
"""Test that retrieve operations produce document artifacts."""
|
||||
from haiku_rag_a2a.a2a.worker import ConversationalWorker
|
||||
from pydantic_ai.messages import (
|
||||
ModelRequest,
|
||||
ModelResponse,
|
||||
|
|
@ -357,8 +357,6 @@ async def test_build_artifacts_for_retrieve():
|
|||
ToolReturnPart,
|
||||
)
|
||||
|
||||
from haiku.rag.a2a.worker import ConversationalWorker
|
||||
|
||||
class MockResult:
|
||||
output = "Document content"
|
||||
|
||||
|
|
@ -407,6 +405,7 @@ async def test_build_artifacts_for_retrieve():
|
|||
@pytest.mark.asyncio
|
||||
async def test_build_artifacts_for_multiple_searches():
|
||||
"""Test that multiple searches each get their own artifact with correct results."""
|
||||
from haiku_rag_a2a.a2a.worker import ConversationalWorker
|
||||
from pydantic_ai.messages import (
|
||||
ModelRequest,
|
||||
ModelResponse,
|
||||
|
|
@ -415,8 +414,6 @@ async def test_build_artifacts_for_multiple_searches():
|
|||
)
|
||||
from pydantic_ai.messages import TextPart as AITextPart
|
||||
|
||||
from haiku.rag.a2a.worker import ConversationalWorker
|
||||
|
||||
class MockResult:
|
||||
output = "Answer based on multiple searches"
|
||||
|
||||
|
|
@ -513,11 +510,10 @@ async def test_build_artifacts_for_multiple_searches():
|
|||
@pytest.mark.asyncio
|
||||
async def test_qa_artifact_for_conversational_messages():
|
||||
"""Test that conversational Q&A messages always create qa_result artifacts."""
|
||||
from haiku_rag_a2a.a2a.worker import ConversationalWorker
|
||||
from pydantic_ai.messages import ModelResponse
|
||||
from pydantic_ai.messages import TextPart as AITextPart
|
||||
|
||||
from haiku.rag.a2a.worker import ConversationalWorker
|
||||
|
||||
class MockResult:
|
||||
output = "Hello! How can I help you?"
|
||||
|
||||
|
|
@ -552,6 +548,7 @@ async def test_qa_artifact_for_conversational_messages():
|
|||
@pytest.mark.asyncio
|
||||
async def test_build_artifacts_for_qa():
|
||||
"""Test that Q&A operations produce artifacts for each tool call."""
|
||||
from haiku_rag_a2a.a2a.worker import ConversationalWorker
|
||||
from pydantic_ai.messages import (
|
||||
ModelRequest,
|
||||
ModelResponse,
|
||||
|
|
@ -560,8 +557,6 @@ async def test_build_artifacts_for_qa():
|
|||
)
|
||||
from pydantic_ai.messages import TextPart as AITextPart
|
||||
|
||||
from haiku.rag.a2a.worker import ConversationalWorker
|
||||
|
||||
class MockResult:
|
||||
output = "This is the answer"
|
||||
|
||||
4373
examples/a2a-server/uv.lock
Normal file
4373
examples/a2a-server/uv.lock
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
# haiku.rag Docker Compose Example
|
||||
|
||||
Run haiku.rag with file monitoring, MCP server, and A2A agent.
|
||||
Run haiku.rag with file monitoring and MCP server.
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
|
@ -23,14 +23,10 @@ docker compose exec haiku-rag haiku-rag search "your query"
|
|||
|
||||
# Ask questions
|
||||
docker compose exec haiku-rag haiku-rag ask "What is haiku.rag?"
|
||||
|
||||
# A2A interactive client
|
||||
docker compose exec haiku-rag haiku-rag a2aclient --url http://localhost:8000
|
||||
```
|
||||
|
||||
## Ports
|
||||
|
||||
- `8000` - A2A agent
|
||||
- `8001` - MCP server
|
||||
|
||||
## Configuration
|
||||
|
|
@ -52,4 +48,3 @@ docker compose up -d
|
|||
- [Configuration](https://ggozad.github.io/haiku.rag/configuration/)
|
||||
- [CLI Commands](https://ggozad.github.io/haiku.rag/cli/)
|
||||
- [MCP Server](https://ggozad.github.io/haiku.rag/mcp/)
|
||||
- [A2A Agent](https://ggozad.github.io/haiku.rag/a2a/)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ services:
|
|||
dockerfile: docker/Dockerfile
|
||||
container_name: haiku-rag
|
||||
ports:
|
||||
- "8000:8000" # A2A server
|
||||
- "8001:8001" # MCP server
|
||||
volumes:
|
||||
- ./data:/data # Persist database
|
||||
|
|
@ -19,9 +18,3 @@ services:
|
|||
- CO_API_KEY=${CO_API_KEY}
|
||||
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Retrieval-Augmented Generation (RAG) library built on LanceDB - Core package with minimal dependencies.
|
||||
|
||||
`haiku.rag-slim` is the core package for users who want to install only the dependencies they need. Document processing (docling), rerankers, and A2A support are all optional extras.
|
||||
`haiku.rag-slim` is the core package for users who want to install only the dependencies they need. Document processing (docling), and reranker support are all optional extras.
|
||||
|
||||
**For most users, we recommend installing [`haiku.rag`](https://pypi.org/project/haiku.rag/) instead**, which includes all features out of the box.
|
||||
|
||||
|
|
@ -48,8 +48,6 @@ Adds support for 40+ file formats including PDF, DOCX, HTML, and more.
|
|||
- `bedrock` - AWS Bedrock
|
||||
- `vertexai` - Google Vertex AI
|
||||
|
||||
**Agent Protocol:**
|
||||
- `a2a` - Agent-to-Agent protocol
|
||||
|
||||
```bash
|
||||
# Common combinations
|
||||
|
|
@ -64,7 +62,6 @@ See the main [`haiku.rag`](https://github.com/ggozad/haiku.rag) repository for:
|
|||
- CLI examples
|
||||
- Python API usage
|
||||
- MCP server setup
|
||||
- A2A agent configuration
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
|
|||
|
|
@ -455,9 +455,6 @@ class HaikuRAGApp:
|
|||
enable_mcp: bool = True,
|
||||
mcp_transport: str | None = None,
|
||||
mcp_port: int = 8001,
|
||||
enable_a2a: bool = False,
|
||||
a2a_host: str = "127.0.0.1",
|
||||
a2a_port: int = 8000,
|
||||
):
|
||||
"""Start the server with selected services."""
|
||||
async with HaikuRAG(self.db_path, config=self.config) as client:
|
||||
|
|
@ -485,33 +482,6 @@ class HaikuRAGApp:
|
|||
mcp_task = asyncio.create_task(run_mcp())
|
||||
tasks.append(mcp_task)
|
||||
|
||||
# Start A2A server if enabled
|
||||
if enable_a2a:
|
||||
try:
|
||||
from haiku.rag.a2a import create_a2a_app
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import A2A: {e}")
|
||||
return
|
||||
|
||||
import uvicorn
|
||||
|
||||
logger.info(f"Starting A2A server on {a2a_host}:{a2a_port}")
|
||||
|
||||
async def run_a2a():
|
||||
app = create_a2a_app(db_path=self.db_path, config=self.config)
|
||||
uvicorn_config = uvicorn.Config(
|
||||
app,
|
||||
host=a2a_host,
|
||||
port=a2a_port,
|
||||
log_level="warning",
|
||||
access_log=False,
|
||||
)
|
||||
server = uvicorn.Server(uvicorn_config)
|
||||
await server.serve()
|
||||
|
||||
a2a_task = asyncio.create_task(run_a2a())
|
||||
tasks.append(a2a_task)
|
||||
|
||||
if not tasks:
|
||||
logger.warning("No services enabled")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -401,7 +401,7 @@ def download_models_cmd():
|
|||
|
||||
@cli.command(
|
||||
"serve",
|
||||
help="Start haiku.rag server. Use --monitor, --mcp, and/or --a2a to enable services.",
|
||||
help="Start haiku.rag server. Use --monitor and/or --mcp to enable services.",
|
||||
)
|
||||
def serve(
|
||||
db: Path | None = typer.Option(
|
||||
|
|
@ -429,27 +429,12 @@ def serve(
|
|||
"--mcp-port",
|
||||
help="Port to bind MCP server to (ignored with --stdio)",
|
||||
),
|
||||
a2a: bool = typer.Option(
|
||||
False,
|
||||
"--a2a",
|
||||
help="Enable A2A (Agent-to-Agent) server",
|
||||
),
|
||||
a2a_host: str = typer.Option(
|
||||
"127.0.0.1",
|
||||
"--a2a-host",
|
||||
help="Host to bind A2A server to",
|
||||
),
|
||||
a2a_port: int = typer.Option(
|
||||
8000,
|
||||
"--a2a-port",
|
||||
help="Port to bind A2A server to",
|
||||
),
|
||||
) -> None:
|
||||
"""Start the server with selected services."""
|
||||
# Require at least one service flag
|
||||
if not (monitor or mcp or a2a):
|
||||
if not (monitor or mcp):
|
||||
typer.echo(
|
||||
"Error: At least one service flag (--monitor, --mcp, or --a2a) must be specified"
|
||||
"Error: At least one service flag (--monitor or --mcp) must be specified"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
|
@ -467,34 +452,9 @@ def serve(
|
|||
enable_mcp=mcp,
|
||||
mcp_transport=transport,
|
||||
mcp_port=mcp_port,
|
||||
enable_a2a=a2a,
|
||||
a2a_host=a2a_host,
|
||||
a2a_port=a2a_port,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@cli.command(
|
||||
"a2aclient", help="Run interactive client to chat with haiku.rag's A2A server"
|
||||
)
|
||||
def a2aclient(
|
||||
url: str = typer.Option(
|
||||
"http://localhost:8000",
|
||||
"--url",
|
||||
help="Base URL of the A2A server",
|
||||
),
|
||||
):
|
||||
try:
|
||||
from haiku.rag.a2a.client import run_interactive_client
|
||||
except ImportError:
|
||||
typer.echo(
|
||||
"Error: A2A support requires the 'a2a' extra. "
|
||||
"Install with: uv pip install 'haiku.rag[a2a]'"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
asyncio.run(run_interactive_client(url=url))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from haiku.rag.config.loader import (
|
|||
load_yaml_config,
|
||||
)
|
||||
from haiku.rag.config.models import (
|
||||
A2AConfig,
|
||||
AppConfig,
|
||||
EmbeddingsConfig,
|
||||
LanceDBConfig,
|
||||
|
|
@ -35,7 +34,6 @@ __all__ = [
|
|||
"OllamaConfig",
|
||||
"VLLMConfig",
|
||||
"ProvidersConfig",
|
||||
"A2AConfig",
|
||||
"find_config_file",
|
||||
"load_yaml_config",
|
||||
"generate_default_config",
|
||||
|
|
|
|||
|
|
@ -84,5 +84,4 @@ def generate_default_config() -> dict:
|
|||
"research_base_url": "",
|
||||
},
|
||||
},
|
||||
"a2a": {"max_contexts": 1000},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,10 +76,6 @@ class ProvidersConfig(BaseModel):
|
|||
vllm: VLLMConfig = Field(default_factory=VLLMConfig)
|
||||
|
||||
|
||||
class A2AConfig(BaseModel):
|
||||
max_contexts: int = 1000
|
||||
|
||||
|
||||
class AppConfig(BaseModel):
|
||||
environment: str = "production"
|
||||
storage: StorageConfig = Field(default_factory=StorageConfig)
|
||||
|
|
@ -91,4 +87,3 @@ class AppConfig(BaseModel):
|
|||
research: ResearchConfig = Field(default_factory=ResearchConfig)
|
||||
processing: ProcessingConfig = Field(default_factory=ProcessingConfig)
|
||||
providers: ProvidersConfig = Field(default_factory=ProvidersConfig)
|
||||
a2a: A2AConfig = Field(default_factory=A2AConfig)
|
||||
|
|
|
|||
|
|
@ -45,8 +45,6 @@ voyageai = ["voyageai>=0.3.5"]
|
|||
mxbai = ["mxbai-rerank>=0.1.6"]
|
||||
cohere = ["cohere>=5.0.0"]
|
||||
zeroentropy = ["zeroentropy>=0.1.0a6"]
|
||||
# Agent protocols
|
||||
a2a = ["fasta2a>=0.1.0", "pydantic-ai-slim[a2a]"]
|
||||
# Model providers (delegated to pydantic-ai-slim)
|
||||
anthropic = ["pydantic-ai-slim[anthropic]"]
|
||||
groq = ["pydantic-ai-slim[groq]"]
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ nav:
|
|||
- Agents: agents.md
|
||||
- Server: server.md
|
||||
- MCP: mcp.md
|
||||
- A2A: a2a.md
|
||||
- Benchmarks: benchmarks.md
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ classifiers = [
|
|||
"Typing :: Typed",
|
||||
]
|
||||
|
||||
dependencies = ["haiku.rag-slim[docling,voyageai,mxbai,a2a,cohere,zeroentropy]"]
|
||||
dependencies = ["haiku.rag-slim[docling,voyageai,mxbai,cohere,zeroentropy]"]
|
||||
|
||||
[project.scripts]
|
||||
haiku-rag = "haiku.rag.cli:cli"
|
||||
|
|
@ -100,6 +100,8 @@ pythonVersion = "3.12"
|
|||
[tool.pytest.ini_options]
|
||||
asyncio_default_fixture_loop_scope = "session"
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
norecursedirs = ["examples", "docs", "evaluations", ".git", ".venv"]
|
||||
|
||||
# pyproject.toml
|
||||
filterwarnings = ["error", "ignore::UserWarning", "ignore::DeprecationWarning"]
|
||||
|
|
|
|||
|
|
@ -211,7 +211,6 @@ async def test_serve_mcp_only(app: HaikuRAGApp, monkeypatch, transport):
|
|||
enable_monitor=False,
|
||||
enable_mcp=True,
|
||||
mcp_transport=transport,
|
||||
enable_a2a=False,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
|
@ -245,50 +244,16 @@ async def test_serve_monitor_only(app: HaikuRAGApp, monkeypatch):
|
|||
|
||||
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
||||
try:
|
||||
await app.serve(enable_monitor=True, enable_mcp=False, enable_a2a=False)
|
||||
await app.serve(enable_monitor=True, enable_mcp=False)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
assert len(created_tasks) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_serve_a2a_only(app: HaikuRAGApp, monkeypatch):
|
||||
"""Test the serve method with A2A server only."""
|
||||
pytest.importorskip("fasta2a")
|
||||
created_tasks = []
|
||||
original_create_task = asyncio.create_task
|
||||
|
||||
def track_task(coro):
|
||||
task = original_create_task(coro)
|
||||
created_tasks.append(task)
|
||||
task.cancel()
|
||||
return task
|
||||
|
||||
monkeypatch.setattr("haiku.rag.app.asyncio.create_task", track_task)
|
||||
monkeypatch.setattr(
|
||||
"haiku.rag.app.asyncio.gather", AsyncMock(side_effect=asyncio.CancelledError)
|
||||
)
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__.return_value = mock_client
|
||||
|
||||
mock_a2a_app = MagicMock()
|
||||
|
||||
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
||||
with patch("haiku.rag.a2a.create_a2a_app", return_value=mock_a2a_app):
|
||||
try:
|
||||
await app.serve(enable_monitor=False, enable_mcp=False, enable_a2a=True)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
assert len(created_tasks) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_serve_all_services(app: HaikuRAGApp, monkeypatch):
|
||||
"""Test the serve method with all services enabled."""
|
||||
pytest.importorskip("fasta2a")
|
||||
created_tasks = []
|
||||
original_create_task = asyncio.create_task
|
||||
|
||||
|
|
@ -300,7 +265,6 @@ async def test_serve_all_services(app: HaikuRAGApp, monkeypatch):
|
|||
|
||||
mock_server = AsyncMock()
|
||||
mock_watcher = AsyncMock()
|
||||
mock_a2a_app = MagicMock()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"haiku.rag.app.create_mcp_server", MagicMock(return_value=mock_server)
|
||||
|
|
@ -317,13 +281,12 @@ async def test_serve_all_services(app: HaikuRAGApp, monkeypatch):
|
|||
mock_client.__aenter__.return_value = mock_client
|
||||
|
||||
with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
|
||||
with patch("haiku.rag.a2a.create_a2a_app", return_value=mock_a2a_app):
|
||||
try:
|
||||
await app.serve(enable_monitor=True, enable_mcp=True, enable_a2a=True)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
try:
|
||||
await app.serve(enable_monitor=True, enable_mcp=True)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
assert len(created_tasks) == 3
|
||||
assert len(created_tasks) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -196,7 +196,6 @@ def test_serve_mcp_only():
|
|||
_, kwargs = mock_app_instance.serve.call_args
|
||||
assert kwargs["enable_monitor"] is False
|
||||
assert kwargs["enable_mcp"] is True
|
||||
assert kwargs["enable_a2a"] is False
|
||||
assert kwargs["mcp_transport"] is None
|
||||
assert kwargs["mcp_port"] == 8001
|
||||
|
||||
|
|
@ -230,26 +229,6 @@ def test_serve_monitor_only():
|
|||
_, kwargs = mock_app_instance.serve.call_args
|
||||
assert kwargs["enable_monitor"] is True
|
||||
assert kwargs["enable_mcp"] is False
|
||||
assert kwargs["enable_a2a"] is False
|
||||
|
||||
|
||||
def test_serve_a2a_only():
|
||||
"""Test serve command with A2A only."""
|
||||
with patch("haiku.rag.cli.HaikuRAGApp") as mock_app:
|
||||
mock_app_instance = MagicMock()
|
||||
mock_app_instance.serve = AsyncMock()
|
||||
mock_app.return_value = mock_app_instance
|
||||
|
||||
result = runner.invoke(cli, ["serve", "--a2a"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_app_instance.serve.assert_called_once()
|
||||
_, kwargs = mock_app_instance.serve.call_args
|
||||
assert kwargs["enable_monitor"] is False
|
||||
assert kwargs["enable_mcp"] is False
|
||||
assert kwargs["enable_a2a"] is True
|
||||
assert kwargs["a2a_host"] == "127.0.0.1"
|
||||
assert kwargs["a2a_port"] == 8000
|
||||
|
||||
|
||||
def test_serve_all_services():
|
||||
|
|
@ -259,32 +238,28 @@ def test_serve_all_services():
|
|||
mock_app_instance.serve = AsyncMock()
|
||||
mock_app.return_value = mock_app_instance
|
||||
|
||||
result = runner.invoke(cli, ["serve", "--monitor", "--mcp", "--a2a"])
|
||||
result = runner.invoke(cli, ["serve", "--monitor", "--mcp"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_app_instance.serve.assert_called_once()
|
||||
_, kwargs = mock_app_instance.serve.call_args
|
||||
assert kwargs["enable_monitor"] is True
|
||||
assert kwargs["enable_mcp"] is True
|
||||
assert kwargs["enable_a2a"] is True
|
||||
|
||||
|
||||
def test_serve_custom_ports():
|
||||
"""Test serve command with custom ports."""
|
||||
"""Test serve command with custom MCP port."""
|
||||
with patch("haiku.rag.cli.HaikuRAGApp") as mock_app:
|
||||
mock_app_instance = MagicMock()
|
||||
mock_app_instance.serve = AsyncMock()
|
||||
mock_app.return_value = mock_app_instance
|
||||
|
||||
result = runner.invoke(
|
||||
cli, ["serve", "--mcp", "--mcp-port", "9000", "--a2a", "--a2a-port", "9001"]
|
||||
)
|
||||
result = runner.invoke(cli, ["serve", "--mcp", "--mcp-port", "9000"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_app_instance.serve.assert_called_once()
|
||||
_, kwargs = mock_app_instance.serve.call_args
|
||||
assert kwargs["mcp_port"] == 9000
|
||||
assert kwargs["a2a_port"] == 9001
|
||||
|
||||
|
||||
def test_serve_stdio_without_mcp():
|
||||
|
|
|
|||
Loading…
Reference in a new issue