diff --git a/pyproject.toml b/pyproject.toml index 9ee8911..9e0ad30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -161,3 +161,8 @@ torchaudio = [ name = "pytorch-cpu" url = "https://download.pytorch.org/whl/cpu" explicit = true + +[dependency-groups] +dev = [ + "pytest>=9.1.1", +] diff --git a/src/vidxp/application_models.py b/src/vidxp/application_models.py index 395bde4..8a64ef4 100644 --- a/src/vidxp/application_models.py +++ b/src/vidxp/application_models.py @@ -1044,18 +1044,48 @@ def _unique_modalities( return values +class OccurrenceMode(StrEnum): + first = "first" + best = "best" + all = "all" + + class SearchMomentsPlanStep(ApplicationModel): kind: Literal["search_moments"] = "search_moments" modality: Identifier query: SearchQuery + occurrence_mode: OccurrenceMode = OccurrenceMode.best class ActorOverviewPlanStep(ApplicationModel): kind: Literal["actor_overview"] = "actor_overview" +class TemporalRelation(StrEnum): + before = "before" + after = "after" + during = "during" + + +class TemporalRelationPlanStep(ApplicationModel): + kind: Literal["temporal_relation"] = "temporal_relation" + relation: TemporalRelation + reference_query: SearchQuery + target_modality: Identifier + target_query: SearchQuery + + +class EvidenceRequestPlanStep(ApplicationModel): + kind: Literal["evidence_request"] = "evidence_request" + delivery_mode: EvidenceDeliveryMode + include_board: bool = False + + QueryPlanStep = Annotated[ - SearchMomentsPlanStep | ActorOverviewPlanStep, + SearchMomentsPlanStep + | ActorOverviewPlanStep + | TemporalRelationPlanStep + | EvidenceRequestPlanStep, Field(discriminator="kind"), ] @@ -1068,6 +1098,8 @@ class QueryPlanningRequest(ApplicationModel): question: SearchQuery allowed_modalities: tuple[Identifier, ...] actor_overview_allowed: bool = False + temporal_relations_allowed: bool = False + evidence_requests_allowed: bool = False class QueryModelIdentity(ApplicationModel): diff --git a/src/vidxp/query_service.py b/src/vidxp/query_service.py index c8d0aaf..09a647d 100644 --- a/src/vidxp/query_service.py +++ b/src/vidxp/query_service.py @@ -5,8 +5,10 @@ from vidxp.application_models import ( ActorEvidence, + ActorOverviewPlanStep, DraftAnswer, Evidence, + EvidenceRequestPlanStep, FusedSearchResult, GroundedClaim, IndexSnapshotReference, @@ -18,7 +20,7 @@ QuerySynthesisRequest, QueryVideoCommand, SearchMomentsPlanStep, - ActorOverviewPlanStep, + TemporalRelationPlanStep, ) from vidxp.capabilities.actor.schemas import ActorClusterSummary from vidxp.ports import QueryModelPort, QueryProviderError @@ -39,6 +41,8 @@ def _default_plan( *, search_modalities: tuple[str, ...], actor_overview: bool, + temporal_relations: bool = False, + evidence_requests: bool = False, ) -> QueryPlan: steps = [ SearchMomentsPlanStep( @@ -57,6 +61,8 @@ def _valid_plan( *, search_modalities: tuple[str, ...], actor_overview: bool, + temporal_relations: bool = False, + evidence_requests: bool = False, ) -> bool: searches = [ step.modality @@ -66,6 +72,16 @@ def _valid_plan( actor_steps = sum( isinstance(step, ActorOverviewPlanStep) for step in plan.steps ) + temporal_steps = sum( + isinstance(step, TemporalRelationPlanStep) for step in plan.steps + ) + evidence_steps = sum( + isinstance(step, EvidenceRequestPlanStep) for step in plan.steps + ) + if not temporal_relations and temporal_steps > 0: + return False + if not evidence_requests and evidence_steps > 0: + return False return ( len(searches) == len(set(searches)) and set(searches) == set(search_modalities) @@ -85,11 +101,15 @@ def plan( *, search_modalities: tuple[str, ...], actor_overview: bool, + temporal_relations: bool = False, + evidence_requests: bool = False, ) -> tuple[QueryPlan, str | None]: fallback = _default_plan( command, search_modalities=search_modalities, actor_overview=actor_overview, + temporal_relations=temporal_relations, + evidence_requests=evidence_requests, ) if self.model is None: return fallback, "query_model_not_configured" @@ -99,6 +119,8 @@ def plan( question=command.question, allowed_modalities=search_modalities, actor_overview_allowed=actor_overview, + temporal_relations_allowed=temporal_relations, + evidence_requests_allowed=evidence_requests, ) ) except QueryProviderError: @@ -107,6 +129,8 @@ def plan( proposed, search_modalities=search_modalities, actor_overview=actor_overview, + temporal_relations=temporal_relations, + evidence_requests=evidence_requests, ): return fallback, "query_plan_rejected" return proposed, None diff --git a/uv.lock b/uv.lock index 159418d..0ce9e52 100644 --- a/uv.lock +++ b/uv.lock @@ -3773,7 +3773,7 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'linux' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -3852,7 +3852,7 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform != 'linux' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } wheels = [ @@ -4233,13 +4233,13 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'linux' and sys_platform != 'win32'", ] dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx" }, - { name = "setuptools" }, - { name = "sympy" }, - { name = "typing-extensions" }, + { name = "filelock", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "fsspec", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "jinja2", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "networkx", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "setuptools", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "sympy", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "typing-extensions", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, @@ -4260,13 +4260,13 @@ resolution-markers = [ "(python_full_version < '3.12' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform == 'win32')", ] dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "jinja2" }, - { name = "networkx" }, - { name = "setuptools" }, - { name = "sympy" }, - { name = "typing-extensions" }, + { name = "filelock", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "fsspec", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "jinja2", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "networkx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "setuptools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "sympy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] wheels = [ { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-linux_s390x.whl", hash = "sha256:6e9817dbdf5ea76789babd46e457eac5bf14ff566cf85f8addbfdff2d56601ce", upload-time = "2026-07-08T19:27:52Z" }, @@ -4355,10 +4355,10 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'linux' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pillow" }, - { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' and sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "pillow", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/7a/b2/1e010052079e4c577007b789db336ea7075f1a426e84d17121fbc3745516/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", size = 1856017, upload-time = "2026-07-08T16:07:55.533Z" }, @@ -4379,10 +4379,10 @@ resolution-markers = [ "(python_full_version < '3.12' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform == 'win32')", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pillow" }, - { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" } }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform == 'win32')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform == 'win32')" }, + { name = "pillow", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] wheels = [ { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:22958193d72444ed7cbcc665ba4821a31e5279f9c4d1ad08520918b30896b78a", upload-time = "2026-07-08T12:26:39Z" }, @@ -4739,6 +4739,11 @@ test = [ { name = "pytest" }, ] +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + [package.metadata] requires-dist = [ { name = "alembic", marker = "extra == 'server'", specifier = ">=1.18.5,<2" }, @@ -4878,6 +4883,9 @@ requires-dist = [ ] provides-extras = ["storage", "speech", "scene", "actor", "action", "sound", "all", "local-worker", "mcp", "slm", "server", "server-worker", "test", "frontend", "benchmarks"] +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=9.1.1" }] + [[package]] name = "watchdog" version = "6.0.0"