Initial commit
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
# API Integration
|
||||
|
||||
This document explains how Edison integrates with the OpenAI API to translate natural language into shell commands.
|
||||
|
||||
## Overview
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[User Query] --> B[Prompt Construction]
|
||||
B --> C[API Request]
|
||||
C --> D[Response Processing]
|
||||
D --> E[Command Extraction]
|
||||
E --> F[Command Validation]
|
||||
F --> G[Command Execution]
|
||||
|
||||
style A fill:#f9d5e5,stroke:#333,stroke-width:2px
|
||||
style B fill:#eeeeee,stroke:#333,stroke-width:2px
|
||||
style C fill:#d3f6db,stroke:#333,stroke-width:2px
|
||||
style D fill:#d3f6f5,stroke:#333,stroke-width:2px
|
||||
style E fill:#d5f6d5,stroke:#333,stroke-width:2px
|
||||
style F fill:#f6f6d5,stroke:#333,stroke-width:2px
|
||||
style G fill:#f5d5f5,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
## API Client Module
|
||||
|
||||
The `api_client.py` module is responsible for all OpenAI API interactions:
|
||||
|
||||
```python
|
||||
def get_api_key(config):
|
||||
"""Get the OpenAI API key from various sources."""
|
||||
# Find API key from environment, file, or config
|
||||
|
||||
def create_client(config):
|
||||
"""Create and initialize an OpenAI client."""
|
||||
# Initialize client with API key
|
||||
|
||||
def call_api(client, config, query):
|
||||
"""Call the OpenAI API with the given query."""
|
||||
# Send request to API and extract response
|
||||
|
||||
def generate_command(client, config, query, max_retries=3):
|
||||
"""Generate a command using the OpenAI API with retry logic."""
|
||||
# Call API with retries for rate limits
|
||||
```
|
||||
|
||||
## API Key Management
|
||||
|
||||
Edison supports multiple methods for supplying the OpenAI API key, processed in this order:
|
||||
|
||||
1. **Environment Variable**: `OPENAI_API_KEY`
|
||||
2. **API Key File**: `~/.openai.apikey`
|
||||
3. **Configuration File**: `openai_api_key` in `edison.yaml`
|
||||
|
||||
This implementation is in `get_api_key()`:
|
||||
|
||||
```python
|
||||
def get_api_key(config):
|
||||
"""Get the OpenAI API key from various sources."""
|
||||
dotenv.load_dotenv()
|
||||
|
||||
# Method 1: Environment variable
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
# Method 2: File in home directory
|
||||
if not api_key:
|
||||
home_path = os.path.expanduser("~")
|
||||
api_key_path = os.path.join(home_path, ".openai.apikey")
|
||||
if os.path.exists(api_key_path):
|
||||
with open(api_key_path, 'r') as f:
|
||||
api_key = f.read().strip()
|
||||
|
||||
# Method 3: Configuration file
|
||||
if not api_key:
|
||||
api_key = config.get("openai_api_key")
|
||||
|
||||
if not api_key:
|
||||
raise ValueError("No OpenAI API key found")
|
||||
|
||||
return api_key
|
||||
```
|
||||
|
||||
## Prompt Construction
|
||||
|
||||
Edison uses a template-based approach to construct effective prompts:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant PromptManager
|
||||
participant Template
|
||||
participant APIClient
|
||||
participant OpenAI
|
||||
|
||||
User->>APIClient: Query: "list all files"
|
||||
APIClient->>PromptManager: get_full_prompt(query, shell)
|
||||
PromptManager->>Template: Load template
|
||||
Template-->>PromptManager: Template content
|
||||
PromptManager->>PromptManager: Format template with query
|
||||
PromptManager-->>APIClient: Formatted prompt
|
||||
APIClient->>OpenAI: Send API request
|
||||
OpenAI-->>APIClient: Command response
|
||||
```
|
||||
|
||||
The `prompt_manager.py` module handles this:
|
||||
|
||||
```python
|
||||
def load_prompt_template(shell="bash"):
|
||||
"""Load the prompt template with shell-specific considerations."""
|
||||
# Load and return the appropriate template
|
||||
|
||||
def get_full_prompt(query, shell="bash"):
|
||||
"""Get the full prompt for the given query and shell."""
|
||||
# Format prompt with query and shell
|
||||
```
|
||||
|
||||
### Prompt Template
|
||||
|
||||
Edison uses a prompt template (`edison.prompt`) to structure requests to the AI model. The template:
|
||||
|
||||
1. Provides context about the desired output format
|
||||
2. Includes examples of good responses
|
||||
3. Specifies the shell environment
|
||||
4. Encourages safe commands
|
||||
5. Includes the user's query
|
||||
|
||||
## API Request
|
||||
|
||||
Edison uses the OpenAI Python client library for API requests:
|
||||
|
||||
```python
|
||||
def call_api(client, config, query):
|
||||
"""Call the OpenAI API with the given query."""
|
||||
prompt = prompt_manager.get_full_prompt(query, config.get("shell", "bash"))
|
||||
system_prompt = prompt.split('\n')[0] if '\n' in prompt else prompt
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=config.get("model", "gpt-3.5-turbo"),
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=config.get("temperature", 0),
|
||||
max_tokens=config.get("max_tokens", 500),
|
||||
)
|
||||
|
||||
return response.choices[0].message.content.strip()
|
||||
```
|
||||
|
||||
### Streaming API Integration
|
||||
|
||||
Edison also supports streaming command generation, which provides a more responsive user experience:
|
||||
|
||||
```python
|
||||
def call_api_streaming(client, config, query, callback):
|
||||
"""Call the OpenAI API with streaming enabled.
|
||||
|
||||
Args:
|
||||
client: The OpenAI client
|
||||
config: The configuration dictionary
|
||||
query: The user query
|
||||
callback: Function to call with each token
|
||||
"""
|
||||
prompt = prompt_manager.get_full_prompt(query, config.get("shell", "bash"))
|
||||
system_prompt = prompt.split('\n')[0] if '\n' in prompt else prompt
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model=config.get("model", "gpt-3.5-turbo"),
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=config.get("temperature", 0),
|
||||
max_tokens=config.get("max_tokens", 500),
|
||||
stream=True # Enable streaming
|
||||
)
|
||||
|
||||
# Collect the full response while calling the callback for each chunk
|
||||
full_response = ""
|
||||
|
||||
for chunk in response:
|
||||
if chunk.choices and chunk.choices[0].delta.content:
|
||||
content = chunk.choices[0].delta.content
|
||||
full_response += content
|
||||
callback(content) # Call the callback with each chunk
|
||||
|
||||
return full_response.strip()
|
||||
```
|
||||
|
||||
This streaming functionality is integrated with the UI through a callback function that updates the display in real-time as tokens are received.
|
||||
|
||||
### Request Parameters
|
||||
|
||||
The API request includes these key parameters:
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| model | The OpenAI model to use | gpt-3.5-turbo |
|
||||
| temperature | Randomness of completions (0-1) | 0 |
|
||||
| max_tokens | Maximum tokens in response | 500 |
|
||||
|
||||
## Error Handling and Retries
|
||||
|
||||
The `generate_command()` function implements retry logic to handle rate limiting:
|
||||
|
||||
```python
|
||||
def generate_command(client, config, query, max_retries=3):
|
||||
"""Generate a command using the OpenAI API with retry logic."""
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
return call_api(client, config, query)
|
||||
except Exception as e:
|
||||
if "rate limit" in str(e).lower() and attempt < max_retries - 1:
|
||||
wait_time = 2 ** attempt # Exponential backoff
|
||||
logger.warning(f"Rate limited. Retrying in {wait_time} seconds...")
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
logger.error(f"Error after {attempt+1} attempts: {str(e)}")
|
||||
raise
|
||||
```
|
||||
|
||||
Key features:
|
||||
- Exponential backoff for rate limits
|
||||
- Maximum retry attempts
|
||||
- Detailed error logging
|
||||
|
||||
## Command Processing and Validation
|
||||
|
||||
After receiving the API response, Edison:
|
||||
|
||||
1. Extracts the command from the response
|
||||
2. Validates the command for safety
|
||||
3. Checks for markdown or other formatting issues
|
||||
4. Prepares the command for execution
|
||||
|
||||
## Extending the API Integration
|
||||
|
||||
To support additional AI providers or models:
|
||||
|
||||
1. **Create a new client factory function**:
|
||||
```python
|
||||
def create_anthropic_client(config):
|
||||
# Initialize Anthropic client
|
||||
```
|
||||
|
||||
2. **Add a model selection mechanism**:
|
||||
```python
|
||||
def get_client_for_model(config):
|
||||
model = config.get("model", "gpt-3.5-turbo")
|
||||
if model.startswith("claude"):
|
||||
return create_anthropic_client(config)
|
||||
else:
|
||||
return create_client(config)
|
||||
```
|
||||
|
||||
3. **Implement provider-specific API call function**:
|
||||
```python
|
||||
def call_anthropic_api(client, config, query):
|
||||
# Format request for Anthropic API
|
||||
```
|
||||
|
||||
4. **Update the command generation logic**:
|
||||
```python
|
||||
def generate_command(client, config, query, max_retries=3):
|
||||
model = config.get("model", "gpt-3.5-turbo")
|
||||
if model.startswith("claude"):
|
||||
return call_anthropic_api(client, config, query)
|
||||
else:
|
||||
return call_api(client, config, query)
|
||||
```
|
||||
|
||||
## API Response Examples
|
||||
|
||||
### Successful Response
|
||||
|
||||
```json
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"content": "ls -la",
|
||||
"role": "assistant"
|
||||
},
|
||||
"index": 0,
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"message": "Rate limit exceeded",
|
||||
"type": "rate_limit_error",
|
||||
"param": null,
|
||||
"code": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
To optimize API usage and performance:
|
||||
|
||||
1. **Use Efficient Models**: Default to `gpt-3.5-turbo` for lower latency
|
||||
2. **Limit Token Usage**: Keep max_tokens reasonable (default 500)
|
||||
3. **Request Caching**: Consider implementing caching for common queries
|
||||
4. **Concurrent Requests**: For batch processing, consider async requests
|
||||
5. **Prompt Optimization**: Keep prompt templates concise but effective
|
||||
Reference in New Issue
Block a user