Add initial working version

This commit is contained in:
Tim Jagenberg 2025-07-04 22:28:55 +02:00
parent e8612f5eed
commit c69e6d390d
7 changed files with 1523 additions and 0 deletions

10
.gitignore vendored
View file

@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

1
.python-version Normal file
View file

@ -0,0 +1 @@
3.13

55
README.md Normal file
View file

@ -0,0 +1,55 @@
# TheVoices
A FastMCP server providing role-based AI assistant tools for specialized AI personas and LLM model discovery.
## Features
- **Model Discovery**: Auto-detect available LLM models based on API keys
- **Role-based AI**: Create specialized AI assistants with defined expertise and personas
- **Multiple Providers**: Support for OpenAI, Anthropic, Azure, and other LLM providers
## Usage
For example in Zed:
```json
"context_servers": {
"TheVoices": {
"source": "custom",
"command": {
"path": "uvx thevoices",
"args": [],
"env": {
"LITELLM_MODEL": "openai/gpt-4.1",
"ANTHROPIC_API_KEY": "YOUR_API_KEY",
"OPENAI_API_KEY": "YOUR_API_KEY"
}
}
}
}
```
### Available Tools
#### `list_available_models()`
Returns available LLM models based on your API keys.
#### `ask_the_voice(role_title, role_description, context, task, model?, temperature?)`
Create specialized AI assistants with defined roles and expertise.
**Example:**
```python
ask_the_voice(
role_title="The Security Architect",
role_description="Senior cybersecurity expert specializing in threat analysis",
context="We detected unusual network traffic patterns...",
task="Analyze this security incident and provide remediation steps"
)
```
## Requirements
- Python 3.13+
- FastMCP 2.9.2+
- LiteLLM 1.73.6+
- Valid API keys for at least one LLM provider

15
pyproject.toml Normal file
View file

@ -0,0 +1,15 @@
[project]
name = "thevoices"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [{ name = "Tim Jagenberg", email = "tim@jagenberg.info" }]
requires-python = ">=3.13"
dependencies = ["fastmcp>=2.9.2", "litellm>=1.73.6"]
[project.scripts]
thevoices = "thevoices.__main__:run"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

View file

278
src/thevoices/__main__.py Normal file
View file

