A modern REST API service for managing and serving AI prompts
Project description
๐ Exemplar Prompt Hub
A modern REST API service for managing and serving AI prompts. This service provides a centralized repository for storing, versioning, and retrieving prompts for various AI applications. It uses PostgreSQL as the database for robust and scalable data management.
๐ Table of Contents
- Features
- Getting Started
- Running Tests
- Contributing
- License
- API Documentation
- API Usage Examples
- Project Structure
- Database Table Structure
- Updating Prompts with Versioning
โจ Features
For a detailed checklist of implemented and planned features, see FEATURES.md.
- RESTful API for prompt management
- Version control for prompts
- Tag-based prompt organization
- Metadata support for prompts
- Authentication and authorization
- Search and filtering capabilities
๐ ๏ธ Getting Started
Prerequisites
- Python 3.8 or higher
- pip (Python package manager)
- Git
- PostgreSQL (for database) (by default it uses sqlite as per .env.example)
- Docker and Docker Compose (for containerized setup)
Installation
Using pip
You can install the package directly from PyPI:
๐ Python (pip, Virtual Environment)
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install exemplar-prompt-hub
# Create a .env file and copy content from .env.example as per the github repo
cp .env.example .env
# Edit .env as needed
prompt-hub
Or install from the source:
# Clone the repository
git clone https://github.com/yourusername/exemplar-prompt-hub.git
cd exemplar-prompt-hub
# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
# Install the package
pip install -e .
# Copy .env.example to .env [copy .env.example from github repo branch]
cp .env.example .env
# Edit .env to configure your database and other settings
After installation, you can use the following command:
prompt-hub- Start the FastAPI server
Using Docker
The easiest way to get started is using Docker Compose:
-
Clone the repository:
git clone https://github.com/yourusername/exemplar-prompt-hub.git cd exemplar-prompt-hub
-
Start the services:
docker-compose up -d
This will start:
- FastAPI backend at http://localhost:8000
- PostgreSQL database at localhost:5432
-
Access the services:
- API Documentation: http://localhost:8000/docs
-
Stop the services:
docker-compose down
Manual Installation
If you prefer to run the services manually:
-
Clone the repository:
git clone https://github.com/yourusername/exemplar-prompt-hub.git cd exemplar-prompt-hub
-
Create a virtual environment:
python -m venv venv source venv/bin/activate # On Windows, use `venv\\Scripts\\activate`
-
Install dependencies:
pip install -r requirements.txt
-
Set up environment variables:
- Copy
.env.exampleto.env:cp .env.example .env
- Edit
.envto configure your database and other settings.
- Copy
-
Start the application:
uvicorn app.main:app --reload
Running Tests
To run the tests, use:
pytest
For detailed test coverage, use:
pytest --cov=app --cov-report=term-missing
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For detailed contribution guidelines, please refer to the CONTRIBUTING.md file.
License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ API Documentation
Once the server is running, you can access the interactive API documentation at:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
๐ API Usage Examples
Here are some example curl commands to interact with the API:
Create a Prompt
curl -X POST "http://localhost:8000/api/v1/prompts/" \
-H "Content-Type: application/json" \
-d '{
"name": "example-prompt",
"text": "This is an example prompt text",
"description": "A sample prompt for demonstration",
"meta": {
"author": "test-user",
"category": "example"
},
"tags": ["example", "test"]
}'
Note: The version field is optional and handled automatically by the API. New prompts start with version 1, and subsequent updates will increment the version number automatically.
Get All Prompts
# Get all prompts
curl "http://localhost:8000/api/v1/prompts/"
# Get prompts with search
curl "http://localhost:8000/api/v1/prompts/?search=example"
# Get prompts with tag filter
curl "http://localhost:8000/api/v1/prompts/?tag=test"
# Get prompts with pagination
curl "http://localhost:8000/api/v1/prompts/?skip=0&limit=10"
Get a Specific Prompt
# Replace {prompt_id} with actual ID
curl "http://localhost:8000/api/v1/prompts/{prompt_id}"
Update a Prompt
curl -X PUT "http://localhost:8000/api/v1/prompts/{prompt_id}" \
-H "Content-Type: application/json" \
-d '{
"text": "Updated prompt text",
"description": "Updated description",
"meta": {
"author": "test-user",
"category": "updated"
},
"tags": ["updated", "test"]
}'
Delete a Prompt
curl -X DELETE "http://localhost:8000/api/v1/prompts/{prompt_id}"
๐ Project Structure
exemplar-prompt-hub/
โโโ app/
โ โโโ api/
โ โ โโโ endpoints/
โ โ โโโ prompts.py
โ โโโ core/
โ โ โโโ config.py
โ โโโ db/
โ โ โโโ base_class.py
โ โ โโโ models.py
โ โ โโโ session.py
โ โโโ schemas/
โ โ โโโ prompt.py
โ โโโ main.py
โโโ tests/
โ โโโ test_prompts.py
โโโ alembic/
โ โโโ versions/
โโโ .env.example
โโโ .gitignore
โโโ docker-compose.yml
โโโ Dockerfile
โโโ LICENSE
โโโ MANIFEST.in
โโโ pyproject.toml
โโโ README.md
โโโ requirements.txt
โโโ setup.py
๐ Database Table Structure
Prompts Table
CREATE TABLE prompts (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
text TEXT NOT NULL,
description TEXT,
version INTEGER NOT NULL,
meta JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE
);
Tags Table
CREATE TABLE tags (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
Prompt Tags Table (Many-to-Many Relationship)
CREATE TABLE prompt_tags (
prompt_id INTEGER REFERENCES prompts(id) ON DELETE CASCADE,
tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (prompt_id, tag_id)
);
๐ Updating Prompts with Versioning
The API supports versioning of prompts. When updating a prompt:
- The current version is incremented
- A new record is created with the updated content
- The old version is preserved for reference
To update a prompt, use the PUT endpoint with the prompt ID:
curl -X PUT "http://localhost:8000/api/v1/prompts/{prompt_id}" \
-H "Content-Type: application/json" \
-d '{
"text": "Updated prompt text",
"description": "Updated description",
"meta": {
"author": "test-user",
"category": "updated"
},
"tags": ["updated", "test"]
}'
The API will automatically handle versioning and maintain the history of changes.
๐จ Using Prompts with Jinja Templating
The API supports Jinja2 templating in prompts, allowing you to create dynamic prompts with variables. Here's how to use it:
1. Create a Template Prompt
# Create a basic greeting template
curl -X POST "http://localhost:8000/api/v1/prompts/" \
-H "Content-Type: application/json" \
-d '{
"name": "greeting-template",
"text": "Hello {{ name }}! Welcome to {{ platform }}. Your role is {{ role }}.",
"description": "A greeting template with dynamic variables",
"meta": {
"template_variables": ["name", "platform", "role"],
"author": "test-user"
},
"tags": ["template", "greeting"]
}'
# Response:
{
"id": 1,
"name": "greeting-template",
"text": "Hello {{ name }}! Welcome to {{ platform }}. Your role is {{ role }}.",
"description": "A greeting template with dynamic variables",
"version": 1,
"meta": {
"template_variables": ["name", "platform", "role"],
"author": "test-user"
},
"tags": [
{"id": 1, "name": "template"},
{"id": 2, "name": "greeting"}
],
"created_at": "2024-03-20T10:00:00",
"updated_at": null
}
See examples/jinja_sample.py for a complete Python implementation of how to use this template with Jinja2.
2. Create a Template with Control Structures
# Create a template with if-else and loops
curl -X POST "http://localhost:8000/api/v1/prompts/" \
-H "Content-Type: application/json" \
-d '{
"name": "advanced-template",
"text": "{% if user_type == \"admin\" %}Welcome, Administrator!{% else %}Welcome, User!{% endif %}\n\n{% for item in features %}- {{ item }}\n{% endfor %}",
"description": "Advanced template with control structures",
"meta": {
"template_variables": ["user_type", "features"],
"author": "test-user"
},
"tags": ["template", "advanced"]
}'
# Response:
{
"id": 2,
"name": "advanced-template",
"text": "{% if user_type == \"admin\" %}Welcome, Administrator!{% else %}Welcome, User!{% endif %}\n\n{% for item in features %}- {{ item }}\n{% endfor %}",
"description": "Advanced template with control structures",
"version": 1,
"meta": {
"template_variables": ["user_type", "features"],
"author": "test-user"
},
"tags": [
{"id": 1, "name": "template"},
{"id": 3, "name": "advanced"}
],
"created_at": "2024-03-20T10:05:00",
"updated_at": null
}
3. Create a Template with Macros
# Create a template with Jinja2 macros
curl -X POST "http://localhost:8000/api/v1/prompts/" \
-H "Content-Type: application/json" \
-d '{
"name": "macro-template",
"text": "{% macro format_item(item) %}- {{ item|title }}\n{% endmacro %}\n\n{% for category in categories %}{{ category.name }}:\n{% for item in category.items %}{{ format_item(item) }}{% endfor %}\n{% endfor %}",
"description": "Template using Jinja2 macros",
"meta": {
"template_variables": ["categories"],
"author": "test-user"
},
"tags": ["template", "macro"]
}'
# Response:
{
"id": 3,
"name": "macro-template",
"text": "{% macro format_item(item) %}- {{ item|title }}\n{% endmacro %}\n\n{% for category in categories %}{{ category.name }}:\n{% for item in category.items %}{{ format_item(item) }}{% endfor %}\n{% endfor %}",
"description": "Template using Jinja2 macros",
"version": 1,
"meta": {
"template_variables": ["categories"],
"author": "test-user"
},
"tags": [
{"id": 1, "name": "template"},
{"id": 4, "name": "macro"}
],
"created_at": "2024-03-20T10:10:00",
"updated_at": null
}
4. Fetch and Use Templates
# Fetch a template by ID
curl "http://localhost:8000/api/v1/prompts/1"
# Response:
{
"id": 1,
"name": "greeting-template",
"text": "Hello {{ name }}! Welcome to {{ platform }}. Your role is {{ role }}.",
"description": "A greeting template with dynamic variables",
"version": 1,
"meta": {
"template_variables": ["name", "platform", "role"],
"author": "test-user"
},
"tags": [
{"id": 1, "name": "template"},
{"id": 2, "name": "greeting"}
],
"created_at": "2024-03-20T10:00:00",
"updated_at": null
}
# Fetch a template by name
curl "http://localhost:8000/api/v1/prompts/?search=greeting-template"
# Response:
[
{
"id": 1,
"name": "greeting-template",
"text": "Hello {{ name }}! Welcome to {{ platform }}. Your role is {{ role }}.",
"description": "A greeting template with dynamic variables",
"version": 1,
"meta": {
"template_variables": ["name", "platform", "role"],
"author": "test-user"
},
"tags": [
{"id": 1, "name": "template"},
{"id": 2, "name": "greeting"}
],
"created_at": "2024-03-20T10:00:00",
"updated_at": null
}
]
5. Update a Template
# Update a template with new variables
curl -X PUT "http://localhost:8000/api/v1/prompts/1" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello {{ name }}! Welcome to {{ platform }}. Your role is {{ role }}. Your department is {{ department }}.",
"description": "Updated greeting template with department",
"meta": {
"template_variables": ["name", "platform", "role", "department"],
"author": "test-user",
"updated": true
},
"tags": ["template", "greeting", "updated"]
}'
# Response:
{
"id": 1,
"name": "greeting-template",
"text": "Hello {{ name }}! Welcome to {{ platform }}. Your role is {{ role }}. Your department is {{ department }}.",
"description": "Updated greeting template with department",
"version": 2,
"meta": {
"template_variables": ["name", "platform", "role", "department"],
"author": "test-user",
"updated": true
},
"tags": [
{"id": 1, "name": "template"},
{"id": 2, "name": "greeting"},
{"id": 5, "name": "updated"}
],
"created_at": "2024-03-20T10:00:00",
"updated_at": "2024-03-20T10:15:00"
}
6. Delete a Template
# Delete a template
curl -X DELETE "http://localhost:8000/api/v1/prompts/1"
# Response: 204 No Content
7. Using Templates in Python
import requests
import jinja2
from jinja2 import Template
# Fetch the prompt template
response = requests.get("http://localhost:8000/api/v1/prompts/1")
prompt_data = response.json()
# Create a Jinja template
template = Template(prompt_data["text"])
# Render with variables
rendered_prompt = template.render(
name="John",
platform="Exemplar Prompt Hub",
role="Developer",
department="Engineering"
)
print(rendered_prompt)
# Output: Hello John! Welcome to Exemplar Prompt Hub. Your role is Developer. Your department is Engineering.
8. Using Templates in JavaScript
// Fetch and render a template
async function renderPrompt(promptId, variables) {
const response = await fetch(`http://localhost:8000/api/v1/prompts/${promptId}`);
const promptData = await response.json();
// Create template function
const template = new Function('variables', `
with(variables) {
return \`${promptData.text}\`;
}
`);
// Render with variables
return template(variables);
}
// Usage
const renderedPrompt = await renderPrompt(1, {
name: 'John',
platform: 'Exemplar Prompt Hub',
role: 'Developer',
department: 'Engineering'
});
console.log(renderedPrompt);
// Output: Hello John! Welcome to Exemplar Prompt Hub. Your role is Developer. Your department is Engineering.
Best Practices
- Document Variables: Always document template variables in the prompt's meta field
- Default Values: Consider providing default values in the template
- Error Handling: Use Jinja2's error handling features
- Security: Be careful with user input in templates
- Versioning: Use the API's versioning feature to track template changes
Example with Error Handling
from jinja2 import Template, TemplateError
try:
template = Template(prompt_data["text"])
rendered_prompt = template.render(
name="John",
platform="Exemplar Prompt Hub"
# role is missing, will use default if defined
)
except TemplateError as e:
print(f"Template error: {e}")
This templating system allows you to create dynamic, reusable prompts while maintaining version control and easy management through the API.
๐ Alternative Implementations
Python Implementations
1. Using string.Template (Built-in)
from string import Template
import requests
# Fetch the prompt
response = requests.get("http://localhost:8000/api/v1/prompts/{prompt_id}")
prompt_data = response.json()
# Create template
template = Template(prompt_data["text"])
# Render with variables
rendered_prompt = template.substitute(
name="John",
platform="Exemplar Prompt Hub",
role="Developer"
)
2. Using f-strings (Python 3.6+)
import requests
def render_prompt(template: str, **kwargs) -> str:
return template.format(**kwargs)
# Fetch the prompt
response = requests.get("http://localhost:8000/api/v1/prompts/{prompt_id}")
prompt_data = response.json()
# Render with variables
rendered_prompt = render_prompt(
prompt_data["text"],
name="John",
platform="Exemplar Prompt Hub",
role="Developer"
)
3. Using Template Engine (Mako)
from mako.template import Template
import requests
# Fetch the prompt
response = requests.get("http://localhost:8000/api/v1/prompts/{prompt_id}")
prompt_data = response.json()
# Create template
template = Template(prompt_data["text"])
# Render with variables
rendered_prompt = template.render(
name="John",
platform="Exemplar Prompt Hub",
role="Developer"
)
JavaScript Implementations
1. Using Template Literals
async function renderPrompt(promptId, variables) {
// Fetch the prompt
const response = await fetch(`http://localhost:8000/api/v1/prompts/${promptId}`);
const promptData = await response.json();
// Create template function
const template = new Function('variables', `
with(variables) {
return \`${promptData.text}\`;
}
`);
// Render with variables
return template(variables);
}
// Usage
const renderedPrompt = await renderPrompt('prompt_id', {
name: 'John',
platform: 'Exemplar Prompt Hub',
role: 'Developer'
});
2. Using Handlebars.js
import Handlebars from 'handlebars';
async function renderPrompt(promptId, variables) {
// Fetch the prompt
const response = await fetch(`http://localhost:8000/api/v1/prompts/${promptId}`);
const promptData = await response.json();
// Compile template
const template = Handlebars.compile(promptData.text);
// Render with variables
return template(variables);
}
// Usage
const renderedPrompt = await renderPrompt('prompt_id', {
name: 'John',
platform: 'Exemplar Prompt Hub',
role: 'Developer'
});
3. Using Mustache.js
import Mustache from 'mustache';
async function renderPrompt(promptId, variables) {
// Fetch the prompt
const response = await fetch(`http://localhost:8000/api/v1/prompts/${promptId}`);
const promptData = await response.json();
// Render with variables
return Mustache.render(promptData.text, variables);
}
// Usage
const renderedPrompt = await renderPrompt('prompt_id', {
name: 'John',
platform: 'Exemplar Prompt Hub',
role: 'Developer'
});
4. Using React with Template Strings
import React, { useState, useEffect } from 'react';
function PromptRenderer({ promptId, variables }) {
const [prompt, setPrompt] = useState('');
const [renderedPrompt, setRenderedPrompt] = useState('');
useEffect(() => {
async function fetchPrompt() {
const response = await fetch(`http://localhost:8000/api/v1/prompts/${promptId}`);
const promptData = await response.json();
setPrompt(promptData.text);
}
fetchPrompt();
}, [promptId]);
useEffect(() => {
if (prompt) {
const template = new Function('variables', `
with(variables) {
return \`${prompt}\`;
}
`);
setRenderedPrompt(template(variables));
}
}, [prompt, variables]);
return <div>{renderedPrompt}</div>;
}
// Usage
<PromptRenderer
promptId="prompt_id"
variables={{
name: 'John',
platform: 'Exemplar Prompt Hub',
role: 'Developer'
}}
/>
Comparison of Approaches
-
Python:
string.Template: Simple, built-in, limited featuresf-strings: Modern, readable, but less flexibleJinja2: Full-featured, powerful, widely usedMako: Fast, flexible, good for large templates
-
JavaScript:
- Template Literals: Native, simple, limited features
- Handlebars.js: Powerful, extensible, good for complex templates
- Mustache.js: Logic-less, simple, good for basic needs
- React: Component-based, good for UI integration
Choose the implementation that best fits your needs:
- For simple templates: Use built-in solutions (string.Template, f-strings, Template Literals)
- For complex templates: Use full-featured engines (Jinja2, Handlebars.js)
- For UI integration: Use framework-specific solutions (React)
- For performance: Consider Mako or Mustache.js
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file exemplar_prompt_hub-0.2.0.tar.gz.
File metadata
- Download URL: exemplar_prompt_hub-0.2.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d58943924ccaa94db39af55adb108d725ca2262c8579c0046ade5b5e9de2d914
|
|
| MD5 |
37afe13f8b78568666edac69d23e9f20
|
|
| BLAKE2b-256 |
d435ecb1658bcc44c879efa16eeba7eb4ec1d71fea477ce73f8a3ac97fdec102
|
File details
Details for the file exemplar_prompt_hub-0.2.0-py3-none-any.whl.
File metadata
- Download URL: exemplar_prompt_hub-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af412f934c257ae49e011cfbd7afdaa77f828f439379271057116ecf60752643
|
|
| MD5 |
e30aa8e570f778b0cefea3067f878a65
|
|
| BLAKE2b-256 |
abf78fb46259eee1cee22e018da845fa0e6f29b6e2a3154ee5dc9b0178a6a49f
|