chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Ruff
|
||||
.ruff_cache/
|
||||
@@ -0,0 +1,327 @@
|
||||
# LlamaIndex Go Micro Integration
|
||||
|
||||
[](https://badge.fury.io/py/go-micro-llamaindex)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
Official LlamaIndex integration for Go Micro services. This package enables LlamaIndex agents to discover and call Go Micro microservices through the Model Context Protocol (MCP).
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Service Discovery** - Discovers available services from MCP gateway
|
||||
- **Dynamic Tool Generation** - Converts service endpoints into LlamaIndex tools
|
||||
- **Rich Descriptions** - Uses service metadata for accurate tool descriptions
|
||||
- **Authentication Support** - Bearer token auth with scope-based permissions
|
||||
- **RAG Integration** - Combine service tools with LlamaIndex's RAG capabilities
|
||||
- **Type-Safe** - Fully typed with Python 3.8+ type hints
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install go-micro-llamaindex
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Start Your Go Micro Services
|
||||
|
||||
```bash
|
||||
# Start MCP gateway
|
||||
micro mcp serve --address :3000
|
||||
```
|
||||
|
||||
### 2. Create LlamaIndex Agent
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
from llama_index.core.agent import ReActAgent
|
||||
from llama_index.llms.openai import OpenAI
|
||||
|
||||
# Initialize toolkit from MCP gateway
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Create agent
|
||||
llm = OpenAI(model="gpt-4")
|
||||
agent = ReActAgent.from_tools(toolkit.get_tools(), llm=llm, verbose=True)
|
||||
|
||||
# Use the agent!
|
||||
response = agent.chat("Create a user named Alice with email alice@example.com")
|
||||
print(response)
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Tool Discovery
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
|
||||
# Connect to MCP gateway
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# List available tools
|
||||
for tool in toolkit.get_tools():
|
||||
print(f"Tool: {tool.metadata.name}")
|
||||
print(f"Description: {tool.metadata.description}")
|
||||
print()
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
|
||||
# Create toolkit with authentication
|
||||
toolkit = GoMicroToolkit.from_gateway(
|
||||
gateway_url="http://localhost:3000",
|
||||
auth_token="your-bearer-token"
|
||||
)
|
||||
|
||||
# Tools will automatically use the auth token
|
||||
tools = toolkit.get_tools()
|
||||
```
|
||||
|
||||
### Filter Tools by Service
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Get only user service tools
|
||||
user_tools = toolkit.get_tools(service_filter="users")
|
||||
|
||||
# Get tools matching a pattern
|
||||
blog_tools = toolkit.get_tools(name_pattern="blog.*")
|
||||
```
|
||||
|
||||
### Custom Tool Selection
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Select specific tools
|
||||
selected_tools = toolkit.get_tools(
|
||||
include=["users.Users.Get", "users.Users.Create"]
|
||||
)
|
||||
|
||||
# Exclude certain tools
|
||||
filtered_tools = toolkit.get_tools(
|
||||
exclude=["users.Users.Delete"]
|
||||
)
|
||||
```
|
||||
|
||||
### RAG + Microservices
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
from llama_index.core import VectorStoreIndex, Document
|
||||
from llama_index.core.agent import ReActAgent
|
||||
from llama_index.core.tools import QueryEngineTool, ToolMetadata
|
||||
from llama_index.llms.openai import OpenAI
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Combine service tools with a RAG query engine
|
||||
index = VectorStoreIndex.from_documents([...])
|
||||
rag_tool = QueryEngineTool(
|
||||
query_engine=index.as_query_engine(),
|
||||
metadata=ToolMetadata(name="docs", description="Search documentation"),
|
||||
)
|
||||
|
||||
all_tools = [rag_tool] + toolkit.get_tools()
|
||||
agent = ReActAgent.from_tools(all_tools, llm=OpenAI(model="gpt-4"))
|
||||
```
|
||||
|
||||
### Multi-Agent Workflows
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
from llama_index.core.agent import ReActAgent
|
||||
from llama_index.llms.openai import OpenAI
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
llm = OpenAI(model="gpt-4")
|
||||
|
||||
# Agent 1: User management
|
||||
user_agent = ReActAgent.from_tools(
|
||||
toolkit.get_tools(service_filter="users"), llm=llm
|
||||
)
|
||||
|
||||
# Agent 2: Blog management
|
||||
blog_agent = ReActAgent.from_tools(
|
||||
toolkit.get_tools(service_filter="blog"), llm=llm
|
||||
)
|
||||
|
||||
# Coordinate between agents
|
||||
user_result = user_agent.chat("Create user Alice")
|
||||
blog_result = blog_agent.chat(f"Create blog post for {user_result}")
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit, GoMicroError
|
||||
|
||||
try:
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools()
|
||||
except GoMicroError as e:
|
||||
print(f"Error: {e}")
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```python
|
||||
from go_micro_llamaindex import GoMicroToolkit, GoMicroConfig
|
||||
|
||||
config = GoMicroConfig(
|
||||
gateway_url="http://localhost:3000",
|
||||
auth_token="your-token",
|
||||
timeout=30,
|
||||
retry_count=3,
|
||||
retry_delay=1.0,
|
||||
verify_ssl=True,
|
||||
)
|
||||
|
||||
toolkit = GoMicroToolkit(config)
|
||||
tools = toolkit.get_tools()
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### GoMicroToolkit
|
||||
|
||||
Main class for interacting with Go Micro services.
|
||||
|
||||
#### Methods
|
||||
|
||||
- `from_gateway(gateway_url, auth_token=None, **kwargs)` - Create toolkit from MCP gateway
|
||||
- `get_tools(service_filter=None, name_pattern=None, include=None, exclude=None)` - Get LlamaIndex tools
|
||||
- `refresh()` - Refresh tool list from gateway
|
||||
- `call_tool(tool_name, arguments)` - Call a tool directly
|
||||
- `list_tools()` - Get raw list of available tools
|
||||
|
||||
### GoMicroConfig
|
||||
|
||||
Configuration for the toolkit.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `gateway_url` (str) - MCP gateway URL
|
||||
- `auth_token` (str, optional) - Bearer authentication token
|
||||
- `timeout` (int) - Request timeout in seconds (default: 30)
|
||||
- `retry_count` (int) - Number of retries (default: 3)
|
||||
- `retry_delay` (float) - Delay between retries in seconds (default: 1.0)
|
||||
- `verify_ssl` (bool) - Verify SSL certificates (default: True)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.8+
|
||||
- llama-index-core >= 0.10.0
|
||||
- requests >= 2.31.0
|
||||
- pydantic >= 2.0.0
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/micro/go-micro
|
||||
cd go-micro/contrib/go-micro-llamaindex
|
||||
|
||||
# Create virtual environment
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
|
||||
# Install in development mode
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=go_micro_llamaindex
|
||||
|
||||
# Run specific test
|
||||
pytest tests/test_toolkit.py
|
||||
```
|
||||
|
||||
### Code Formatting
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
black go_micro_llamaindex tests
|
||||
|
||||
# Check types
|
||||
mypy go_micro_llamaindex
|
||||
|
||||
# Lint
|
||||
ruff check go_micro_llamaindex
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples](./examples) directory for complete examples:
|
||||
|
||||
- [basic_agent.py](./examples/basic_agent.py) - Simple ReAct agent
|
||||
- [rag_with_services.py](./examples/rag_with_services.py) - RAG combined with microservices
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gateway Connection Issues
|
||||
|
||||
If you can't connect to the MCP gateway:
|
||||
|
||||
1. Verify the gateway is running:
|
||||
```bash
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
2. Check the gateway URL is correct
|
||||
3. Verify firewall settings
|
||||
|
||||
### Authentication Errors
|
||||
|
||||
If you get authentication errors:
|
||||
|
||||
1. Verify your token is valid
|
||||
2. Check the token has required scopes
|
||||
3. Review gateway logs for details
|
||||
|
||||
### Tool Discovery Issues
|
||||
|
||||
If tools aren't being discovered:
|
||||
|
||||
1. List services from gateway:
|
||||
```bash
|
||||
curl http://localhost:3000/mcp/tools
|
||||
```
|
||||
|
||||
2. Verify services are registered
|
||||
3. Check service metadata is correct
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - See [LICENSE](../../LICENSE) for details.
|
||||
|
||||
## Links
|
||||
|
||||
- [Go Micro](https://github.com/micro/go-micro)
|
||||
- [MCP Documentation](../../gateway/mcp/DOCUMENTATION.md)
|
||||
- [LlamaIndex](https://docs.llamaindex.ai/)
|
||||
- [Issue Tracker](https://github.com/micro/go-micro/issues)
|
||||
|
||||
## Support
|
||||
|
||||
- GitHub Discussions: https://github.com/micro/go-micro/discussions
|
||||
- Discord: https://discord.gg/G8Gk5j3uXr
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Basic LlamaIndex agent example using Go Micro services.
|
||||
|
||||
This example shows how to create a simple LlamaIndex agent that can
|
||||
interact with Go Micro services through the MCP gateway.
|
||||
"""
|
||||
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
from llama_index.core.agent import ReActAgent
|
||||
from llama_index.llms.openai import OpenAI
|
||||
|
||||
|
||||
def main():
|
||||
"""Run basic agent example."""
|
||||
# Initialize toolkit from MCP gateway
|
||||
print("Connecting to MCP gateway...")
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Get available tools
|
||||
tools = toolkit.get_tools()
|
||||
print(f"\nDiscovered {len(tools)} tools:")
|
||||
for tool in tools:
|
||||
print(f" - {tool.metadata.name}: {tool.metadata.description}")
|
||||
|
||||
# Create LlamaIndex ReAct agent
|
||||
print("\nCreating LlamaIndex agent...")
|
||||
llm = OpenAI(model="gpt-4", temperature=0)
|
||||
agent = ReActAgent.from_tools(tools, llm=llm, verbose=True)
|
||||
|
||||
# Example queries
|
||||
queries = [
|
||||
"Create a user named Alice with email alice@example.com",
|
||||
"Get the user we just created",
|
||||
]
|
||||
|
||||
for query in queries:
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Query: {query}")
|
||||
print("=" * 60)
|
||||
response = agent.chat(query)
|
||||
print(f"\nResult: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,72 @@
|
||||
"""RAG with Go Micro services example.
|
||||
|
||||
This example demonstrates how to combine LlamaIndex's RAG capabilities
|
||||
with Go Micro service tools, allowing an agent to both query documents
|
||||
and interact with microservices.
|
||||
"""
|
||||
|
||||
from go_micro_llamaindex import GoMicroToolkit
|
||||
from llama_index.core import VectorStoreIndex, Document
|
||||
from llama_index.core.agent import ReActAgent
|
||||
from llama_index.core.tools import QueryEngineTool, ToolMetadata
|
||||
from llama_index.llms.openai import OpenAI
|
||||
|
||||
|
||||
def main():
|
||||
"""Run RAG + services example."""
|
||||
# Initialize toolkit from MCP gateway
|
||||
print("Connecting to MCP gateway...")
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Get service tools (e.g., user management)
|
||||
service_tools = toolkit.get_tools(service_filter="users")
|
||||
print(f"Discovered {len(service_tools)} user service tools")
|
||||
|
||||
# Create a simple document index for RAG
|
||||
documents = [
|
||||
Document(text="Alice is the admin user with ID user-001."),
|
||||
Document(text="Bob is a regular user with ID user-002."),
|
||||
Document(text="The blog service supports creating, reading, and deleting posts."),
|
||||
Document(text="Users need the 'blog:write' scope to create blog posts."),
|
||||
]
|
||||
|
||||
print("Building document index...")
|
||||
index = VectorStoreIndex.from_documents(documents)
|
||||
query_engine = index.as_query_engine()
|
||||
|
||||
# Create a query engine tool for RAG
|
||||
rag_tool = QueryEngineTool(
|
||||
query_engine=query_engine,
|
||||
metadata=ToolMetadata(
|
||||
name="knowledge_base",
|
||||
description="Search the knowledge base for information about users, "
|
||||
"services, and permissions. Use this to look up user IDs, "
|
||||
"service capabilities, and required scopes.",
|
||||
),
|
||||
)
|
||||
|
||||
# Combine RAG tool with service tools
|
||||
all_tools = [rag_tool] + service_tools
|
||||
|
||||
# Create agent with both capabilities
|
||||
print("\nCreating agent with RAG + service tools...")
|
||||
llm = OpenAI(model="gpt-4", temperature=0)
|
||||
agent = ReActAgent.from_tools(all_tools, llm=llm, verbose=True)
|
||||
|
||||
# Example: Agent uses RAG to find user ID, then calls service
|
||||
queries = [
|
||||
"What is Alice's user ID?",
|
||||
"Look up Alice's user ID from the knowledge base, then get her full profile from the user service",
|
||||
"What scope do I need to create blog posts?",
|
||||
]
|
||||
|
||||
for query in queries:
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Query: {query}")
|
||||
print("=" * 60)
|
||||
response = agent.chat(query)
|
||||
print(f"\nResult: {response}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,17 @@
|
||||
"""LlamaIndex Go Micro Integration.
|
||||
|
||||
This package provides LlamaIndex integration for Go Micro services through
|
||||
the Model Context Protocol (MCP).
|
||||
"""
|
||||
|
||||
from go_micro_llamaindex.toolkit import GoMicroToolkit, GoMicroConfig
|
||||
from go_micro_llamaindex.exceptions import GoMicroError, GoMicroConnectionError, GoMicroAuthError
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__all__ = [
|
||||
"GoMicroToolkit",
|
||||
"GoMicroConfig",
|
||||
"GoMicroError",
|
||||
"GoMicroConnectionError",
|
||||
"GoMicroAuthError",
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Custom exceptions for LlamaIndex Go Micro integration."""
|
||||
|
||||
|
||||
class GoMicroError(Exception):
|
||||
"""Base exception for Go Micro integration errors."""
|
||||
pass
|
||||
|
||||
|
||||
class GoMicroConnectionError(GoMicroError):
|
||||
"""Raised when unable to connect to MCP gateway."""
|
||||
pass
|
||||
|
||||
|
||||
class GoMicroAuthError(GoMicroError):
|
||||
"""Raised when authentication fails."""
|
||||
pass
|
||||
|
||||
|
||||
class GoMicroToolError(GoMicroError):
|
||||
"""Raised when tool execution fails."""
|
||||
pass
|
||||
@@ -0,0 +1,311 @@
|
||||
"""LlamaIndex toolkit for Go Micro services."""
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
import requests
|
||||
from llama_index.core.tools import FunctionTool, ToolMetadata
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from go_micro_llamaindex.exceptions import (
|
||||
GoMicroConnectionError,
|
||||
GoMicroAuthError,
|
||||
GoMicroToolError,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class GoMicroConfig:
|
||||
"""Configuration for Go Micro MCP gateway connection.
|
||||
|
||||
Attributes:
|
||||
gateway_url: URL of the MCP gateway (e.g., http://localhost:3000)
|
||||
auth_token: Optional bearer authentication token
|
||||
timeout: Request timeout in seconds
|
||||
retry_count: Number of retries on failure
|
||||
retry_delay: Delay between retries in seconds
|
||||
verify_ssl: Whether to verify SSL certificates
|
||||
"""
|
||||
|
||||
gateway_url: str
|
||||
auth_token: Optional[str] = None
|
||||
timeout: int = 30
|
||||
retry_count: int = 3
|
||||
retry_delay: float = 1.0
|
||||
verify_ssl: bool = True
|
||||
|
||||
|
||||
class GoMicroTool(BaseModel):
|
||||
"""Represents a Go Micro service tool.
|
||||
|
||||
Attributes:
|
||||
name: Tool name (e.g., "users.Users.Get")
|
||||
service: Service name (e.g., "users")
|
||||
endpoint: Endpoint name (e.g., "Users.Get")
|
||||
description: Tool description
|
||||
example: Example input JSON
|
||||
scopes: Required auth scopes
|
||||
metadata: Additional metadata from service
|
||||
"""
|
||||
|
||||
name: str
|
||||
service: str
|
||||
endpoint: str
|
||||
description: str
|
||||
example: Optional[str] = None
|
||||
scopes: Optional[List[str]] = None
|
||||
metadata: Dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class GoMicroToolkit:
|
||||
"""LlamaIndex toolkit for Go Micro services.
|
||||
|
||||
This class provides integration between LlamaIndex and Go Micro services
|
||||
via the Model Context Protocol (MCP) gateway.
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> tools = toolkit.get_tools()
|
||||
>>> for tool in tools:
|
||||
... print(f"Tool: {tool.metadata.name}")
|
||||
"""
|
||||
|
||||
def __init__(self, config: GoMicroConfig):
|
||||
"""Initialize the toolkit.
|
||||
|
||||
Args:
|
||||
config: Configuration for MCP gateway connection
|
||||
"""
|
||||
self.config = config
|
||||
self._tools: Optional[List[GoMicroTool]] = None
|
||||
self._session = requests.Session()
|
||||
|
||||
if config.auth_token:
|
||||
self._session.headers.update({
|
||||
"Authorization": f"Bearer {config.auth_token}"
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def from_gateway(
|
||||
cls,
|
||||
gateway_url: str,
|
||||
auth_token: Optional[str] = None,
|
||||
**kwargs: Any
|
||||
) -> "GoMicroToolkit":
|
||||
"""Create toolkit from MCP gateway URL.
|
||||
|
||||
Args:
|
||||
gateway_url: URL of the MCP gateway
|
||||
auth_token: Optional bearer authentication token
|
||||
**kwargs: Additional configuration options
|
||||
|
||||
Returns:
|
||||
GoMicroToolkit instance
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
"""
|
||||
config = GoMicroConfig(
|
||||
gateway_url=gateway_url,
|
||||
auth_token=auth_token,
|
||||
**kwargs
|
||||
)
|
||||
return cls(config)
|
||||
|
||||
def _make_request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
**kwargs: Any
|
||||
) -> requests.Response:
|
||||
"""Make HTTP request to MCP gateway.
|
||||
|
||||
Args:
|
||||
method: HTTP method (GET, POST, etc.)
|
||||
path: API path
|
||||
**kwargs: Additional request arguments
|
||||
|
||||
Returns:
|
||||
Response object
|
||||
|
||||
Raises:
|
||||
GoMicroConnectionError: If connection fails
|
||||
GoMicroAuthError: If authentication fails
|
||||
"""
|
||||
url = f"{self.config.gateway_url}{path}"
|
||||
kwargs.setdefault("timeout", self.config.timeout)
|
||||
kwargs.setdefault("verify", self.config.verify_ssl)
|
||||
|
||||
try:
|
||||
response = self._session.request(method, url, **kwargs)
|
||||
|
||||
if response.status_code == 401:
|
||||
raise GoMicroAuthError("Authentication failed")
|
||||
elif response.status_code == 403:
|
||||
raise GoMicroAuthError("Forbidden: insufficient permissions")
|
||||
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
except requests.ConnectionError as e:
|
||||
raise GoMicroConnectionError(
|
||||
f"Failed to connect to MCP gateway at {url}: {e}"
|
||||
)
|
||||
except requests.Timeout as e:
|
||||
raise GoMicroConnectionError(
|
||||
f"Request to MCP gateway timed out: {e}"
|
||||
)
|
||||
except requests.RequestException as e:
|
||||
if isinstance(e, (GoMicroConnectionError, GoMicroAuthError)):
|
||||
raise
|
||||
raise GoMicroConnectionError(f"Request failed: {e}")
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Refresh tool list from MCP gateway.
|
||||
|
||||
Raises:
|
||||
GoMicroConnectionError: If unable to connect to gateway
|
||||
"""
|
||||
response = self._make_request("GET", "/mcp/tools")
|
||||
data = response.json()
|
||||
|
||||
tools_data = data.get("tools", [])
|
||||
self._tools = [
|
||||
GoMicroTool(
|
||||
name=tool["name"],
|
||||
service=tool["service"],
|
||||
endpoint=tool["endpoint"],
|
||||
description=tool.get("description", ""),
|
||||
example=tool.get("example"),
|
||||
scopes=tool.get("scopes"),
|
||||
metadata=tool.get("metadata", {})
|
||||
)
|
||||
for tool in tools_data
|
||||
]
|
||||
|
||||
def get_tools(
|
||||
self,
|
||||
service_filter: Optional[str] = None,
|
||||
name_pattern: Optional[str] = None,
|
||||
include: Optional[List[str]] = None,
|
||||
exclude: Optional[List[str]] = None,
|
||||
) -> List[FunctionTool]:
|
||||
"""Get LlamaIndex tools from Go Micro services.
|
||||
|
||||
Args:
|
||||
service_filter: Filter tools by service name
|
||||
name_pattern: Filter tools by name pattern (regex)
|
||||
include: List of tool names to include
|
||||
exclude: List of tool names to exclude
|
||||
|
||||
Returns:
|
||||
List of LlamaIndex FunctionTool objects
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> all_tools = toolkit.get_tools()
|
||||
>>> user_tools = toolkit.get_tools(service_filter="users")
|
||||
"""
|
||||
if self._tools is None:
|
||||
self.refresh()
|
||||
|
||||
tools = self._tools or []
|
||||
|
||||
if service_filter:
|
||||
tools = [t for t in tools if t.service == service_filter]
|
||||
|
||||
if name_pattern:
|
||||
pattern = re.compile(name_pattern)
|
||||
tools = [t for t in tools if pattern.match(t.name)]
|
||||
|
||||
if include:
|
||||
tools = [t for t in tools if t.name in include]
|
||||
|
||||
if exclude:
|
||||
tools = [t for t in tools if t.name not in exclude]
|
||||
|
||||
return [self._create_llamaindex_tool(tool) for tool in tools]
|
||||
|
||||
def _create_llamaindex_tool(self, tool: GoMicroTool) -> FunctionTool:
|
||||
"""Create a LlamaIndex FunctionTool from a GoMicroTool.
|
||||
|
||||
Args:
|
||||
tool: GoMicroTool to convert
|
||||
|
||||
Returns:
|
||||
LlamaIndex FunctionTool object
|
||||
"""
|
||||
toolkit = self
|
||||
|
||||
def tool_func(arguments: str) -> str:
|
||||
"""Execute the tool.
|
||||
|
||||
Args:
|
||||
arguments: JSON string with tool arguments
|
||||
|
||||
Returns:
|
||||
JSON string with tool result
|
||||
"""
|
||||
return toolkit.call_tool(tool.name, arguments)
|
||||
|
||||
description = tool.description
|
||||
if tool.example:
|
||||
description += f"\n\nExample input: {tool.example}"
|
||||
|
||||
return FunctionTool.from_defaults(
|
||||
fn=tool_func,
|
||||
name=tool.name,
|
||||
description=description,
|
||||
)
|
||||
|
||||
def call_tool(self, tool_name: str, arguments: str) -> str:
|
||||
"""Call a specific tool directly.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool to call
|
||||
arguments: JSON string with tool arguments
|
||||
|
||||
Returns:
|
||||
JSON string with tool result
|
||||
|
||||
Raises:
|
||||
GoMicroToolError: If tool execution fails
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> result = toolkit.call_tool(
|
||||
... "users.Users.Get",
|
||||
... '{"id": "user-123"}'
|
||||
... )
|
||||
"""
|
||||
try:
|
||||
args = json.loads(arguments) if isinstance(arguments, str) else arguments
|
||||
except json.JSONDecodeError as e:
|
||||
raise GoMicroToolError(f"Invalid JSON arguments: {e}")
|
||||
|
||||
try:
|
||||
response = self._make_request(
|
||||
"POST",
|
||||
"/mcp/call",
|
||||
json={"name": tool_name, "arguments": args}
|
||||
)
|
||||
return json.dumps(response.json())
|
||||
except requests.RequestException as e:
|
||||
raise GoMicroToolError(f"Tool execution failed: {e}")
|
||||
|
||||
def list_tools(self) -> List[GoMicroTool]:
|
||||
"""Get raw list of available tools.
|
||||
|
||||
Returns:
|
||||
List of GoMicroTool objects
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> for tool in toolkit.list_tools():
|
||||
... print(f"{tool.name}: {tool.description}")
|
||||
"""
|
||||
if self._tools is None:
|
||||
self.refresh()
|
||||
return self._tools or []
|
||||
@@ -0,0 +1,72 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "go-micro-llamaindex"
|
||||
version = "0.1.0"
|
||||
description = "LlamaIndex integration for Go Micro services via MCP"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
license = {text = "Apache-2.0"}
|
||||
authors = [
|
||||
{name = "Micro Team", email = "hello@micro.dev"}
|
||||
]
|
||||
keywords = ["llamaindex", "go-micro", "mcp", "microservices", "ai", "rag"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"llama-index-core>=0.10.0",
|
||||
"requests>=2.31.0",
|
||||
"pydantic>=2.0.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
"black>=23.0.0",
|
||||
"mypy>=1.0.0",
|
||||
"ruff>=0.1.0",
|
||||
"types-requests>=2.31.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/micro/go-micro"
|
||||
Documentation = "https://github.com/micro/go-micro/tree/master/contrib/go-micro-llamaindex"
|
||||
Repository = "https://github.com/micro/go-micro"
|
||||
Issues = "https://github.com/micro/go-micro/issues"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["go_micro_llamaindex*"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ['py39', 'py310', 'py311']
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.9"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py39"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
@@ -0,0 +1,261 @@
|
||||
"""Tests for GoMicroToolkit."""
|
||||
|
||||
import json
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from go_micro_llamaindex import GoMicroToolkit, GoMicroConfig
|
||||
from go_micro_llamaindex.exceptions import (
|
||||
GoMicroConnectionError,
|
||||
GoMicroAuthError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_gateway_response():
|
||||
"""Mock MCP gateway response."""
|
||||
return {
|
||||
"tools": [
|
||||
{
|
||||
"name": "users.Users.Get",
|
||||
"service": "users",
|
||||
"endpoint": "Users.Get",
|
||||
"description": "Get a user by ID",
|
||||
"example": '{"id": "user-123"}',
|
||||
"scopes": ["users:read"],
|
||||
"metadata": {
|
||||
"description": "Get a user by ID",
|
||||
"example": '{"id": "user-123"}',
|
||||
"scopes": "users:read"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users.Users.Create",
|
||||
"service": "users",
|
||||
"endpoint": "Users.Create",
|
||||
"description": "Create a new user",
|
||||
"example": '{"name": "Alice", "email": "alice@example.com"}',
|
||||
"scopes": ["users:write"],
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"name": "blog.Blog.List",
|
||||
"service": "blog",
|
||||
"endpoint": "Blog.List",
|
||||
"description": "List blog posts",
|
||||
"scopes": ["blog:read"],
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"count": 3
|
||||
}
|
||||
|
||||
|
||||
class TestGoMicroConfig:
|
||||
"""Tests for GoMicroConfig."""
|
||||
|
||||
def test_config_defaults(self):
|
||||
"""Test config default values."""
|
||||
config = GoMicroConfig(gateway_url="http://localhost:3000")
|
||||
|
||||
assert config.gateway_url == "http://localhost:3000"
|
||||
assert config.auth_token is None
|
||||
assert config.timeout == 30
|
||||
assert config.retry_count == 3
|
||||
assert config.retry_delay == 1.0
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_config_custom_values(self):
|
||||
"""Test config with custom values."""
|
||||
config = GoMicroConfig(
|
||||
gateway_url="http://localhost:8080",
|
||||
auth_token="test-token",
|
||||
timeout=60,
|
||||
retry_count=5,
|
||||
retry_delay=2.0,
|
||||
verify_ssl=False
|
||||
)
|
||||
|
||||
assert config.gateway_url == "http://localhost:8080"
|
||||
assert config.auth_token == "test-token"
|
||||
assert config.timeout == 60
|
||||
assert config.retry_count == 5
|
||||
assert config.retry_delay == 2.0
|
||||
assert config.verify_ssl is False
|
||||
|
||||
|
||||
class TestGoMicroToolkit:
|
||||
"""Tests for GoMicroToolkit."""
|
||||
|
||||
def test_from_gateway(self):
|
||||
"""Test creating toolkit from gateway URL."""
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
assert toolkit.config.gateway_url == "http://localhost:3000"
|
||||
assert toolkit.config.auth_token is None
|
||||
|
||||
def test_from_gateway_with_auth(self):
|
||||
"""Test creating toolkit with authentication."""
|
||||
toolkit = GoMicroToolkit.from_gateway(
|
||||
"http://localhost:3000",
|
||||
auth_token="test-token"
|
||||
)
|
||||
|
||||
assert toolkit.config.auth_token == "test-token"
|
||||
assert "Authorization" in toolkit._session.headers
|
||||
assert toolkit._session.headers["Authorization"] == "Bearer test-token"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_refresh(self, mock_request, mock_gateway_response):
|
||||
"""Test refreshing tool list."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
toolkit.refresh()
|
||||
|
||||
assert len(toolkit._tools) == 3
|
||||
assert toolkit._tools[0].name == "users.Users.Get"
|
||||
assert toolkit._tools[1].name == "users.Users.Create"
|
||||
assert toolkit._tools[2].name == "blog.Blog.List"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools(self, mock_request, mock_gateway_response):
|
||||
"""Test getting LlamaIndex tools."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools()
|
||||
|
||||
assert len(tools) == 3
|
||||
names = [t.metadata.name for t in tools]
|
||||
assert "users.Users.Get" in names
|
||||
assert "users.Users.Create" in names
|
||||
assert "blog.Blog.List" in names
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools_with_service_filter(self, mock_request, mock_gateway_response):
|
||||
"""Test filtering tools by service."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools(service_filter="users")
|
||||
|
||||
assert len(tools) == 2
|
||||
for tool in tools:
|
||||
assert "users" in tool.metadata.name
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools_with_include(self, mock_request, mock_gateway_response):
|
||||
"""Test including specific tools."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools(include=["users.Users.Get"])
|
||||
|
||||
assert len(tools) == 1
|
||||
assert tools[0].metadata.name == "users.Users.Get"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools_with_exclude(self, mock_request, mock_gateway_response):
|
||||
"""Test excluding specific tools."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools(exclude=["users.Users.Create"])
|
||||
|
||||
assert len(tools) == 2
|
||||
names = [t.metadata.name for t in tools]
|
||||
assert "users.Users.Create" not in names
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools_with_name_pattern(self, mock_request, mock_gateway_response):
|
||||
"""Test filtering tools by name pattern."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools(name_pattern="blog\\..*")
|
||||
|
||||
assert len(tools) == 1
|
||||
assert tools[0].metadata.name == "blog.Blog.List"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_call_tool(self, mock_request):
|
||||
"""Test calling a tool directly."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {"user": {"id": "user-123", "name": "Alice"}}
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
result = toolkit.call_tool("users.Users.Get", '{"id": "user-123"}')
|
||||
|
||||
result_data = json.loads(result)
|
||||
assert result_data["user"]["id"] == "user-123"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_list_tools(self, mock_request, mock_gateway_response):
|
||||
"""Test listing raw tools."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.list_tools()
|
||||
|
||||
assert len(tools) == 3
|
||||
assert tools[0].name == "users.Users.Get"
|
||||
assert tools[0].service == "users"
|
||||
assert tools[0].scopes == ["users:read"]
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_connection_error(self, mock_request):
|
||||
"""Test handling connection errors."""
|
||||
mock_request.side_effect = requests.ConnectionError("Connection failed")
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
with pytest.raises(GoMicroConnectionError):
|
||||
toolkit.refresh()
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_auth_error(self, mock_request):
|
||||
"""Test handling authentication errors."""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 401
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
with pytest.raises(GoMicroAuthError):
|
||||
toolkit.refresh()
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_timeout(self, mock_request):
|
||||
"""Test handling timeouts."""
|
||||
mock_request.side_effect = requests.Timeout("Request timed out")
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
with pytest.raises(GoMicroConnectionError):
|
||||
toolkit.refresh()
|
||||
@@ -0,0 +1,65 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Ruff
|
||||
.ruff_cache/
|
||||
@@ -0,0 +1,105 @@
|
||||
# Contributing to LangChain Go Micro
|
||||
|
||||
Thank you for your interest in contributing to the LangChain Go Micro integration!
|
||||
|
||||
## Development Setup
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/micro/go-micro
|
||||
cd go-micro/contrib/langchain-go-micro
|
||||
```
|
||||
|
||||
2. Create a virtual environment:
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
3. Install in development mode:
|
||||
```bash
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
Run all tests:
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
Run with coverage:
|
||||
```bash
|
||||
pytest --cov=langchain_go_micro --cov-report=html
|
||||
```
|
||||
|
||||
Run specific tests:
|
||||
```bash
|
||||
pytest tests/test_toolkit.py::TestGoMicroToolkit::test_get_tools
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
We use several tools to maintain code quality:
|
||||
|
||||
### Black (code formatting)
|
||||
```bash
|
||||
black langchain_go_micro tests examples
|
||||
```
|
||||
|
||||
### MyPy (type checking)
|
||||
```bash
|
||||
mypy langchain_go_micro
|
||||
```
|
||||
|
||||
### Ruff (linting)
|
||||
```bash
|
||||
ruff check langchain_go_micro tests
|
||||
```
|
||||
|
||||
Run all checks:
|
||||
```bash
|
||||
black langchain_go_micro tests examples && \
|
||||
mypy langchain_go_micro && \
|
||||
ruff check langchain_go_micro tests
|
||||
```
|
||||
|
||||
## Testing with Real Services
|
||||
|
||||
To test with real Go Micro services:
|
||||
|
||||
1. Start example services:
|
||||
```bash
|
||||
cd ../../examples/mcp/documented
|
||||
go run main.go
|
||||
```
|
||||
|
||||
2. Run integration tests:
|
||||
```bash
|
||||
cd contrib/langchain-go-micro
|
||||
pytest tests/integration/ -v
|
||||
```
|
||||
|
||||
## Submitting Changes
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
||||
3. Make your changes
|
||||
4. Run tests and code quality checks
|
||||
5. Commit your changes (`git commit -am 'Add new feature'`)
|
||||
6. Push to your fork (`git push origin feature/my-feature`)
|
||||
7. Create a Pull Request
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- Include tests for new features
|
||||
- Update documentation as needed
|
||||
- Follow existing code style
|
||||
- Add entry to CHANGELOG.md
|
||||
- Ensure all tests pass
|
||||
- Keep changes focused and atomic
|
||||
|
||||
## Questions?
|
||||
|
||||
- GitHub Discussions: https://github.com/micro/go-micro/discussions
|
||||
- Discord: https://discord.gg/G8Gk5j3uXr
|
||||
@@ -0,0 +1,373 @@
|
||||
# LangChain Go Micro Integration
|
||||
|
||||
[](https://badge.fury.io/py/langchain-go-micro)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
Official LangChain integration for Go Micro services. This package enables LangChain agents to discover and call Go Micro microservices through the Model Context Protocol (MCP).
|
||||
|
||||
## Features
|
||||
|
||||
- 🔍 **Automatic Service Discovery** - Discovers available services from MCP gateway
|
||||
- 🛠️ **Dynamic Tool Generation** - Converts service endpoints into LangChain tools
|
||||
- 📝 **Rich Descriptions** - Uses service metadata for accurate tool descriptions
|
||||
- 🔐 **Authentication Support** - Bearer token auth with scope-based permissions
|
||||
- ⚡ **Type-Safe** - Fully typed with Python 3.8+ type hints
|
||||
- 🎯 **Easy Integration** - Works with any LangChain agent
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install langchain-go-micro
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Start Your Go Micro Services
|
||||
|
||||
```bash
|
||||
# Start MCP gateway
|
||||
micro mcp serve --address :3000
|
||||
```
|
||||
|
||||
### 2. Create LangChain Agent
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
from langchain.agents import initialize_agent, AgentType
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
# Initialize toolkit from MCP gateway
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Create agent
|
||||
llm = ChatOpenAI(model="gpt-4")
|
||||
agent = initialize_agent(
|
||||
toolkit.get_tools(),
|
||||
llm,
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Use the agent!
|
||||
result = agent.run("Create a user named Alice with email alice@example.com")
|
||||
print(result)
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Tool Discovery
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
|
||||
# Connect to MCP gateway
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# List available tools
|
||||
for tool in toolkit.get_tools():
|
||||
print(f"Tool: {tool.name}")
|
||||
print(f"Description: {tool.description}")
|
||||
print()
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
|
||||
# Create toolkit with authentication
|
||||
toolkit = GoMicroToolkit.from_gateway(
|
||||
gateway_url="http://localhost:3000",
|
||||
auth_token="your-bearer-token"
|
||||
)
|
||||
|
||||
# Tools will automatically use the auth token
|
||||
tools = toolkit.get_tools()
|
||||
```
|
||||
|
||||
### Filter Tools by Service
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Get only user service tools
|
||||
user_tools = toolkit.get_tools(service_filter="users")
|
||||
|
||||
# Get tools matching a pattern
|
||||
blog_tools = toolkit.get_tools(name_pattern="blog.*")
|
||||
```
|
||||
|
||||
### Custom Tool Selection
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Select specific tools
|
||||
selected_tools = toolkit.get_tools(
|
||||
include=["users.Users.Get", "users.Users.Create"]
|
||||
)
|
||||
|
||||
# Exclude certain tools
|
||||
filtered_tools = toolkit.get_tools(
|
||||
exclude=["users.Users.Delete"]
|
||||
)
|
||||
```
|
||||
|
||||
### Multi-Agent Workflows
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
from langchain.agents import initialize_agent, AgentType
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
# Create specialized agents for different services
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Agent 1: User management
|
||||
user_agent = initialize_agent(
|
||||
toolkit.get_tools(service_filter="users"),
|
||||
ChatOpenAI(model="gpt-4"),
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION
|
||||
)
|
||||
|
||||
# Agent 2: Order processing
|
||||
order_agent = initialize_agent(
|
||||
toolkit.get_tools(service_filter="orders"),
|
||||
ChatOpenAI(model="gpt-4"),
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION
|
||||
)
|
||||
|
||||
# Coordinate between agents
|
||||
user = user_agent.run("Create user Alice")
|
||||
order = order_agent.run(f"Create order for user {user['id']}")
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit, GoMicroError
|
||||
|
||||
try:
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools()
|
||||
except GoMicroError as e:
|
||||
print(f"Error: {e}")
|
||||
# Handle error (gateway unreachable, auth failed, etc.)
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit, GoMicroConfig
|
||||
|
||||
config = GoMicroConfig(
|
||||
gateway_url="http://localhost:3000",
|
||||
auth_token="your-token",
|
||||
timeout=30, # Request timeout in seconds
|
||||
retry_count=3, # Number of retries on failure
|
||||
retry_delay=1.0, # Delay between retries
|
||||
verify_ssl=True, # SSL certificate verification
|
||||
)
|
||||
|
||||
toolkit = GoMicroToolkit(config)
|
||||
tools = toolkit.get_tools()
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### GoMicroToolkit
|
||||
|
||||
Main class for interacting with Go Micro services.
|
||||
|
||||
#### Methods
|
||||
|
||||
- `from_gateway(gateway_url, auth_token=None, **kwargs)` - Create toolkit from MCP gateway
|
||||
- `get_tools(service_filter=None, name_pattern=None, include=None, exclude=None)` - Get LangChain tools
|
||||
- `refresh()` - Refresh tool list from gateway
|
||||
- `call_tool(tool_name, arguments)` - Call a tool directly
|
||||
|
||||
### GoMicroConfig
|
||||
|
||||
Configuration for the toolkit.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `gateway_url` (str) - MCP gateway URL
|
||||
- `auth_token` (str, optional) - Bearer authentication token
|
||||
- `timeout` (int) - Request timeout in seconds (default: 30)
|
||||
- `retry_count` (int) - Number of retries (default: 3)
|
||||
- `retry_delay` (float) - Delay between retries in seconds (default: 1.0)
|
||||
- `verify_ssl` (bool) - Verify SSL certificates (default: True)
|
||||
|
||||
## Integration with LangChain Components
|
||||
|
||||
### With LangChain Agents
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
from langchain.agents import initialize_agent, AgentType
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
llm = ChatOpenAI(model="gpt-4")
|
||||
|
||||
agent = initialize_agent(
|
||||
toolkit.get_tools(),
|
||||
llm,
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
### With LangChain Memory
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
from langchain.agents import initialize_agent, AgentType
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain.memory import ConversationBufferMemory
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
memory = ConversationBufferMemory(memory_key="chat_history")
|
||||
|
||||
agent = initialize_agent(
|
||||
toolkit.get_tools(),
|
||||
ChatOpenAI(model="gpt-4"),
|
||||
agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
|
||||
memory=memory,
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
### With Custom LLMs
|
||||
|
||||
```python
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
from langchain.agents import initialize_agent, AgentType
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Use Claude instead of GPT
|
||||
agent = initialize_agent(
|
||||
toolkit.get_tools(),
|
||||
ChatAnthropic(model="claude-3-sonnet-20240229"),
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||
verbose=True
|
||||
)
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.8+
|
||||
- LangChain >= 0.1.0
|
||||
- requests >= 2.31.0
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/micro/go-micro
|
||||
cd go-micro/contrib/langchain-go-micro
|
||||
|
||||
# Create virtual environment
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
|
||||
# Install in development mode
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=langchain_go_micro
|
||||
|
||||
# Run specific test
|
||||
pytest tests/test_toolkit.py
|
||||
```
|
||||
|
||||
### Code Formatting
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
black langchain_go_micro tests
|
||||
|
||||
# Check types
|
||||
mypy langchain_go_micro
|
||||
|
||||
# Lint
|
||||
ruff check langchain_go_micro
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples](./examples) directory for complete examples:
|
||||
|
||||
- [basic_agent.py](./examples/basic_agent.py) - Simple agent example
|
||||
- [multi_agent.py](./examples/multi_agent.py) - Multi-agent workflow
|
||||
- [with_memory.py](./examples/with_memory.py) - Agent with conversation memory
|
||||
- [custom_llm.py](./examples/custom_llm.py) - Using different LLMs
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gateway Connection Issues
|
||||
|
||||
If you can't connect to the MCP gateway:
|
||||
|
||||
1. Verify the gateway is running:
|
||||
```bash
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
2. Check the gateway URL is correct
|
||||
3. Verify firewall settings
|
||||
|
||||
### Authentication Errors
|
||||
|
||||
If you get authentication errors:
|
||||
|
||||
1. Verify your token is valid
|
||||
2. Check the token has required scopes
|
||||
3. Review gateway logs for details
|
||||
|
||||
### Tool Discovery Issues
|
||||
|
||||
If tools aren't being discovered:
|
||||
|
||||
1. List services from gateway:
|
||||
```bash
|
||||
curl http://localhost:3000/mcp/tools
|
||||
```
|
||||
|
||||
2. Verify services are registered
|
||||
3. Check service metadata is correct
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - See [LICENSE](../../LICENSE) for details.
|
||||
|
||||
## Links
|
||||
|
||||
- [Go Micro](https://github.com/micro/go-micro)
|
||||
- [MCP Documentation](../../gateway/mcp/DOCUMENTATION.md)
|
||||
- [LangChain](https://python.langchain.com/)
|
||||
- [Issue Tracker](https://github.com/micro/go-micro/issues)
|
||||
|
||||
## Support
|
||||
|
||||
- GitHub Discussions: https://github.com/micro/go-micro/discussions
|
||||
- Discord: https://discord.gg/G8Gk5j3uXr
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Basic LangChain agent example using Go Micro services.
|
||||
|
||||
This example shows how to create a simple LangChain agent that can
|
||||
interact with Go Micro services through the MCP gateway.
|
||||
"""
|
||||
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
from langchain.agents import initialize_agent, AgentType
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
|
||||
def main():
|
||||
"""Run basic agent example."""
|
||||
# Initialize toolkit from MCP gateway
|
||||
print("Connecting to MCP gateway...")
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Get available tools
|
||||
tools = toolkit.get_tools()
|
||||
print(f"\nDiscovered {len(tools)} tools:")
|
||||
for tool in tools:
|
||||
print(f" - {tool.name}: {tool.description}")
|
||||
|
||||
# Create LangChain agent
|
||||
print("\nCreating LangChain agent...")
|
||||
llm = ChatOpenAI(model="gpt-4", temperature=0)
|
||||
agent = initialize_agent(
|
||||
tools,
|
||||
llm,
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||
verbose=True
|
||||
)
|
||||
|
||||
# Example queries
|
||||
queries = [
|
||||
"Create a user named Alice with email alice@example.com",
|
||||
"Get the user we just created",
|
||||
]
|
||||
|
||||
for query in queries:
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Query: {query}")
|
||||
print('='*60)
|
||||
result = agent.run(query)
|
||||
print(f"\nResult: {result}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Multi-agent workflow example.
|
||||
|
||||
This example demonstrates how to create specialized agents for different
|
||||
services and coordinate between them.
|
||||
"""
|
||||
|
||||
from langchain_go_micro import GoMicroToolkit
|
||||
from langchain.agents import initialize_agent, AgentType
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
|
||||
def main():
|
||||
"""Run multi-agent example."""
|
||||
# Connect to MCP gateway
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
# Create LLM
|
||||
llm = ChatOpenAI(model="gpt-4", temperature=0)
|
||||
|
||||
# Create specialized agents for different services
|
||||
print("Creating specialized agents...")
|
||||
|
||||
# Agent 1: User management
|
||||
user_tools = toolkit.get_tools(service_filter="users")
|
||||
user_agent = initialize_agent(
|
||||
user_tools,
|
||||
llm,
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||
verbose=True
|
||||
)
|
||||
print(f"User agent: {len(user_tools)} tools")
|
||||
|
||||
# Agent 2: Blog management
|
||||
blog_tools = toolkit.get_tools(service_filter="blog")
|
||||
blog_agent = initialize_agent(
|
||||
blog_tools,
|
||||
llm,
|
||||
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||
verbose=True
|
||||
)
|
||||
print(f"Blog agent: {len(blog_tools)} tools")
|
||||
|
||||
# Coordinate between agents
|
||||
print("\n" + "="*60)
|
||||
print("Multi-agent workflow")
|
||||
print("="*60)
|
||||
|
||||
# Step 1: Create a user
|
||||
print("\nStep 1: Creating user...")
|
||||
user_result = user_agent.run(
|
||||
"Create a user named Bob Smith with email bob@example.com"
|
||||
)
|
||||
print(f"User created: {user_result}")
|
||||
|
||||
# Step 2: Create a blog post for that user
|
||||
print("\nStep 2: Creating blog post...")
|
||||
blog_result = blog_agent.run(
|
||||
f"Create a blog post titled 'Hello World' with content "
|
||||
f"'This is my first post' by user {user_result}"
|
||||
)
|
||||
print(f"Blog post created: {blog_result}")
|
||||
|
||||
# Step 3: List user's posts
|
||||
print("\nStep 3: Listing user's posts...")
|
||||
posts = blog_agent.run(f"List all blog posts by {user_result}")
|
||||
print(f"User's posts: {posts}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,17 @@
|
||||
"""LangChain Go Micro Integration.
|
||||
|
||||
This package provides LangChain integration for Go Micro services through
|
||||
the Model Context Protocol (MCP).
|
||||
"""
|
||||
|
||||
from langchain_go_micro.toolkit import GoMicroToolkit, GoMicroConfig
|
||||
from langchain_go_micro.exceptions import GoMicroError, GoMicroConnectionError, GoMicroAuthError
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__all__ = [
|
||||
"GoMicroToolkit",
|
||||
"GoMicroConfig",
|
||||
"GoMicroError",
|
||||
"GoMicroConnectionError",
|
||||
"GoMicroAuthError",
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Custom exceptions for LangChain Go Micro integration."""
|
||||
|
||||
|
||||
class GoMicroError(Exception):
|
||||
"""Base exception for Go Micro integration errors."""
|
||||
pass
|
||||
|
||||
|
||||
class GoMicroConnectionError(GoMicroError):
|
||||
"""Raised when unable to connect to MCP gateway."""
|
||||
pass
|
||||
|
||||
|
||||
class GoMicroAuthError(GoMicroError):
|
||||
"""Raised when authentication fails."""
|
||||
pass
|
||||
|
||||
|
||||
class GoMicroToolError(GoMicroError):
|
||||
"""Raised when tool execution fails."""
|
||||
pass
|
||||
@@ -0,0 +1,319 @@
|
||||
"""LangChain toolkit for Go Micro services."""
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional, Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
import requests
|
||||
from langchain.tools import Tool
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from langchain_go_micro.exceptions import (
|
||||
GoMicroConnectionError,
|
||||
GoMicroAuthError,
|
||||
GoMicroToolError,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class GoMicroConfig:
|
||||
"""Configuration for Go Micro MCP gateway connection.
|
||||
|
||||
Attributes:
|
||||
gateway_url: URL of the MCP gateway (e.g., http://localhost:3000)
|
||||
auth_token: Optional bearer authentication token
|
||||
timeout: Request timeout in seconds
|
||||
retry_count: Number of retries on failure
|
||||
retry_delay: Delay between retries in seconds
|
||||
verify_ssl: Whether to verify SSL certificates
|
||||
"""
|
||||
|
||||
gateway_url: str
|
||||
auth_token: Optional[str] = None
|
||||
timeout: int = 30
|
||||
retry_count: int = 3
|
||||
retry_delay: float = 1.0
|
||||
verify_ssl: bool = True
|
||||
|
||||
|
||||
class GoMicroTool(BaseModel):
|
||||
"""Represents a Go Micro service tool.
|
||||
|
||||
Attributes:
|
||||
name: Tool name (e.g., "users.Users.Get")
|
||||
service: Service name (e.g., "users")
|
||||
endpoint: Endpoint name (e.g., "Users.Get")
|
||||
description: Tool description
|
||||
example: Example input JSON
|
||||
scopes: Required auth scopes
|
||||
metadata: Additional metadata from service
|
||||
"""
|
||||
|
||||
name: str
|
||||
service: str
|
||||
endpoint: str
|
||||
description: str
|
||||
example: Optional[str] = None
|
||||
scopes: Optional[List[str]] = None
|
||||
metadata: Dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class GoMicroToolkit:
|
||||
"""LangChain toolkit for Go Micro services.
|
||||
|
||||
This class provides integration between LangChain and Go Micro services
|
||||
via the Model Context Protocol (MCP) gateway.
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> tools = toolkit.get_tools()
|
||||
>>> for tool in tools:
|
||||
... print(f"Tool: {tool.name}")
|
||||
"""
|
||||
|
||||
def __init__(self, config: GoMicroConfig):
|
||||
"""Initialize the toolkit.
|
||||
|
||||
Args:
|
||||
config: Configuration for MCP gateway connection
|
||||
"""
|
||||
self.config = config
|
||||
self._tools: Optional[List[GoMicroTool]] = None
|
||||
self._session = requests.Session()
|
||||
|
||||
# Set up authentication
|
||||
if config.auth_token:
|
||||
self._session.headers.update({
|
||||
"Authorization": f"Bearer {config.auth_token}"
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def from_gateway(
|
||||
cls,
|
||||
gateway_url: str,
|
||||
auth_token: Optional[str] = None,
|
||||
**kwargs: Any
|
||||
) -> "GoMicroToolkit":
|
||||
"""Create toolkit from MCP gateway URL.
|
||||
|
||||
Args:
|
||||
gateway_url: URL of the MCP gateway
|
||||
auth_token: Optional bearer authentication token
|
||||
**kwargs: Additional configuration options
|
||||
|
||||
Returns:
|
||||
GoMicroToolkit instance
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
"""
|
||||
config = GoMicroConfig(
|
||||
gateway_url=gateway_url,
|
||||
auth_token=auth_token,
|
||||
**kwargs
|
||||
)
|
||||
return cls(config)
|
||||
|
||||
def _make_request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
**kwargs: Any
|
||||
) -> requests.Response:
|
||||
"""Make HTTP request to MCP gateway.
|
||||
|
||||
Args:
|
||||
method: HTTP method (GET, POST, etc.)
|
||||
path: API path
|
||||
**kwargs: Additional request arguments
|
||||
|
||||
Returns:
|
||||
Response object
|
||||
|
||||
Raises:
|
||||
GoMicroConnectionError: If connection fails
|
||||
GoMicroAuthError: If authentication fails
|
||||
"""
|
||||
url = f"{self.config.gateway_url}{path}"
|
||||
kwargs.setdefault("timeout", self.config.timeout)
|
||||
kwargs.setdefault("verify", self.config.verify_ssl)
|
||||
|
||||
try:
|
||||
response = self._session.request(method, url, **kwargs)
|
||||
|
||||
if response.status_code == 401:
|
||||
raise GoMicroAuthError("Authentication failed")
|
||||
elif response.status_code == 403:
|
||||
raise GoMicroAuthError("Forbidden: insufficient permissions")
|
||||
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
except requests.ConnectionError as e:
|
||||
raise GoMicroConnectionError(
|
||||
f"Failed to connect to MCP gateway at {url}: {e}"
|
||||
)
|
||||
except requests.Timeout as e:
|
||||
raise GoMicroConnectionError(
|
||||
f"Request to MCP gateway timed out: {e}"
|
||||
)
|
||||
except requests.RequestException as e:
|
||||
if isinstance(e, (GoMicroConnectionError, GoMicroAuthError)):
|
||||
raise
|
||||
raise GoMicroConnectionError(f"Request failed: {e}")
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Refresh tool list from MCP gateway.
|
||||
|
||||
Raises:
|
||||
GoMicroConnectionError: If unable to connect to gateway
|
||||
"""
|
||||
response = self._make_request("GET", "/mcp/tools")
|
||||
data = response.json()
|
||||
|
||||
tools_data = data.get("tools", [])
|
||||
self._tools = [
|
||||
GoMicroTool(
|
||||
name=tool["name"],
|
||||
service=tool["service"],
|
||||
endpoint=tool["endpoint"],
|
||||
description=tool.get("description", ""),
|
||||
example=tool.get("example"),
|
||||
scopes=tool.get("scopes"),
|
||||
metadata=tool.get("metadata", {})
|
||||
)
|
||||
for tool in tools_data
|
||||
]
|
||||
|
||||
def get_tools(
|
||||
self,
|
||||
service_filter: Optional[str] = None,
|
||||
name_pattern: Optional[str] = None,
|
||||
include: Optional[List[str]] = None,
|
||||
exclude: Optional[List[str]] = None,
|
||||
) -> List[Tool]:
|
||||
"""Get LangChain tools from Go Micro services.
|
||||
|
||||
Args:
|
||||
service_filter: Filter tools by service name
|
||||
name_pattern: Filter tools by name pattern (regex)
|
||||
include: List of tool names to include
|
||||
exclude: List of tool names to exclude
|
||||
|
||||
Returns:
|
||||
List of LangChain Tool objects
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> # Get all tools
|
||||
>>> all_tools = toolkit.get_tools()
|
||||
>>> # Get only user service tools
|
||||
>>> user_tools = toolkit.get_tools(service_filter="users")
|
||||
>>> # Get specific tools
|
||||
>>> selected_tools = toolkit.get_tools(include=["users.Users.Get"])
|
||||
"""
|
||||
if self._tools is None:
|
||||
self.refresh()
|
||||
|
||||
tools = self._tools or []
|
||||
|
||||
# Apply filters
|
||||
if service_filter:
|
||||
tools = [t for t in tools if t.service == service_filter]
|
||||
|
||||
if name_pattern:
|
||||
pattern = re.compile(name_pattern)
|
||||
tools = [t for t in tools if pattern.match(t.name)]
|
||||
|
||||
if include:
|
||||
tools = [t for t in tools if t.name in include]
|
||||
|
||||
if exclude:
|
||||
tools = [t for t in tools if t.name not in exclude]
|
||||
|
||||
# Convert to LangChain tools
|
||||
return [self._create_langchain_tool(tool) for tool in tools]
|
||||
|
||||
def _create_langchain_tool(self, tool: GoMicroTool) -> Tool:
|
||||
"""Create a LangChain Tool from a GoMicroTool.
|
||||
|
||||
Args:
|
||||
tool: GoMicroTool to convert
|
||||
|
||||
Returns:
|
||||
LangChain Tool object
|
||||
"""
|
||||
def tool_func(arguments: str) -> str:
|
||||
"""Execute the tool.
|
||||
|
||||
Args:
|
||||
arguments: JSON string with tool arguments
|
||||
|
||||
Returns:
|
||||
JSON string with tool result
|
||||
"""
|
||||
return self.call_tool(tool.name, arguments)
|
||||
|
||||
# Build description with example if available
|
||||
description = tool.description
|
||||
if tool.example:
|
||||
description += f"\n\nExample input: {tool.example}"
|
||||
|
||||
return Tool(
|
||||
name=tool.name,
|
||||
func=tool_func,
|
||||
description=description,
|
||||
)
|
||||
|
||||
def call_tool(self, tool_name: str, arguments: str) -> str:
|
||||
"""Call a specific tool directly.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool to call
|
||||
arguments: JSON string with tool arguments
|
||||
|
||||
Returns:
|
||||
JSON string with tool result
|
||||
|
||||
Raises:
|
||||
GoMicroToolError: If tool execution fails
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> result = toolkit.call_tool(
|
||||
... "users.Users.Get",
|
||||
... '{"id": "user-123"}'
|
||||
... )
|
||||
"""
|
||||
# Parse arguments
|
||||
try:
|
||||
args = json.loads(arguments) if isinstance(arguments, str) else arguments
|
||||
except json.JSONDecodeError as e:
|
||||
raise GoMicroToolError(f"Invalid JSON arguments: {e}")
|
||||
|
||||
# Make request
|
||||
try:
|
||||
response = self._make_request(
|
||||
"POST",
|
||||
"/mcp/call",
|
||||
json={"name": tool_name, "arguments": args}
|
||||
)
|
||||
return json.dumps(response.json())
|
||||
except requests.RequestException as e:
|
||||
raise GoMicroToolError(f"Tool execution failed: {e}")
|
||||
|
||||
def list_tools(self) -> List[GoMicroTool]:
|
||||
"""Get raw list of available tools.
|
||||
|
||||
Returns:
|
||||
List of GoMicroTool objects
|
||||
|
||||
Example:
|
||||
>>> toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
>>> for tool in toolkit.list_tools():
|
||||
... print(f"{tool.name}: {tool.description}")
|
||||
"""
|
||||
if self._tools is None:
|
||||
self.refresh()
|
||||
return self._tools or []
|
||||
@@ -0,0 +1,73 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "langchain-go-micro"
|
||||
version = "0.1.0"
|
||||
description = "LangChain integration for Go Micro services via MCP"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {text = "Apache-2.0"}
|
||||
authors = [
|
||||
{name = "Micro Team", email = "hello@micro.dev"}
|
||||
]
|
||||
keywords = ["langchain", "go-micro", "mcp", "microservices", "ai", "agents"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"langchain>=0.1.0",
|
||||
"requests>=2.31.0",
|
||||
"pydantic>=2.0.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
"black>=23.0.0",
|
||||
"mypy>=1.0.0",
|
||||
"ruff>=0.1.0",
|
||||
"types-requests>=2.31.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/micro/go-micro"
|
||||
Documentation = "https://github.com/micro/go-micro/tree/master/contrib/langchain-go-micro"
|
||||
Repository = "https://github.com/micro/go-micro"
|
||||
Issues = "https://github.com/micro/go-micro/issues"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["langchain_go_micro*"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ['py38', 'py39', 'py310', 'py311']
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
@@ -0,0 +1,219 @@
|
||||
"""Tests for GoMicroToolkit."""
|
||||
|
||||
import json
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from langchain_go_micro import GoMicroToolkit, GoMicroConfig
|
||||
from langchain_go_micro.exceptions import (
|
||||
GoMicroConnectionError,
|
||||
GoMicroAuthError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_gateway_response():
|
||||
"""Mock MCP gateway response."""
|
||||
return {
|
||||
"tools": [
|
||||
{
|
||||
"name": "users.Users.Get",
|
||||
"service": "users",
|
||||
"endpoint": "Users.Get",
|
||||
"description": "Get a user by ID",
|
||||
"example": '{"id": "user-123"}',
|
||||
"scopes": ["users:read"],
|
||||
"metadata": {
|
||||
"description": "Get a user by ID",
|
||||
"example": '{"id": "user-123"}',
|
||||
"scopes": "users:read"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "users.Users.Create",
|
||||
"service": "users",
|
||||
"endpoint": "Users.Create",
|
||||
"description": "Create a new user",
|
||||
"example": '{"name": "Alice", "email": "alice@example.com"}',
|
||||
"scopes": ["users:write"],
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
|
||||
|
||||
class TestGoMicroConfig:
|
||||
"""Tests for GoMicroConfig."""
|
||||
|
||||
def test_config_defaults(self):
|
||||
"""Test config default values."""
|
||||
config = GoMicroConfig(gateway_url="http://localhost:3000")
|
||||
|
||||
assert config.gateway_url == "http://localhost:3000"
|
||||
assert config.auth_token is None
|
||||
assert config.timeout == 30
|
||||
assert config.retry_count == 3
|
||||
assert config.retry_delay == 1.0
|
||||
assert config.verify_ssl is True
|
||||
|
||||
def test_config_custom_values(self):
|
||||
"""Test config with custom values."""
|
||||
config = GoMicroConfig(
|
||||
gateway_url="http://localhost:8080",
|
||||
auth_token="test-token",
|
||||
timeout=60,
|
||||
retry_count=5,
|
||||
retry_delay=2.0,
|
||||
verify_ssl=False
|
||||
)
|
||||
|
||||
assert config.gateway_url == "http://localhost:8080"
|
||||
assert config.auth_token == "test-token"
|
||||
assert config.timeout == 60
|
||||
assert config.retry_count == 5
|
||||
assert config.retry_delay == 2.0
|
||||
assert config.verify_ssl is False
|
||||
|
||||
|
||||
class TestGoMicroToolkit:
|
||||
"""Tests for GoMicroToolkit."""
|
||||
|
||||
def test_from_gateway(self):
|
||||
"""Test creating toolkit from gateway URL."""
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
assert toolkit.config.gateway_url == "http://localhost:3000"
|
||||
assert toolkit.config.auth_token is None
|
||||
|
||||
def test_from_gateway_with_auth(self):
|
||||
"""Test creating toolkit with authentication."""
|
||||
toolkit = GoMicroToolkit.from_gateway(
|
||||
"http://localhost:3000",
|
||||
auth_token="test-token"
|
||||
)
|
||||
|
||||
assert toolkit.config.auth_token == "test-token"
|
||||
assert "Authorization" in toolkit._session.headers
|
||||
assert toolkit._session.headers["Authorization"] == "Bearer test-token"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_refresh(self, mock_request, mock_gateway_response):
|
||||
"""Test refreshing tool list."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
toolkit.refresh()
|
||||
|
||||
assert len(toolkit._tools) == 2
|
||||
assert toolkit._tools[0].name == "users.Users.Get"
|
||||
assert toolkit._tools[1].name == "users.Users.Create"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools(self, mock_request, mock_gateway_response):
|
||||
"""Test getting LangChain tools."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools()
|
||||
|
||||
assert len(tools) == 2
|
||||
assert tools[0].name == "users.Users.Get"
|
||||
assert tools[1].name == "users.Users.Create"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools_with_service_filter(self, mock_request, mock_gateway_response):
|
||||
"""Test filtering tools by service."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools(service_filter="users")
|
||||
|
||||
assert len(tools) == 2
|
||||
for tool in tools:
|
||||
assert "users" in tool.name
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools_with_include(self, mock_request, mock_gateway_response):
|
||||
"""Test including specific tools."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools(include=["users.Users.Get"])
|
||||
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "users.Users.Get"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_get_tools_with_exclude(self, mock_request, mock_gateway_response):
|
||||
"""Test excluding specific tools."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = mock_gateway_response
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
tools = toolkit.get_tools(exclude=["users.Users.Create"])
|
||||
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "users.Users.Get"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_call_tool(self, mock_request):
|
||||
"""Test calling a tool directly."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {"user": {"id": "user-123", "name": "Alice"}}
|
||||
mock_response.status_code = 200
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
result = toolkit.call_tool("users.Users.Get", '{"id": "user-123"}')
|
||||
|
||||
result_data = json.loads(result)
|
||||
assert result_data["user"]["id"] == "user-123"
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_connection_error(self, mock_request):
|
||||
"""Test handling connection errors."""
|
||||
mock_request.side_effect = requests.ConnectionError("Connection failed")
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
with pytest.raises(GoMicroConnectionError):
|
||||
toolkit.refresh()
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_auth_error(self, mock_request):
|
||||
"""Test handling authentication errors."""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 401
|
||||
mock_request.return_value = mock_response
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
with pytest.raises(GoMicroAuthError):
|
||||
toolkit.refresh()
|
||||
|
||||
@patch("requests.Session.request")
|
||||
def test_timeout(self, mock_request):
|
||||
"""Test handling timeouts."""
|
||||
mock_request.side_effect = requests.Timeout("Request timed out")
|
||||
|
||||
toolkit = GoMicroToolkit.from_gateway("http://localhost:3000")
|
||||
|
||||
with pytest.raises(GoMicroConnectionError):
|
||||
toolkit.refresh()
|
||||
Reference in New Issue
Block a user