Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
06ea999
fix and add tests
jp-agenta Feb 16, 2026
0f22475
fix tests and structure
jp-agenta Feb 17, 2026
6116432
always resolve
jp-agenta Feb 17, 2026
e57a2fc
Merge branch 'main' into feat/add-embeds
jp-agenta Feb 18, 2026
8769e55
Merge branch 'main' into feat/add-embeds
jp-agenta Feb 24, 2026
082706a
Merge branch 'main' into feat/add-embeds
jp-agenta Feb 25, 2026
0c8db3e
ruff
jp-agenta Feb 25, 2026
c24e59d
Fix copilot issues
jp-agenta Feb 27, 2026
3d7aa41
Merge branch 'main' into feat/add-embeds
junaway Feb 27, 2026
ee1fb0a
Fix copilot/devin CR
jp-agenta Feb 27, 2026
00eeaef
Merge branch 'main' into feat/add-embeds
junaway Feb 27, 2026
740498b
Merge branch 'release/v0.87.3' into feat/add-embeds
junaway Mar 2, 2026
4bff01d
ruff
jp-agenta Mar 2, 2026
11d276c
cleanup
jp-agenta Mar 3, 2026
95a1005
Fixes
jp-agenta Mar 3, 2026
9939144
remove resolve from query
jp-agenta Mar 3, 2026
2f6a88d
Merge branch 'release/v0.89.0' into feat/add-embeds
jp-agenta Mar 3, 2026
128b2b1
Fix mixed entities, fix key/path
jp-agenta Mar 3, 2026
1e0bffb
Add snippets
jp-agenta Mar 4, 2026
2eeb4cf
Fix Snippet Embeds
jp-agenta Mar 4, 2026
e9655f7
fix FE
jp-agenta Mar 5, 2026
8f8b8e2
ruff changes
jp-agenta Mar 5, 2026
07860de
initial poc
jp-agenta Mar 5, 2026
f939afc
working FE
jp-agenta Mar 5, 2026
b90f0d8
simplify
jp-agenta Mar 5, 2026
d39d530
remove logs
jp-agenta Mar 5, 2026
fbefdac
Merge pull request #3922 from Agenta-AI/feat/add-embeds-switch-poc
jp-agenta Mar 5, 2026
9d9dcb7
FE cleanup
jp-agenta Mar 5, 2026
d93db39
cleanup BE logs
jp-agenta Mar 5, 2026
03ef272
chore(frontend): keep query slash fix only on add-embeds
jp-agenta Mar 5, 2026
c0256b6
fix extra resolve in FE
jp-agenta Mar 5, 2026
ad641af
Merge branch 'release/v0.91.0' into feat/add-embeds
jp-agenta Mar 6, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ api/tests/results/
api/oss/tests/results/
api/ee/tests/results/
sdk/tests/results/
tests/results/
sdk/oss/tests/results/
sdk/ee/tests/results/
services/tests/results/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)
from oss.src.dbs.postgres.folders.dbes import FolderDBE # noqa: F401 — registers 'folders' table in SQLAlchemy metadata
from oss.src.dbs.postgres.git.dao import GitDAO
from oss.src.core.applications.services import ApplicationsService
from oss.src.core.applications.service import ApplicationsService
from oss.src.core.applications.dtos import (
Application,
ApplicationCreate,
Expand Down
39 changes: 34 additions & 5 deletions api/entrypoints/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

from oss.src.services.auth_service import authentication_middleware
from oss.src.services.analytics_service import analytics_middleware
from oss.src.services.legacy_adapter import (
configure_legacy_adapter,
configure_legacy_environments_adapter,
)

from oss.src.core.auth.supertokens.config import init_supertokens

Expand Down Expand Up @@ -72,8 +76,8 @@
from oss.src.core.testsets.service import SimpleTestsetsService
from oss.src.core.queries.service import QueriesService
from oss.src.core.queries.service import SimpleQueriesService
from oss.src.core.applications.services import ApplicationsService
from oss.src.core.applications.services import SimpleApplicationsService
from oss.src.core.applications.service import ApplicationsService
from oss.src.core.applications.service import SimpleApplicationsService
from oss.src.core.folders.service import FoldersService
from oss.src.core.workflows.service import WorkflowsService
from oss.src.core.evaluators.service import EvaluatorsService
Expand All @@ -82,6 +86,7 @@
from oss.src.core.environments.service import SimpleEnvironmentsService
from oss.src.core.evaluations.service import EvaluationsService
from oss.src.core.evaluations.service import SimpleEvaluationsService
from oss.src.core.embeds.service import EmbedsService
from oss.src.core.evaluations.service import SimpleQueuesService

# Routers
Expand Down Expand Up @@ -311,6 +316,10 @@ async def lifespan(*args, **kwargs):
workflows_dao=workflows_dao,
)

environments_service = EnvironmentsService(
environments_dao=environments_dao,
)

applications_service = ApplicationsService(
workflows_service=workflows_service,
)
Expand All @@ -323,18 +332,38 @@ async def lifespan(*args, **kwargs):
workflows_service=workflows_service,
)

simple_evaluators_service = SimpleEvaluatorsService(
embeds_service = EmbedsService(
workflows_service=workflows_service,
environments_service=environments_service,
applications_service=applications_service,
evaluators_service=evaluators_service,
)

environments_service = EnvironmentsService(
environments_dao=environments_dao,
# Inject embeds_service into all services that need it
workflows_service.embeds_service = embeds_service
environments_service.embeds_service = embeds_service
applications_service.embeds_service = embeds_service
evaluators_service.embeds_service = embeds_service

simple_evaluators_service = SimpleEvaluatorsService(
evaluators_service=evaluators_service,
)

simple_environments_service = SimpleEnvironmentsService(
environments_service=environments_service,
)

# Ensure legacy adapters reuse the same pre-wired services (including embeds_service).
configure_legacy_adapter(
applications_service=applications_service,
simple_applications_service=simple_applications_service,
)
configure_legacy_environments_adapter(
environments_service=environments_service,
simple_environments_service=simple_environments_service,
applications_service=applications_service,
)

evaluations_service = EvaluationsService(
evaluations_dao=evaluations_dao,
tracing_service=tracing_service,
Expand Down
2 changes: 1 addition & 1 deletion api/entrypoints/worker_evaluations.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from oss.src.core.queries.service import QueriesService
from oss.src.core.testcases.service import TestcasesService
from oss.src.core.testsets.service import TestsetsService, SimpleTestsetsService
from oss.src.core.applications.services import ApplicationsService
from oss.src.core.applications.service import ApplicationsService
from oss.src.core.workflows.service import WorkflowsService
from oss.src.core.evaluators.service import EvaluatorsService, SimpleEvaluatorsService
from oss.src.core.evaluations.service import EvaluationsService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
)
from oss.src.dbs.postgres.folders.dbes import FolderDBE # noqa: F401 — registers 'folders' table in SQLAlchemy metadata
from oss.src.dbs.postgres.git.dao import GitDAO
from oss.src.core.applications.services import ApplicationsService
from oss.src.core.applications.service import ApplicationsService
from oss.src.core.applications.dtos import (
Application,
ApplicationCreate,
Expand Down
26 changes: 26 additions & 0 deletions api/oss/src/apis/fastapi/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
SimpleApplicationEdit,
SimpleApplicationQuery,
)
from oss.src.core.embeds.dtos import (
ErrorPolicy,
ResolutionInfo,
)

# APPLICATIONS -----------------------------------------------------------------

Expand Down Expand Up @@ -123,6 +127,7 @@ class ApplicationRevisionQueryRequest(BaseModel):
include_archived: Optional[bool] = None
#
windowing: Optional[Windowing] = None
resolve: bool = False # Optionally resolve embeds on query


class ApplicationRevisionCommitRequest(BaseModel):
Expand All @@ -133,11 +138,13 @@ class ApplicationRevisionRetrieveRequest(BaseModel):
application_ref: Optional[Reference] = None
application_variant_ref: Optional[Reference] = None
application_revision_ref: Optional[Reference] = None
resolve: bool = False # Optionally resolve embeds on retrieve


class ApplicationRevisionResponse(BaseModel):
count: int = 0
application_revision: Optional[ApplicationRevision] = None
resolution_info: Optional[ResolutionInfo] = None # Included when resolve=True


class ApplicationRevisionsResponse(BaseModel):
Expand Down Expand Up @@ -174,3 +181,22 @@ class SimpleApplicationResponse(BaseModel):
class SimpleApplicationsResponse(BaseModel):
count: int = 0
applications: List[SimpleApplication] = []


# APPLICATION REVISION RESOLUTION ----------------------------------------------


class ApplicationRevisionResolveRequest(BaseModel):
application_ref: Optional[Reference] = None
application_variant_ref: Optional[Reference] = None
application_revision_ref: Optional[Reference] = None
#
max_depth: Optional[int] = 10
max_embeds: Optional[int] = 100
error_policy: Optional[ErrorPolicy] = ErrorPolicy.EXCEPTION


class ApplicationRevisionResolveResponse(BaseModel):
count: int = 0
application_revision: Optional[ApplicationRevision] = None
resolution_info: Optional[ResolutionInfo] = None
127 changes: 126 additions & 1 deletion api/oss/src/apis/fastapi/applications/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from oss.src.core.shared.dtos import (
Reference,
)
from oss.src.core.applications.services import (
from oss.src.core.applications.service import (
ApplicationsService,
SimpleApplicationsService,
)
from oss.src.core.applications.dtos import ApplicationRevisionData

from oss.src.apis.fastapi.applications.models import (
ApplicationCreateRequest,
Expand All @@ -38,6 +39,8 @@
ApplicationRevisionRetrieveRequest,
ApplicationRevisionResponse,
ApplicationRevisionsResponse,
ApplicationRevisionResolveRequest,
ApplicationRevisionResolveResponse,
#
SimpleApplicationCreateRequest,
SimpleApplicationEditRequest,
Expand Down Expand Up @@ -307,6 +310,16 @@ def __init__(
response_model_exclude_none=True,
)

self.router.add_api_route(
"/revisions/resolve",
self.resolve_application_revision,
methods=["POST"],
operation_id="resolve_application_revision",
status_code=status.HTTP_200_OK,
response_model=ApplicationRevisionResolveResponse,
response_model_exclude_none=True,
)

# APPLICATIONS -------------------------------------------------------------

@intercept_exceptions()
Expand Down Expand Up @@ -789,6 +802,16 @@ async def retrieve_application_revision(
):
raise FORBIDDEN_EXCEPTION # type: ignore

log.info(
"[applications.revisions.retrieve] request project_id=%s user_id=%s resolve=%s app_ref=%s variant_ref=%s revision_ref=%s",
request.state.project_id,
request.state.user_id,
application_revision_retrieve_request.resolve,
application_revision_retrieve_request.application_ref,
application_revision_retrieve_request.application_variant_ref,
application_revision_retrieve_request.application_revision_ref,
)

cache_key = {
"artifact_ref": application_revision_retrieve_request.application_ref, # type: ignore
"variant_ref": application_revision_retrieve_request.application_variant_ref, # type: ignore
Expand All @@ -813,6 +836,13 @@ async def retrieve_application_revision(
application_revision_ref=application_revision_retrieve_request.application_revision_ref, # type: ignore
)

log.info(
"[applications.revisions.retrieve] fetched project_id=%s revision_id=%s has_data=%s",
request.state.project_id,
getattr(application_revision, "id", None),
bool(application_revision and application_revision.data),
)

await set_cache(
namespace="applications:retrieve",
project_id=request.state.project_id,
Expand All @@ -821,9 +851,46 @@ async def retrieve_application_revision(
value=application_revision,
)

# Optionally resolve embeds if requested
resolution_info = None
if application_revision and application_revision_retrieve_request.resolve:
log.info(
"[applications.revisions.retrieve] resolve-start project_id=%s revision_id=%s",
request.state.project_id,
getattr(application_revision, "id", None),
)
embeds_service = self.applications_service.embeds_service
(
resolved_config,
resolution_info,
) = await embeds_service.resolve_configuration(
project_id=UUID(request.state.project_id),
configuration=application_revision.data.model_dump()
if application_revision.data
else {},
)

if application_revision.data:
application_revision.data = ApplicationRevisionData(**resolved_config)

log.info(
"[applications.revisions.retrieve] resolve-done project_id=%s revision_id=%s has_resolution_info=%s",
request.state.project_id,
getattr(application_revision, "id", None),
resolution_info is not None,
)

application_revision_response = ApplicationRevisionResponse(
count=1 if application_revision else 0,
application_revision=application_revision,
resolution_info=resolution_info,
)

log.info(
"[applications.revisions.retrieve] response project_id=%s count=%s revision_id=%s",
request.state.project_id,
application_revision_response.count,
getattr(application_revision, "id", None),
)

return application_revision_response
Expand Down Expand Up @@ -1009,6 +1076,23 @@ async def query_application_revisions(
windowing=application_revision_query_request.windowing,
)

# Optionally resolve embeds for all revisions if requested
if application_revisions and application_revision_query_request.resolve:
embeds_service = self.applications_service.embeds_service

for revision in application_revisions:
if revision and revision.data:
try:
resolved_config, _ = await embeds_service.resolve_configuration(
project_id=UUID(request.state.project_id),
configuration=revision.data.model_dump(),
)
revision.data = ApplicationRevisionData(**resolved_config)
except Exception as e:
log.error(
f"Failed to resolve embeds for revision {revision.id}: {e}"
)

return ApplicationRevisionsResponse(
count=len(application_revisions),
application_revisions=application_revisions,
Expand Down Expand Up @@ -1071,6 +1155,47 @@ async def log_application_revisions(

return revisions_response

@intercept_exceptions()
async def resolve_application_revision(
self,
request: Request,
*,
application_revision_resolve_request: ApplicationRevisionResolveRequest,
) -> ApplicationRevisionResolveResponse:
if is_ee():
if not await check_action_access( # type: ignore
user_uid=request.state.user_id,
project_id=request.state.project_id,
permission=Permission.VIEW_APPLICATIONS, # type: ignore
):
raise FORBIDDEN_EXCEPTION # type: ignore

result = await self.applications_service.resolve_application_revision(
project_id=UUID(request.state.project_id),
user_id=UUID(request.state.user_id),
#
application_ref=application_revision_resolve_request.application_ref,
application_variant_ref=application_revision_resolve_request.application_variant_ref,
application_revision_ref=application_revision_resolve_request.application_revision_ref,
#
max_depth=application_revision_resolve_request.max_depth or 10,
max_embeds=application_revision_resolve_request.max_embeds or 100,
error_policy=application_revision_resolve_request.error_policy.value
if application_revision_resolve_request.error_policy
else "exception",
)

if not result:
return ApplicationRevisionResolveResponse()

application_revision, resolution_info = result

return ApplicationRevisionResolveResponse(
count=1,
application_revision=application_revision,
resolution_info=resolution_info,
)


class SimpleApplicationsRouter:
def __init__(
Expand Down
Loading