@ -0,0 +1,278 @@
#!/usr/bin/env python3
import os
from typing import List
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError
from litellm import completion, model_list
from litellm.utils import get_llm_provider # type: ignore
# Create the MCP server
mcp = FastMCP(name="TheVoicesServer")
@mcp.tool
def list_available_models() -> List[str]:
"""
Returns a list of available LLM models based on detected API keys in the environment.
Auto-discovers models from LiteLLM's model list and filters based on available API keys.
Returns:
List of model names in LiteLLM format (provider/model-name).
"""
available_models = []
# Define provider to API key mapping
provider_key_mapping = {
"openai": "OPENAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"vertex_ai": ["VERTEXAI_PROJECT", "VERTEXAI_LOCATION"], # Both needed
"azure": ["AZURE_API_KEY", "AZURE_API_BASE"], # Both needed
"cohere": "COHERE_API_KEY",
"huggingface": "HUGGINGFACE_API_KEY",
"replicate": "REPLICATE_API_TOKEN",
"together_ai": "TOGETHERAI_API_KEY",
"openrouter": "OPENROUTER_API_KEY",
"ai21": "AI21_API_KEY",
"palm": "PALM_API_KEY",
"bedrock": ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], # AWS credentials
"sagemaker": ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], # AWS credentials
}
# Check which providers have API keys available
available_providers = set()
for provider, keys in provider_key_mapping.items():
if isinstance(keys, list):
# All keys in the list must be present
if all(os.environ.get(key) for key in keys):
available_providers.add(provider)
else:
# Single key must be present
if os.environ.get(keys):
available_providers.add(provider)
# Filter models based on available providers
for model in model_list:
try:
# Get provider for this model
_, provider, _, _ = get_llm_provider(model)
# Check if we have API keys for this provider
if provider in available_providers:
available_models.append(f"{provider}/{model}")
except Exception:
# Skip models that can't be processed
continue
# Add currently selected model if set
current_model = os.environ.get("LITELLM_MODEL")
if current_model:
available_models.insert(0, current_model)
# Sort alphabetically for better readability
available_models.sort()
return available_models
@mcp.tool
def ask_the_voice(
role_title: str,
role_description: str,
context: str,
task: str,
model: str = None,
temperature: float = None,
) -> str:
"""
Role-based assistant tool for specialized AI personas and expert consultations.
This tool creates focused AI assistants by defining specific roles, contexts, and tasks.
It's designed to leverage the power of role-playing to get more targeted, expert-level
responses from language models by establishing clear personas and domains of expertise.
## Core Concept
Instead of generic AI responses, this tool creates specialized "voices" - AI personas
with distinct expertise, perspectives, and communication styles. Each voice is tailored
to specific domains, from technical analysis to creative ideation.
## Parameters
### role_title (str) - Required
The name/title of the AI persona, preferably with a definite article for clarity.
Examples:
- "The Security Architect" - for cybersecurity analysis
- "The UX Researcher" - for user experience insights
- "The Data Scientist" - for statistical analysis
- "The Creative Director" - for design and branding
- "The Systems Analyst" - for technical architecture
- "The Product Manager" - for feature prioritization
### role_description (str) - Required
A concise but comprehensive description of the role's expertise, background, and approach.
Should establish credibility and set expectations for the type of responses.
Best practices:
- Include relevant experience/background
- Mention key skills and methodologies
- Define the persona's communication style
- Establish domain expertise boundaries
Example: "A senior cybersecurity professional with 15+ years in enterprise security,
specializing in threat modeling, risk assessment, and security architecture. Known for
pragmatic, actionable advice that balances security with business needs."
### context (str) - Required
Comprehensive background information for the task (can be long, up to 75% of the maximum context window).
This is where you provide all relevant details, data, constraints, and background.
Should include:
- Current situation or problem statement
- Relevant technical details or specifications
- Business constraints or requirements
- Previous attempts or existing solutions
- Success criteria or desired outcomes
- Any relevant data, logs, or documentation
### task (str) - Required
The specific request or question you want the AI persona to address.
Should be clear, actionable, and aligned with the role's expertise.
Examples:
- "Analyze this security incident and provide a remediation plan"
- "Design a user research study for our new mobile app feature"
- "Review this architecture proposal and identify potential issues"
- "Develop a go-to-market strategy for our B2B SaaS product"
### model (str) - Optional
Override the default LLM model. Use format: "provider/model-name"
Use the `list_available_models` tool before using this.
Examples:
- "openai/gpt-4-turbo-preview" - For complex reasoning tasks
- "anthropic/claude-3-sonnet-20240229" - For detailed analysis
- "openai/gpt-3.5-turbo" - For faster, cost-effective responses
If not specified, uses the LITELLM_MODEL environment variable.
### temperature (float) - Optional
Controls response creativity and randomness (0.0-2.0).
Guidelines:
- 0.0-0.3: Highly deterministic, factual responses (technical analysis, code review)
- 0.4-0.7: Balanced creativity and consistency (general consulting, planning)
- 0.8-1.2: More creative and varied responses (brainstorming, creative writing)
- 1.3-2.0: Highly creative, experimental responses (artistic concepts, innovation)
## Returns
str: The AI persona's response as a plain string, formatted according to the role's
expertise and communication style.
## Usage Examples
### Technical Code Review
```python
response = ask_the_voice(
role_title="The Senior Software Architect",
role_description="A seasoned software architect with expertise in scalable systems,
clean code principles, and performance optimization. Provides detailed technical
reviews with actionable recommendations.",
context="We're reviewing a new microservice written in Python using FastAPI...",
task="Review this code for scalability, security, and maintainability issues.",
temperature=0.2
)
```
### Business Strategy Analysis
```python
response = ask_the_voice(
role_title="The Strategic Business Consultant",
role_description="MBA with 20+ years in strategy consulting, specializing in
market analysis, competitive positioning, and growth strategies for tech companies.",
context="Our SaaS startup is facing increased competition and slowing growth...",
task="Develop a comprehensive competitive strategy for the next 18 months.",
temperature=0.6
)
```
### Creative Brainstorming
```python
response = ask_the_voice(
role_title="The Innovation Catalyst",
role_description="A creative strategist and design thinker who excels at generating
breakthrough ideas and connecting disparate concepts into innovative solutions.",
context="We need fresh approaches to user engagement in our mobile app...",
task="Generate 10 innovative features that could revolutionize user engagement.",
temperature=1.0
)
```
## Best Practices
1. **Match Role to Task**: Ensure the persona's expertise aligns with your specific need
2. **Rich Context**: Provide comprehensive background - more context = better responses
3. **Specific Tasks**: Clear, actionable requests yield more useful outputs
4. **Temperature Tuning**: Adjust creativity level based on the type of response needed
5. **Iterative Refinement**: Use follow-up questions to dive deeper into specific areas
## Error Handling
Raises ToolError for:
- Missing or invalid model configuration
- LLM API failures or timeouts
- Malformed responses from the language model
"""
# Determine which LLM model to use
selected_model = model or os.environ.get("LITELLM_MODEL")
if not selected_model:
raise ToolError(
"Missing environment variable: LITELLM_MODEL and no model parameter provided"
)
# Construct the prompt
prompt = (
f"You are '{role_title}'\n\n"
"# Role Description\n\n"
f"{role_description}\n\n"
"# Context\n\n"
f"{context}\n\n"
"# Task\n\n"
f"{task}"
)
# Call the LLM via LiteLLM
try:
completion_params = {
"model": selected_model,
"messages": [
{"role": "system", "content": prompt},
{
"role": "user",
"content": "Please respond based on the above instructions.",
},
],
}
# Add temperature if provided
if temperature is not None:
completion_params["temperature"] = temperature
response = completion(**completion_params)
except Exception as e:
raise ToolError(f"LLM request failed: {e}")
# Extract and return the generated content
try:
content = response.choices[0].message.content # type: ignore
return content if content is not None else ""
except (AttributeError, IndexError) as e:
raise ToolError(f"Unexpected response format: {e}")
def run():
mcp.run()
run()

1164
uv.lock Normal file

File diff suppressed because it is too large Load diff