mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
feat(automations): implement model eligibility checks for automation creation
- Added model eligibility checks to ensure automations can only use billable models (premium or BYOK). - Introduced new API endpoint to report model eligibility status for search spaces. - Updated frontend components to display eligibility alerts and disable creation options when models are not billable. - Enhanced automation creation forms to reflect model eligibility, preventing users from submitting invalid configurations. - Implemented server-side logic to capture and preserve model preferences across automation edits, ensuring consistent behavior during execution.
This commit is contained in:
parent
5d90fbe99f
commit
409fec94c3
32 changed files with 1451 additions and 67 deletions
|
|
@ -57,6 +57,7 @@ async def build_agent_with_cache(
|
|||
mcp_tools_by_agent: dict[str, list[BaseTool]],
|
||||
disabled_tools: list[str] | None,
|
||||
config_id: str | None,
|
||||
image_generation_config_id_override: int | None = None,
|
||||
) -> Any:
|
||||
"""Compile the multi-agent graph, serving from cache when key components are stable."""
|
||||
|
||||
|
|
@ -91,7 +92,7 @@ async def build_agent_with_cache(
|
|||
# the key, otherwise a hit will leak state across threads. Bump the schema
|
||||
# version when the component list changes shape.
|
||||
cache_key = stable_hash(
|
||||
"multi-agent-v1",
|
||||
"multi-agent-v2",
|
||||
config_id,
|
||||
thread_id,
|
||||
user_id,
|
||||
|
|
@ -109,6 +110,10 @@ async def build_agent_with_cache(
|
|||
system_prompt_hash(final_system_prompt),
|
||||
max_input_tokens,
|
||||
sorted(disabled_tools) if disabled_tools else None,
|
||||
# Bound into the generate_image subagent tool at construction time, so it
|
||||
# must key the compiled-agent cache to avoid leaking one automation's
|
||||
# image model into another with the same config_id/search_space.
|
||||
image_generation_config_id_override,
|
||||
)
|
||||
return await get_cache().get_or_build(cache_key, builder=_build)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,8 +62,14 @@ async def create_multi_agent_chat_deep_agent(
|
|||
mentioned_document_ids: list[int] | None = None,
|
||||
anon_session_id: str | None = None,
|
||||
filesystem_selection: FilesystemSelection | None = None,
|
||||
image_generation_config_id: int | None = None,
|
||||
):
|
||||
"""Deep agent with SurfSense tools/middleware; registry route subagents behind ``task`` when enabled."""
|
||||
"""Deep agent with SurfSense tools/middleware; registry route subagents behind ``task`` when enabled.
|
||||
|
||||
``image_generation_config_id`` overrides the search space's image model for
|
||||
this invocation (used by automations to run on their captured model). When
|
||||
``None``, the ``generate_image`` tool resolves the live search-space pref.
|
||||
"""
|
||||
_t_agent_total = time.perf_counter()
|
||||
|
||||
apply_litellm_prompt_caching(llm, agent_config=agent_config, thread_id=thread_id)
|
||||
|
|
@ -129,6 +135,9 @@ async def create_multi_agent_chat_deep_agent(
|
|||
"available_document_types": available_document_types,
|
||||
"max_input_tokens": _max_input_tokens,
|
||||
"llm": llm,
|
||||
# Per-invocation image model override (automations run on their captured
|
||||
# model). Reaches the generate_image subagent tool via subagent_dependencies.
|
||||
"image_generation_config_id_override": image_generation_config_id,
|
||||
}
|
||||
|
||||
_t0 = time.perf_counter()
|
||||
|
|
@ -285,6 +294,7 @@ async def create_multi_agent_chat_deep_agent(
|
|||
mcp_tools_by_agent=mcp_tools_by_agent,
|
||||
disabled_tools=disabled_tools,
|
||||
config_id=config_id,
|
||||
image_generation_config_id_override=image_generation_config_id,
|
||||
)
|
||||
_perf_log.info(
|
||||
"[create_agent] Middleware stack + graph compiled in %.3fs",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated impo
|
|||
)
|
||||
from app.automations.schemas.api import AutomationCreate
|
||||
from app.automations.services.automation import AutomationService
|
||||
from app.db import User, async_session_maker
|
||||
from app.automations.services.model_policy import get_automation_model_eligibility
|
||||
from app.db import SearchSpace, User, async_session_maker
|
||||
from app.utils.content_utils import extract_text_content
|
||||
|
||||
from .prompt import build_draft_prompt
|
||||
|
|
@ -98,6 +99,27 @@ def create_create_automation_tool(
|
|||
declined. Acknowledge once and stop — do NOT retry or pitch
|
||||
variants without a fresh user request.
|
||||
"""
|
||||
# --- 0. Eligibility gate (fail fast, before drafting + HITL) ---
|
||||
# Automations may only use premium or BYOK models. Check up front so we
|
||||
# don't make the user draft + approve a card that can't be saved.
|
||||
async with async_session_maker() as session:
|
||||
search_space = await session.get(SearchSpace, search_space_id)
|
||||
if search_space is None:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "search space not found in this session",
|
||||
}
|
||||
eligibility = get_automation_model_eligibility(search_space)
|
||||
if not eligibility["allowed"]:
|
||||
reasons = " ".join(v["reason"] for v in eligibility["violations"])
|
||||
return {
|
||||
"status": "error",
|
||||
"message": (
|
||||
f"{reasons} Update the search space's model settings to a "
|
||||
"premium or your own (BYOK) model, then try again."
|
||||
),
|
||||
}
|
||||
|
||||
# --- 1. Draft via sub-LLM ---
|
||||
prompt = build_draft_prompt(search_space_id=search_space_id, intent=intent)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -63,8 +63,14 @@ def _get_global_image_gen_config(config_id: int) -> dict | None:
|
|||
def create_generate_image_tool(
|
||||
search_space_id: int,
|
||||
db_session: AsyncSession,
|
||||
image_generation_config_id_override: int | None = None,
|
||||
):
|
||||
"""Create ``generate_image`` with bound search space; DB work uses a per-call session."""
|
||||
"""Create ``generate_image`` with bound search space; DB work uses a per-call session.
|
||||
|
||||
``image_generation_config_id_override``: when set (automations running on a
|
||||
captured model), use this config id instead of reading the search space's
|
||||
live ``image_generation_config_id``.
|
||||
"""
|
||||
del db_session # use a fresh per-call session, see below
|
||||
|
||||
@tool
|
||||
|
|
@ -108,19 +114,27 @@ def create_generate_image_tool(
|
|||
# task's session is shared across every tool; without isolation,
|
||||
# autoflushes from a concurrent writer poison this tool too.
|
||||
async with shielded_async_session() as session:
|
||||
result = await session.execute(
|
||||
select(SearchSpace).filter(SearchSpace.id == search_space_id)
|
||||
)
|
||||
search_space = result.scalars().first()
|
||||
if not search_space:
|
||||
return _failed(
|
||||
{"error": "Search space not found"},
|
||||
error="Search space not found",
|
||||
if image_generation_config_id_override is not None:
|
||||
# Automation run: use the captured image model, insulated from
|
||||
# later search-space changes. No search-space read needed.
|
||||
config_id = (
|
||||
image_generation_config_id_override or IMAGE_GEN_AUTO_MODE_ID
|
||||
)
|
||||
else:
|
||||
result = await session.execute(
|
||||
select(SearchSpace).filter(SearchSpace.id == search_space_id)
|
||||
)
|
||||
search_space = result.scalars().first()
|
||||
if not search_space:
|
||||
return _failed(
|
||||
{"error": "Search space not found"},
|
||||
error="Search space not found",
|
||||
)
|
||||
|
||||
config_id = (
|
||||
search_space.image_generation_config_id or IMAGE_GEN_AUTO_MODE_ID
|
||||
)
|
||||
config_id = (
|
||||
search_space.image_generation_config_id
|
||||
or IMAGE_GEN_AUTO_MODE_ID
|
||||
)
|
||||
|
||||
# Build generation kwargs
|
||||
# NOTE: size, quality, and style are intentionally NOT passed.
|
||||
|
|
|
|||
|
|
@ -51,5 +51,8 @@ def load_tools(
|
|||
create_generate_image_tool(
|
||||
search_space_id=d["search_space_id"],
|
||||
db_session=d["db_session"],
|
||||
image_generation_config_id_override=d.get(
|
||||
"image_generation_config_id_override"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue