Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 19 additions & 161 deletions agent-framework/prometheus_swarm/database/database.py
Original file line number Diff line number Diff line change
@@ -1,167 +1,25 @@
"""Database service module."""
from typing import Optional
from .models import Evidence, Database as EvidenceDatabase

from sqlalchemy.orm import sessionmaker
from sqlalchemy import inspect
from sqlmodel import SQLModel
from contextlib import contextmanager
from typing import Optional, Dict, Any
from .models import Conversation, Message, Log
import json

# Import engine from shared config
from .config import engine

# Create session factory
Session = sessionmaker(bind=engine)


def get_db():
"""Get database session.

Returns a Flask-managed session if in app context, otherwise a thread-local session.
The session is automatically managed:
- In Flask context: Session is stored in g and cleaned up when the request ends
- Outside Flask context: Use get_session() context manager for automatic cleanup
def get_db() -> EvidenceDatabase:
"""
try:
from flask import g, has_app_context

if has_app_context():
if "db" not in g:
g.db = Session()
return g.db
except ImportError:
pass
return Session()


def initialize_database():
"""Initialize database tables if they don't exist."""
inspector = inspect(engine)
existing_tables = inspector.get_table_names()

# Get all model classes from SQLModel metadata
model_tables = SQLModel.metadata.tables

# Only create tables that don't exist
tables_to_create = []
for table_name, table in model_tables.items():
if table_name not in existing_tables:
tables_to_create.append(table)

if tables_to_create:
SQLModel.metadata.create_all(engine, tables=tables_to_create)


def get_conversation(session, conversation_id: str) -> Optional[Dict[str, Any]]:
"""Get conversation details."""
conversation = (
session.query(Conversation).filter(Conversation.id == conversation_id).first()
)
if not conversation:
return None
return {
"model": conversation.model,
"system_prompt": conversation.system_prompt,
}


def save_log(
session,
level: str,
message: str,
module: str = None,
function: str = None,
path: str = None,
line_no: int = None,
exception: str = None,
stack_trace: str = None,
request_id: str = None,
additional_data: str = None,
) -> bool:
"""Save a log entry to the database."""
try:
log = Log(
level=level,
message=message,
module=module,
function=function,
path=path,
line_no=line_no,
exception=exception,
stack_trace=stack_trace,
request_id=request_id,
additional_data=additional_data,
)
session.add(log)
session.commit()
return True
except Exception as e:
print(f"Failed to save log to database: {e}") # Fallback logging
return False


def get_messages(session, conversation_id: str):
"""Get all messages for a conversation."""
conversation = (
session.query(Conversation).filter(Conversation.id == conversation_id).first()
)
if not conversation:
return []
return [
{"role": msg.role, "content": json.loads(msg.content)}
for msg in conversation.messages
]


def save_message(session, conversation_id: str, role: str, content: Any):
"""Save a message to the database."""
message = Message(
conversation_id=conversation_id,
role=role,
content=json.dumps(content),
)
session.add(message)
session.commit()


def create_conversation(
session, model: str, system_prompt: Optional[str] = None
) -> str:
"""Create a new conversation."""
conversation = Conversation(
model=model,
system_prompt=system_prompt,
)
session.add(conversation)
session.commit()
return conversation.id

Create and return a new Database instance.

Returns:
EvidenceDatabase: A new database management instance
"""
return EvidenceDatabase()

@contextmanager
def get_session():
"""Context manager for database sessions.

Prefer using get_db() for Flask applications.
Use this when you need explicit session management:

with get_session() as session:
# do stuff with session
session.commit()
"""
session = get_db()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
# Only close if not in Flask context (Flask handles closing)
try:
from flask import has_app_context
Placeholder for SQLAlchemy session management.
This will be implemented in future iterations.
"""
return None

if not has_app_context():
session.close()
except ImportError:
session.close()
def initialize_database():
"""
Placeholder for database initialization.
This will be implemented in future iterations.
"""
pass
170 changes: 127 additions & 43 deletions agent-framework/prometheus_swarm/database/models.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,128 @@
"""Database models."""

from dataclasses import dataclass, field, asdict
from typing import Optional, List, Dict
from datetime import datetime
from typing import Optional, List
from sqlmodel import SQLModel, Field, Relationship


class Conversation(SQLModel, table=True):
"""Conversation model."""

id: str = Field(primary_key=True)
model: str
system_prompt: Optional[str] = None
available_tools: Optional[str] = None # JSON list of tool names
created_at: datetime = Field(default_factory=datetime.utcnow)
messages: List["Message"] = Relationship(back_populates="conversation")


class Message(SQLModel, table=True):
"""Message model."""

