Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement]: Add 'app_type' column for AppDB model with updated API responses #1994

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e5b331e
feat (backend): created AppType enum and introduce new column 'app_ty…
aybruhm Aug 14, 2024
501841d
feat (migration): generate and apply schema migration script with Ale…
aybruhm Aug 14, 2024
474e534
feat (backend): specify enum type for 'app_type' column, extend AppTy…
aybruhm Aug 15, 2024
4296c8d
feat (backend): implement the following:
aybruhm Aug 16, 2024
a41d3d9
style (backend): format code with [email protected]
aybruhm Aug 16, 2024
7397d06
Merge branch 'main' into feature/age-540-endpoint-for-app-type
aybruhm Aug 22, 2024
ad41e91
chore (backend): rename function 'get_app_type_from_template' to 'get…
aybruhm Aug 22, 2024
61ad8c6
refactor (backend): update AppType enum to use opaque template string…
aybruhm Aug 22, 2024
5161ccc
chore (style): format cookbook with [email protected]
aybruhm Aug 22, 2024
cae8a98
refactor (backend): improve migration handling by checking only the l…
aybruhm Aug 22, 2024
1a51c59
refactor (backend): update AppType and enums in migration script
aybruhm Aug 22, 2024
3235f33
Merge branch 'feature/age-540-endpoint-for-app-type' into enhancement…
aybruhm Aug 22, 2024
c29c90f
refactor (web): add app_type to ListAppsItem interface and AppCard co…
aybruhm Aug 26, 2024
2644220
refactor (backend): simplify AppType enums
aybruhm Aug 26, 2024
0b70852
minor refactor (backend): ensure that app_type field in ListAppsItem …
aybruhm Aug 26, 2024
8736de4
Merge pull request #2013 from Agenta-AI/enhancement/refactor-migratio…
aybruhm Aug 29, 2024
1c40fc2
feat (backend): add class method for mapping AppType enum to user-fri…
aybruhm Aug 29, 2024
acdc6ad
minor refactor (backend): rename AppType class method to 'friendly_tag'
aybruhm Aug 29, 2024
fa9f607
Merge branch 'main' into feature/age-540-endpoint-for-app-type
aybruhm Aug 29, 2024
ab4c637
feat (backend): generate alembic migration script to resolve conflict…
aybruhm Aug 29, 2024
c41b713
Merge branch 'main' into feature/age-540-endpoint-for-app-type
aybruhm Sep 9, 2024
f956e40
refactor (backend): remove RAG_TEMPLATE from application types
aybruhm Sep 9, 2024
4d8b5dc
refactor (migration): regenerate alembic migration to add app_type co…
aybruhm Sep 9, 2024
436f85a
refactor (backend): remove conflicting head that is now redundant
aybruhm Sep 9, 2024
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
49 changes: 27 additions & 22 deletions agenta-backend/agenta_backend/migrations/postgres/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ def is_initial_setup(engine) -> bool:
return not all_tables_exist


async def get_applied_migrations(engine: AsyncEngine):
async def get_current_migration_head_from_db(engine: AsyncEngine):
"""
Checks the alembic_version table to get all the migrations that has been applied.
Checks the alembic_version table to get the current migration head that has been applied.

Args:
engine (Engine): The engine that connects to an sqlalchemy pool

Returns:
a list of strings
the current migration head (where 'head' is the revision stored in the migration script)
"""

async with engine.connect() as connection:
Expand All @@ -75,32 +75,37 @@ async def get_applied_migrations(engine: AsyncEngine):
# to make Alembic start tracking the migration changes.
# --------------------------------------------------------------------------------------
# This effect (the exception raising) happens for both users (first-time and returning)
return ["alembic_version"]
return "alembic_version"

applied_migrations = [row[0] for row in result.fetchall()]
return applied_migrations
migration_heads = [row[0] for row in result.fetchall()]
assert (
len(migration_heads) == 1
), "There can only be one migration head stored in the database."
return migration_heads[0]


