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
34 changes: 33 additions & 1 deletion sma-av-streamlit/core/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

from datetime import datetime
from sqlalchemy import (
Column, Integer, String, Text, Boolean, DateTime, ForeignKey, Index
Column,
Integer,
String,
Text,
Boolean,
DateTime,
ForeignKey,
Index,
JSON,
)
from sqlalchemy.orm import declarative_base, relationship

Expand All @@ -13,6 +21,7 @@ class Agent(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255), nullable=False, unique=True, index=True)
domain = Column(String(255), nullable=True)
config_json = Column(JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)

def __repr__(self) -> str:
Expand Down Expand Up @@ -40,12 +49,34 @@ class Run(Base):

agent = relationship("Agent", lazy="joined")
recipe = relationship("Recipe", lazy="joined")
evidence = relationship(
"Evidence",
back_populates="run",
cascade="all, delete-orphan",
passive_deletes=True,
order_by="Evidence.id",
)

def __repr__(self) -> str:
return f"<Run id={self.id} agent_id={self.agent_id} recipe_id={self.recipe_id} status={self.status!r}>"

Index("ix_runs_status_created", Run.status, Run.created_at.desc())


class Evidence(Base):
__tablename__ = "evidence"
id = Column(Integer, primary_key=True, autoincrement=True)
run_id = Column(Integer, ForeignKey("runs.id", ondelete="CASCADE"), nullable=False, index=True)
kind = Column(String(64), nullable=False, default="json")
label = Column(String(255), nullable=True)
payload = Column(JSON, nullable=True)
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)

run = relationship("Run", back_populates="evidence")

def __repr__(self) -> str:
return f"<Evidence id={self.id} run_id={self.run_id} kind={self.kind!r}>"

class Tool(Base):
__tablename__ = "tools"
id = Column(Integer, primary_key=True, autoincrement=True)
Expand Down Expand Up @@ -95,4 +126,5 @@ def __repr__(self) -> str:
"Tool",
"ChatThread",
"ChatMessage",
"Evidence",
]
13 changes: 11 additions & 2 deletions sma-av-streamlit/core/recipes/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

import yaml

RECIPES_DIR = Path("recipes")
PACKAGE_ROOT = Path(__file__).resolve().parents[2]
DEFAULT_RECIPES_DIR = PACKAGE_ROOT / "recipes"
RECIPES_DIR = DEFAULT_RECIPES_DIR

__all__ = [
"ensure_recipes_dir",
Comment on lines +10 to 15

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Writing recipes into package directory causes permission errors

The new path resolution sets RECIPES_DIR to PACKAGE_ROOT / "recipes" and save_recipe_yaml now writes directly into that location. When the project is installed as a package, PACKAGE_ROOT resolves to the site‑packages directory, which is typically read‑only for the running user. Calls like save_recipe_yaml(fname, text) will attempt to create directories and files inside the installed package and will raise PermissionError, and other parts of the app (e.g. pages/4_Recipes.py) still look for user recipes under the working directory so the saved file would be invisible even if the write succeeded. Previously the default Path("recipes") pointed to a writable, working-directory folder. Consider keeping a writable user directory for output and only adding the packaged directory as a read-only search path.

Useful? React with 👍 / 👎.

Expand Down Expand Up @@ -60,6 +62,10 @@ def load_recipe_dict(source: Union[dict, str, Path, Any]) -> dict:
p = Path(str(p_val))
if p.exists():
yaml_text = _read_text_from_path(p)
elif not p.is_absolute():
candidate = DEFAULT_RECIPES_DIR / p
if candidate.exists():
yaml_text = _read_text_from_path(candidate)
if yaml_text is None:
y = getattr(source, "yaml", None)
if y:
Expand All @@ -70,7 +76,10 @@ def load_recipe_dict(source: Union[dict, str, Path, Any]) -> dict:
p = Path(str(source))
candidate_paths = [p]
if not p.is_absolute():
candidate_paths.append(RECIPES_DIR / p)
candidate_paths.extend([
RECIPES_DIR / p,
DEFAULT_RECIPES_DIR / p,
])
for candidate in candidate_paths:
if candidate.exists():
yaml_text = _read_text_from_path(candidate)
Expand Down
7 changes: 6 additions & 1 deletion sma-av-streamlit/core/workflow/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def execute_recipe_run(db: Session, agent_id: int, recipe_id: int) -> Run:
db.add(run); db.commit(); db.refresh(run)
recipe_dict = load_recipe_dict(recipe.yaml_path)
for phase, message in run_workflow_phases(recipe_dict):
attach_json(db, run_id=run.id, payload={"phase": phase, "message": f"{agent.name}: {message}"})
attach_json(
db,
run_id=run.id,
obj={"phase": phase, "message": f"{agent.name}: {message}"},
kind="phase",
)
run.status = "completed"; db.commit(); db.refresh(run)
return run