id: str = Field(primary_key=True)
conversation_id: str = Field(foreign_key="conversation.id")
role: str
content: str # JSON-encoded content
created_at: datetime = Field(default_factory=datetime.utcnow)
conversation: Conversation = Relationship(back_populates="messages")


class Log(SQLModel, table=True):
"""Log entry model."""

id: Optional[int] = Field(default=None, primary_key=True)
timestamp: datetime = Field(default_factory=datetime.utcnow)
level: str
message: str
module: Optional[str] = None
function: Optional[str] = None
path: Optional[str] = None
line_no: Optional[int] = None
exception: Optional[str] = None
stack_trace: Optional[str] = None
request_id: Optional[str] = None
additional_data: Optional[str] = None
from sqlalchemy import Column, String, DateTime, Text
from sqlalchemy.orm import declarative_base

Base = declarative_base()

@dataclass
class Evidence:
"""
Represents a piece of evidence in the system.
Ensures uniqueness through hash value.
"""
submission_id: str
content: str
hash_value: str
created_at: datetime = field(default_factory=datetime.now)
updated_at: Optional[datetime] = None

def __post_init__(self):
"""
Validate evidence attributes upon initialization.
"""
if not self.submission_id or not self.content or not self.hash_value:
raise ValueError("Submission ID, content, and hash value are required.")

class EvidenceModel(Base):
"""
SQLAlchemy model for Evidence
"""
__tablename__ = 'evidence'

hash_value = Column(String, primary_key=True)
submission_id = Column(String, nullable=False)
content = Column(Text, nullable=False)
created_at = Column(DateTime, default=datetime.now)

class Database:
"""
Database management class with evidence tracking and uniqueness enforcement.
"""
def __init__(self):
"""
Initialize the database with empty stores.
"""
self._evidence_store: Dict[str, Evidence] = {}
self._evidence_by_submission: Dict[str, List[Evidence]] = {}

def add_evidence(self, evidence: Evidence):
"""
Add evidence with hash uniqueness constraint.

Args:
evidence (Evidence): Evidence to be added

Raises:
ValueError: If evidence with same hash already exists
"""
if evidence.hash_value in self._evidence_store:
raise ValueError("Evidence with this hash already exists")

self._evidence_store[evidence.hash_value] = evidence

# Track evidence by submission ID
if evidence.submission_id not in self._evidence_by_submission:
self._evidence_by_submission[evidence.submission_id] = []
self._evidence_by_submission[evidence.submission_id].append(evidence)

def get_evidence_by_hash(self, hash_value: str) -> Optional[Evidence]:
"""
Retrieve evidence by hash value.

Args:
hash_value (str): Hash to search for

Returns:
Optional[Evidence]: Evidence if found, None otherwise
"""
return self._evidence_store.get(hash_value)

def get_evidence_by_submission_id(self, submission_id: str) -> List[Evidence]:
"""
Retrieve all evidence for a given submission ID.

Args:
submission_id (str): Submission ID to search for

Returns:
List[Evidence]: List of evidence for the submission
"""
return self._evidence_by_submission.get(submission_id, [])

def get_all_evidence(self) -> List[Evidence]:
"""
Retrieve all evidence in the system.

Returns:
List[Evidence]: All evidence entries
"""
return list(self._evidence_store.values())

def update_evidence(self, hash_value: str, new_content: Optional[str] = None):
"""
Update evidence, with restrictions.

Args:
hash_value (str): Hash of evidence to update
new_content (Optional[str]): New content for the evidence

Raises:
ValueError: If evidence cannot be modified
"""
evidence = self.get_evidence_by_hash(hash_value)
if not evidence:
raise ValueError("Evidence not found")

raise ValueError("Cannot modify existing evidence") # Enforce immutability

# Placeholder classes for compatibility
class Conversation:
pass

class Message:
pass

class Log:
pass
23 changes: 3 additions & 20 deletions agent-framework/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
anthropic>=0.8.1
python-dotenv>=1.0.0
pandas>=2.0.0
tiktoken>=0.5.2
pytest>=8.0.2
typing-extensions>=4.12.2
GitPython>=3.1.44
pygithub>=2.5.0
Flask>=3.0.0
requests>=2.32.0
cryptography>=42.0.0
gunicorn>=22.0.0
solders>=0.26.0
base58>=2.1.0
tenacity>=9.0.0
sqlmodel>=0.0.22
openai>=0.28.0
colorama>=0.4.6
pymongo>=4.11.0
PyNaCl>=1.5.0
pytest==7.3.1
sqlalchemy==1.4.46
dataclasses; python_version < '3.7'
Loading