async def get_pending_migrations():
async def get_pending_migration_head():
"""
Gets the migrations that have not been applied.
Gets the migration head that have not been applied.

Returns:
the number of pending migrations
the pending migration head
"""

engine = create_async_engine(url=os.environ["POSTGRES_URI"])
try:
applied_migrations = await get_applied_migrations(engine=engine)
migration_files = [script.revision for script in script.walk_revisions()]
pending_migrations = [m for m in migration_files if m not in applied_migrations]

if "alembic_version" in applied_migrations:
pending_migrations.append("alembic_version")
current_migration_script_head = script.get_current_head()
migration_head_from_db = await get_current_migration_head_from_db(engine=engine)

pending_migration_head = []
if current_migration_script_head != migration_head_from_db:
pending_migration_head.append(current_migration_script_head)
if "alembic_version" == migration_head_from_db:
pending_migration_head.append("alembic_version")
finally:
await engine.dispose()

return pending_migrations
return pending_migration_head


def run_alembic_migration():
Expand All @@ -109,9 +114,9 @@ def run_alembic_migration():
"""

try:
pending_migrations = asyncio.run(get_pending_migrations())
pending_migration_head = asyncio.run(get_pending_migration_head())
APPLY_AUTO_MIGRATIONS = os.environ.get("AGENTA_AUTO_MIGRATIONS")
FIRST_TIME_USER = True if "alembic_version" in pending_migrations else False
FIRST_TIME_USER = True if "alembic_version" in pending_migration_head else False

if FIRST_TIME_USER or APPLY_AUTO_MIGRATIONS == "true":
command.upgrade(alembic_cfg, "head")
Expand All @@ -133,7 +138,7 @@ def run_alembic_migration():
except Exception as e:
click.echo(
click.style(
f"\nAn ERROR occured while applying migration: {traceback.format_exc()}\nThe container will now exit.",
f"\nAn ERROR occurred while applying migration: {traceback.format_exc()}\nThe container will now exit.",
fg="red",
),
color=True,
Expand All @@ -146,11 +151,11 @@ async def check_for_new_migrations():
Checks for new migrations and notify the user.
"""

