Skip to content

Commit 82cd5df

Browse files
paynejdclaudesnyaggarwal
authored
Add swagger documentation for $match endpoint (#824)
* Add swagger documentation for $match endpoint Add @swagger_auto_schema decorator to MetadataToConceptsListView.post() with full query parameter definitions, request body schema, and response codes. Add 8 new $match-specific parameter definitions to swagger_parameters.py. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fixing URL pattern * Reranker enabled default in Swagger * Swagger param | added example value for encoder model --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Sny <sny.aggarwal@gmail.com>
1 parent 55d8ce9 commit 82cd5df

4 files changed

Lines changed: 103 additions & 1 deletion

File tree

core/common/swagger.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Swagger helpers for OCL-specific OpenAPI generation."""
2+
3+
from drf_yasg.generators import EndpointEnumerator, OpenAPISchemaGenerator
4+
5+
6+
class OCLSwaggerEndpointEnumerator(EndpointEnumerator):
7+
"""Preserve literal dollar signs when deriving OpenAPI paths from Django URL regexes."""
8+
9+
dollar_sentinel = "__ocl_escaped_dollar__"
10+
11+
def get_path_from_regex(self, path_regex):
12+
"""Keep escaped dollar signs intact instead of letting schema simplification drop them."""
13+
sanitized_regex = path_regex.replace(r"\$", self.dollar_sentinel).replace("$", self.dollar_sentinel)
14+
return super().get_path_from_regex(sanitized_regex).replace(self.dollar_sentinel, "$")
15+
16+
17+
class OCLSwaggerSchemaGenerator(OpenAPISchemaGenerator):
18+
"""Use the OCL endpoint enumerator so Swagger paths match the published URLs."""
19+
20+
endpoint_enumerator_class = OCLSwaggerEndpointEnumerator

core/common/swagger_parameters.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,37 @@
202202
'start_date', openapi.IN_QUERY, type=openapi.TYPE_STRING, format='YYYY-MM-DD', required=False,
203203
description='filter by start date of tasks'
204204
)
205+
206+
# $match params
207+
match_semantic_param = openapi.Parameter(
208+
'semantic', openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN, default=False,
209+
description='Use semantic (LM-based) matching algorithm'
210+
)
211+
match_best_match_param = openapi.Parameter(
212+
'bestMatch', openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN, default=False,
213+
description='Apply minimum score threshold, filtering out low-quality matches'
214+
)
215+
match_num_candidates_param = openapi.Parameter(
216+
'numCandidates', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, default=3000,
217+
description='Number of approximate nearest neighbor candidates (semantic only). Max: 3000'
218+
)
219+
match_k_nearest_param = openapi.Parameter(
220+
'kNearest', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, default=100,
221+
description='Number of nearest neighbors to return from vector search (semantic only). Max: 100'
222+
)
223+
match_brief_param = openapi.Parameter(
224+
'brief', openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN, default=False,
225+
description='Return minimal concept fields in results'
226+
)
227+
match_encoder_model_param = openapi.Parameter(
228+
'encoder_model', openapi.IN_QUERY, type=openapi.TYPE_STRING,
229+
description='Custom encoder model name for semantic vector search (e.g. BAAI/bge-reranker-v2-m3)'
230+
)
231+
match_reranker_param = openapi.Parameter(
232+
'reranker', openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN, default=True,
233+
description='Enable cross-encoder reranking of results (semantic only)'
234+
)
235+
match_offset_param = openapi.Parameter(
236+
'offset', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, default=0,
237+
description='Number of results to skip per row'
238+
)

core/concepts/views.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.conf import settings
55
from django.db.models import F
66
from django.http import Http404
7+
from drf_yasg import openapi
78
from drf_yasg.utils import swagger_auto_schema
89
from pydash import get, compact
910
from rest_framework import status
@@ -28,7 +29,9 @@
2829
compress_header, include_source_versions_param, include_collection_versions_param, cascade_method_param,
2930
cascade_map_types_param, cascade_exclude_map_types_param, cascade_hierarchy_param, cascade_mappings_param,
3031
cascade_levels_param, cascade_direction_param, cascade_view_hierarchy, return_map_types_param,
31-
omit_if_exists_in_param, equivalency_map_types_param, search_from_latest_repo_header)
32+
omit_if_exists_in_param, equivalency_map_types_param, search_from_latest_repo_header,
33+
match_semantic_param, match_best_match_param, match_num_candidates_param, match_k_nearest_param,
34+
match_brief_param, match_encoder_model_param, match_reranker_param, match_offset_param)
3235
from core.common.tasks import delete_concept, make_hierarchy
3336
from core.common.throttling import ThrottleUtil
3437
from core.common.utils import (to_parent_uri_from_kwargs, generate_temp_version, get_truthy_values, to_int,
@@ -933,6 +936,49 @@ def get_repo_params(is_semantic, target_repo_params, target_repo_url):
933936
raise Http400(f'Unable to resolve "target_repo_url": "{target_repo_url}"')
934937
return repo_params
935938

939+
@swagger_auto_schema(
940+
operation_description='Find matching concepts across repositories using structured input data.',
941+
operation_summary='$match - Find matching concepts',
942+
manual_parameters=[
943+
verbose_param, include_retired_param, limit_param, page_param, match_offset_param,
944+
match_semantic_param, match_best_match_param, match_num_candidates_param,
945+
match_k_nearest_param, match_brief_param, match_encoder_model_param, match_reranker_param,
946+
],
947+
request_body=openapi.Schema(
948+
type=openapi.TYPE_OBJECT,
949+
required=['rows'],
950+
properties={
951+
'target_repo_url': openapi.Schema(
952+
type=openapi.TYPE_STRING,
953+
description='Repository URL to match against. Either target_repo_url or target_repo is required.'
954+
),
955+
'target_repo': openapi.Schema(
956+
type=openapi.TYPE_OBJECT,
957+
description='Alternative to target_repo_url. Object with owner, source, source_version, '
958+
'owner_type fields.'
959+
),
960+
'rows': openapi.Schema(
961+
type=openapi.TYPE_ARRAY,
962+
items=openapi.Schema(type=openapi.TYPE_OBJECT),
963+
description='List of concept-like key-value pairs to match.'
964+
),
965+
'map_config': openapi.Schema(
966+
type=openapi.TYPE_ARRAY,
967+
items=openapi.Schema(type=openapi.TYPE_OBJECT),
968+
description='Optional list configuring mapping logic per row.'
969+
),
970+
'filter': openapi.Schema(
971+
type=openapi.TYPE_OBJECT,
972+
description='Filtering criteria including locale and faceted filters.'
973+
),
974+
}
975+
),
976+
responses={
977+
200: 'List of matched results per input row',
978+
400: 'Missing required parameters (rows, target_repo_url/target_repo)',
979+
403: 'User not approved for $match or on waitlist',
980+
}
981+
)
936982
def post(self, request, **kwargs): # pylint: disable=unused-argument
937983
user = self.request.user
938984
if user.is_mapper_waitlisted or not user.is_mapper_approved:

core/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import core.reports.views as report_views
2525
from core import VERSION
2626
from core.collections.views import ReferenceExpressionResolveView
27+
from core.common.swagger import OCLSwaggerSchemaGenerator
2728
from core.common.utils import get_api_base_url
2829
from core.common.views import RootView, FeedbackView, APIVersionView, ChangeLogView, StandardChecksumView, \
2930
SmartChecksumView
@@ -41,6 +42,7 @@
4142
SchemaView = get_schema_view(
4243
api_info,
4344
public=True,
45+
generator_class=OCLSwaggerSchemaGenerator,
4446
permission_classes=(permissions.AllowAny,),
4547
url=get_api_base_url()
4648
)

0 commit comments

Comments
 (0)