pending_migrations = await get_pending_migrations()
if len(pending_migrations) >= 1:
pending_migration_head = await get_pending_migration_head()
if len(pending_migration_head) >= 1 and isinstance(pending_migration_head[0], str):
click.echo(
click.style(
f"\nWe have detected that there are pending database migrations {pending_migrations} that need to be applied to keep the application up to date. To ensure the application functions correctly with the latest updates, please follow the guide here => https://docs.agenta.ai/self-host/migration/applying-schema-migration\n",
f"\nWe have detected that there are pending database migrations {pending_migration_head} that need to be applied to keep the application up to date. To ensure the application functions correctly with the latest updates, please follow the guide here => https://docs.agenta.ai/self-host/migration/applying-schema-migration\n",
fg="yellow",
),
color=True,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Added the 'app_type' column to the 'app_db' table

Revision ID: 1abfef8ed0ef
Revises: b80c708c21bb
Create Date: 2024-08-14 19:44:23.707519

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "1abfef8ed0ef"
down_revision: Union[str, None] = "b80c708c21bb"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###

# Create the enum type first
app_enumtype = sa.Enum(
"CHAT_TEMPLATE",
"PROMPT_TEMPLATE",
"RAG_TEMPLATE",
"CUSTOM",
name="app_enumtype",
)
app_enumtype.create(op.get_bind(), checkfirst=True)

# Then add the column using the enum type
op.add_column(
"app_db",
sa.Column(
"app_type",
app_enumtype,
nullable=True,
),
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###

# Drop the column first
op.drop_column("app_db", "app_type")

# Then drop the enum type
app_enumtype = sa.Enum(
"CHAT_TEMPLATE",
"PROMPT_TEMPLATE",
"RAG_TEMPLATE",
"CUSTOM",
name="app_enumtype",
)
app_enumtype.drop(op.get_bind(), checkfirst=True)
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Merge conflicting heads

Revision ID: 78cde3fc549c
Revises: 1abfef8ed0ef, 5c29a64204f4
Create Date: 2024-08-29 22:01:35.030820

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "78cde3fc549c"
down_revision: Union[str, None] = ("1abfef8ed0ef", "5c29a64204f4")
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
pass


def downgrade() -> None:
pass
1 change: 1 addition & 0 deletions agenta-backend/agenta_backend/models/api/api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class URI(BaseModel):
class App(BaseModel):
app_id: str
app_name: str
app_type: Optional[str] = None
updated_at: str


Expand Down
4 changes: 2 additions & 2 deletions agenta-backend/agenta_backend/models/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
"""

import uuid
import json
import logging
from typing import List, Tuple, Any

from agenta_backend.services import db_manager
from agenta_backend.utils.common import isCloudEE
from agenta_backend.models.api.user_models import User
from agenta_backend.models.shared_models import ConfigDB
from agenta_backend.models.shared_models import ConfigDB, AppType
from agenta_backend.models.api.evaluation_model import (
CorrectAnswer,
Evaluation,
Expand Down Expand Up @@ -451,6 +450,7 @@ def app_db_to_pydantic(app_db: AppDB) -> App:
return App(
app_name=app_db.app_name,
app_id=str(app_db.id),
app_type=AppType.friendly_tag(app_db.app_type),
updated_at=str(app_db.updated_at),
)

Expand Down
3 changes: 2 additions & 1 deletion agenta-backend/agenta_backend/models/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from sqlalchemy.dialects.postgresql import UUID, JSONB

from agenta_backend.models.base import Base
from agenta_backend.models.shared_models import TemplateType
from agenta_backend.models.shared_models import TemplateType, AppType


class UserDB(Base):
Expand Down Expand Up @@ -76,6 +76,7 @@ class AppDB(Base):
nullable=False,
)
app_name = Column(String)
app_type = Column(Enum(AppType, name="app_enumtype"), nullable=True)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"))
modified_by_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
created_at = Column(
Expand Down
17 changes: 17 additions & 0 deletions agenta-backend/agenta_backend/models/shared_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,20 @@ class HumanEvaluationScenarioOutput(BaseModel):
class TemplateType(enum.Enum):
IMAGE = "image"
ZIP = "zip"


class AppType(str, enum.Enum):
CHAT_TEMPLATE = "TEMPLATE:simple_chat"
PROMPT_TEMPLATE = "TEMPLATE:single_prompt"
RAG_TEMPLATE = "TEMPLATE:RAG"
CUSTOM = "CUSTOM"

@classmethod
def friendly_tag(cls, app_type: str):
mappings = {
cls.CHAT_TEMPLATE: "chat",
cls.PROMPT_TEMPLATE: "completion",
cls.RAG_TEMPLATE: "rag",
cls.CUSTOM: "custom",
}
return mappings.get(app_type, None)
24 changes: 13 additions & 11 deletions agenta-backend/agenta_backend/routers/app_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ async def create_app(
request.state.user_id,
organization_id if isCloudEE() else None,
str(workspace.id) if isCloudEE() else None,
template_id=None,
)
return CreateAppOutput(app_id=str(app_db.id), app_name=str(app_db.app_name))
except Exception as e:
Expand Down Expand Up @@ -550,32 +551,33 @@ async def create_app_and_variant_from_template(
)

logger.debug(
"Step 5: Creating new app and initializing environments"
"Step 5: Retrieve template from db"
if isCloudEE()
else "Step 2: Creating new app and initializing environments"
else "Step 2: Retrieve template from db"
)
template_db = await db_manager.get_template(payload.template_id)

logger.debug(
"Step 6: Creating new app and initializing environments"
if isCloudEE()
else "Step 3: Creating new app and initializing environments"
)
if app is None:
app = await db_manager.create_app_and_envs(
app_name,
request.state.user_id,
payload.organization_id if isCloudEE() else None, # type: ignore
payload.workspace_id if isCloudEE() else None, # type: ignore
str(template_db.id),
)

logger.debug(
"Step 6: Retrieve template from db"
if isCloudEE()
else "Step 3: Retrieve template from db"
)
template_db = await db_manager.get_template(payload.template_id)
repo_name = os.environ.get("AGENTA_TEMPLATE_REPO", "agentaai/templates_v2")
image_name = f"{repo_name}:{template_db.name}"

logger.debug(
"Step 7: Creating image instance and adding variant based on image"
if isCloudEE()
else "Step 4: Creating image instance and adding variant based on image"
)
repo_name = os.environ.get("AGENTA_TEMPLATE_REPO", "agentaai/templates_v2")
image_name = f"{repo_name}:{template_db.name}"
app_variant_db = await app_manager.add_variant_based_on_image(
app=app,
variant_name="app.default",
Expand Down
30 changes: 29 additions & 1 deletion agenta-backend/agenta_backend/services/db_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import uuid
import logging
from enum import Enum
from pathlib import Path
from urllib.parse import urlparse
from typing import Any, Dict, List, Optional
Expand Down Expand Up @@ -73,6 +74,7 @@

from agenta_backend.models.shared_models import (
Result,
AppType,
ConfigDB,
TemplateType,
CorrectAnswer,
Expand Down Expand Up @@ -617,11 +619,35 @@ async def create_deployment(
raise Exception(f"Error while creating deployment: {e}")


async def get_app_type_from_template_by_id(template_id: Optional[str]) -> str:
"""Get the application type from the specified template.

Args:
template_id (Optional[str]): The ID of the template

Returns:
AppType (str): The determined application type. Defaults to AppType.CUSTOM.
"""

if template_id is None:
return AppType.CUSTOM

template_db = await get_template(template_id=template_id)
aybruhm marked this conversation as resolved.
Show resolved Hide resolved
if "Single Prompt" in template_db.title:
return AppType.PROMPT_TEMPLATE
elif "Chat Application" in template_db.title:
return AppType.CHAT_TEMPLATE
elif "RAG" in template_db.title:
return AppType.RAG_TEMPLATE
return AppType.CUSTOM


async def create_app_and_envs(
app_name: str,
user_uid: str,
organization_id: Optional[str] = None,
workspace_id: Optional[str] = None,
template_id: Optional[str] = None,
) -> AppDB:
"""
Create a new app with the given name and organization ID.
Expand All @@ -631,6 +657,7 @@ async def create_app_and_envs(
user_uid (str): The UID of the user that the app belongs to.
organization_id (str): The ID of the organization that the app belongs to.
workspace_id (str): The ID of the workspace that the app belongs to.
template_id (str): The ID of the template

Returns:
AppDB: The created app.
Expand All @@ -649,8 +676,9 @@ async def create_app_and_envs(
if app is not None:
raise ValueError("App with the same name already exists")

app_type = await get_app_type_from_template_by_id(template_id)
async with db_engine.get_session() as session:
app = AppDB(app_name=app_name, user_id=user.id)
app = AppDB(app_name=app_name, user_id=user.id, app_type=app_type)

if isCloudEE():
# assert that if organization_id is provided, workspace_id is also provided, and vice versa
Expand Down
2 changes: 1 addition & 1 deletion agenta-web/src/components/AppSelector/AppCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const AppCard: React.FC<{
<div data-cy="app-card-link" className={classes.app_card_link}>
<div>
<Text>Type</Text>
<Tag className="mr-0">Template</Tag>
<Tag className="mr-0">{app.app_type}</Tag>
</div>
<div>
<Text>Last modified:</Text>
Expand Down
Loading
Loading