Andrej Janchevski commited on
Commit
db54566
·
1 Parent(s): b609540

feat(coins): add sample-query endpoint and resolve Freebase MIDs to names

Browse files

Add GET /coins/datasets/{id}/sample-query that produces structurally
valid KG queries for all 7 query structures by delegating to
Query.instantiate() from the research code rather than hand-rolling
graph walks. Returns anchors, variables, relations, and a known target
entity — guaranteed to form a real path/intersection in the training KG.

Also resolve Freebase MID identifiers (e.g. /m/06rf7) to human-readable
names (e.g. Schleswig Holstein) using the ent2name.txt lookup table:

- Entity and relation search endpoints now return a `label` field with
cleaned display names alongside the raw `name`
- Search filtering matches against both raw names and cleaned labels,
so searching "Chicago" finds /m/01yjl (Chicago Cubs)
- sample-query and sample-triples responses carry resolved labels

docs/api.yaml CHANGED
@@ -190,6 +190,58 @@ paths:
190
  "404":
191
  $ref: "#/components/responses/NotFound"
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  /coins/models:
194
  get:
195
  operationId: getCoinsModels
@@ -914,6 +966,50 @@ components:
914
  tail:
915
  $ref: "#/components/schemas/CoinsEntity"
916
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
917
  CoinsModelsResponse:
918
  type: object
919
  required: [models]
 
190
  "404":
191
  $ref: "#/components/responses/NotFound"
192
 
193
+ /coins/datasets/{dataset_id}/sample-query:
194
+ get:
195
+ operationId: getCoinsSampleQuery
196
+ tags: [coins]
197
+ summary: Sample a valid KG query
198
+ description: |
199
+ Walks the training knowledge graph to produce a structurally valid query
200
+ for the given query structure. Each returned query contains anchor entities,
201
+ relations, and a known target entity — guaranteed to form a real path or
202
+ intersection in the KG. This is the recommended way to populate the query
203
+ builder UI with meaningful defaults. For simple 1p (single-hop) queries the
204
+ result is equivalent to a random triple; for multi-hop and intersection
205
+ structures (2p, 3p, 2i, 3i, ip, pi) the sampler walks backward from a
206
+ random target through actual KG edges.
207
+ parameters:
208
+ - $ref: "#/components/parameters/CoinsDatasetId"
209
+ - name: query_structure
210
+ in: query
211
+ required: true
212
+ schema:
213
+ $ref: "#/components/schemas/CoinsQueryStructureEnum"
214
+ description: The query structure to sample for.
215
+ - name: count
216
+ in: query
217
+ schema:
218
+ type: integer
219
+ minimum: 1
220
+ maximum: 10
221
+ default: 1
222
+ description: Number of sample queries to return.
223
+ - name: seed
224
+ in: query
225
+ required: false
226
+ schema:
227
+ type: string
228
+ description: |
229
+ Optional sampling seed for deterministic results. Same
230
+ `(dataset_id, query_structure, count, seed)` always yields the
231
+ same queries. Useful for day-stable prefills (e.g. seed by ISO date).
232
+ example: "2026-04-17"
233
+ responses:
234
+ "200":
235
+ description: Sampled queries
236
+ content:
237
+ application/json:
238
+ schema:
239
+ $ref: "#/components/schemas/CoinsSampleQueryResponse"
240
+ "400":
241
+ $ref: "#/components/responses/InvalidRequest"
242
+ "404":
243
+ $ref: "#/components/responses/NotFound"
244
+
245
  /coins/models:
246
  get:
247
  operationId: getCoinsModels
 
966
  tail:
967
  $ref: "#/components/schemas/CoinsEntity"
968
 
969
+ CoinsSampleQueryResponse:
970
+ type: object
971
+ required: [dataset_id, query_structure, queries]
972
+ properties:
973
+ dataset_id:
974
+ $ref: "#/components/schemas/CoinsDatasetIdEnum"
975
+ query_structure:
976
+ $ref: "#/components/schemas/CoinsQueryStructureEnum"
977
+ queries:
978
+ type: array
979
+ description: |
980
+ Sampled queries. Each query is a valid walk through the training KG.
981
+ May return fewer than `count` if the graph is too sparse for the
982
+ requested structure (e.g. 3i requires a target with at least 3 in-edges).
983
+ items:
984
+ $ref: "#/components/schemas/CoinsSampledQuery"
985
+
986
+ CoinsSampledQuery:
987
+ type: object
988
+ required: [anchors, relations, target]
989
+ description: |
990
+ A structurally valid query instance. `anchors` and `relations` keys match the
991
+ node/edge IDs from `GET /coins/query-structures` for the given structure.
992
+ The `target` is the known answer entity.
993
+ properties:
994
+ anchors:
995
+ type: object
996
+ additionalProperties:
997
+ $ref: "#/components/schemas/CoinsEntity"
998
+ description: "Anchor entities keyed by node ID (e.g. `a`, `a1`, `a2`, `a3`)."
999
+ example:
1000
+ a1: { id: 123, name: "/m/06thjt", label: "06thjt" }
1001
+ a2: { id: 456, name: "/m/0d_kd", label: "0d_kd" }
1002
+ relations:
1003
+ type: object
1004
+ additionalProperties:
1005
+ $ref: "#/components/schemas/CoinsRelation"
1006
+ description: "Relations keyed by edge ID (e.g. `r1`, `r2`, `r3`)."
1007
+ example:
1008
+ r1: { id: 7, name: "/people/person/nationality", label: "person nationality" }
1009
+ r2: { id: 12, name: "/location/country/capital", label: "country capital" }
1010
+ target:
1011
+ $ref: "#/components/schemas/CoinsEntity"
1012
+
1013
  CoinsModelsResponse:
1014
  type: object
1015
  required: [models]
docs/postman/collection.json CHANGED
@@ -146,6 +146,24 @@
146
  "description": "Random sample triples from the dataset. Each triple has head/relation/tail entries with { id, name, label }; `label` is a dataset-specific display-friendly form of `name` (NELL strips `concept:` prefixes, Freebase strips `/m/`, WordNet drops the POS suffix). Pass an optional `seed` (any string) for deterministic sampling — e.g. seed by today's ISO date for a day-stable 'fact of the day' widget."
147
  }
148
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  {
150
  "name": "GET /coins/models",
151
  "request": {
 
146
  "description": "Random sample triples from the dataset. Each triple has head/relation/tail entries with { id, name, label }; `label` is a dataset-specific display-friendly form of `name` (NELL strips `concept:` prefixes, Freebase strips `/m/`, WordNet drops the POS suffix). Pass an optional `seed` (any string) for deterministic sampling — e.g. seed by today's ISO date for a day-stable 'fact of the day' widget."
147
  }
148
  },
149
+ {
150
+ "name": "GET /coins/datasets/{id}/sample-query",
151
+ "request": {
152
+ "method": "GET",
153
+ "header": [],
154
+ "url": {
155
+ "raw": "{{base_url}}/coins/datasets/nell/sample-query?query_structure=2i&count=1&seed=2026-04-17",
156
+ "host": ["{{base_url}}"],
157
+ "path": ["coins", "datasets", "nell", "sample-query"],
158
+ "query": [
159
+ { "key": "query_structure", "value": "2i", "description": "Required. One of: 1p, 2p, 3p, 2i, 3i, ip, pi." },
160
+ { "key": "count", "value": "1", "description": "Number of sample queries (max 10)." },
161
+ { "key": "seed", "value": "2026-04-17", "description": "Optional. Deterministic sampling when provided." }
162
+ ]
163
+ },
164
+ "description": "Sample a structurally valid KG query by walking the training graph. Returns anchors, relations, and a known target entity that form a real path/intersection. Keys in `anchors` and `relations` match the node/edge IDs from GET /coins/query-structures. Preferred over sample-triples for multi-hop and intersection query prefills."
165
+ }
166
+ },
167
  {
168
  "name": "GET /coins/models",
169
  "request": {
src/backend/README.md CHANGED
@@ -78,6 +78,7 @@ All endpoints are prefixed with `/api/v1/`.
78
  | `GET` | `/coins/datasets/{id}/entities` | Paginated entity search (`?q=&page=&page_size=`) |
79
  | `GET` | `/coins/datasets/{id}/relations` | Paginated relation search (`?q=&page=&page_size=`) |
80
  | `GET` | `/coins/datasets/{id}/sample-triples` | Random training triples (`?count=10&seed=...`); optional `seed` makes sampling deterministic (same `seed+count` ⇒ same triples, e.g. seed by ISO date for a day-stable widget). Head/relation/tail each carry a dataset-cleaned `label` alongside `id`, `name` |
 
81
  | `GET` | `/coins/models` | Available algorithms + supported query structures |
82
  | `GET` | `/coins/query-structures` | Query graph templates for frontend rendering |
83
  | `POST` | `/coins/predict` | Run link prediction / query answering |
 
78
  | `GET` | `/coins/datasets/{id}/entities` | Paginated entity search (`?q=&page=&page_size=`) |
79
  | `GET` | `/coins/datasets/{id}/relations` | Paginated relation search (`?q=&page=&page_size=`) |
80
  | `GET` | `/coins/datasets/{id}/sample-triples` | Random training triples (`?count=10&seed=...`); optional `seed` makes sampling deterministic (same `seed+count` ⇒ same triples, e.g. seed by ISO date for a day-stable widget). Head/relation/tail each carry a dataset-cleaned `label` alongside `id`, `name` |
81
+ | `GET` | `/coins/datasets/{id}/sample-query` | Sample a structurally valid KG query (`?query_structure=2i&count=1&seed=...`). Walks the training graph to produce real paths/intersections. Returns `{anchors, relations, target}` keyed by node/edge IDs from `/coins/query-structures`. Preferred over `sample-triples` for multi-hop/intersection prefills |
82
  | `GET` | `/coins/models` | Available algorithms + supported query structures |
83
  | `GET` | `/coins/query-structures` | Query graph templates for frontend rendering |
84
  | `POST` | `/coins/predict` | Run link prediction / query answering |
src/backend/api/services/registry.py CHANGED
@@ -263,6 +263,30 @@ class SubgraphInfo:
263
  self.subgraphs = subgraphs
264
 
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  class ModelRegistry:
267
  _instance = None
268
 
@@ -523,7 +547,10 @@ class ModelRegistry:
523
  items = [(int(idx), str(name)) for idx, name in inv_nodes.items()]
524
  if query:
525
  q = query.lower()
526
- items = [(eid, name) for eid, name in items if q in name.lower()]
 
 
 
527
  total = len(items)
528
  start = (max(1, page) - 1) * page_size
529
  return items[start:start + page_size], total
@@ -537,7 +564,10 @@ class ModelRegistry:
537
  items = [(int(idx), str(name)) for idx, name in inv_relations.items()]
538
  if query:
539
  q = query.lower()
540
- items = [(rid, name) for rid, name in items if q in name.lower()]
 
 
 
541
  total = len(items)
542
  start = (max(1, page) - 1) * page_size
543
  return items[start:start + page_size], total
@@ -573,6 +603,94 @@ class ModelRegistry:
573
  })
574
  return result
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  # ---- Sample subgraph generation ------------------------------------
577
 
578
  def _generate_sample_subgraphs(self):
 
263
  self.subgraphs = subgraphs
264
 
265
 
266
+ # ---------------------------------------------------------------------------
267
+ # Query sampling — delegates to Query.instantiate() from the research code.
268
+ #
269
+ # _STRUCTURE_INFO maps frontend query structure IDs to:
270
+ # - research code structure string (e.g. "ip" -> "2i1p")
271
+ # - anchor_map: {query_tree_node_index: frontend_anchor_id}
272
+ # - variable_map: {query_tree_node_index: frontend_variable_id}
273
+ # - relation_map: {mapped_tree_edge_index: frontend_relation_id} for "p" edges
274
+ #
275
+ # After Query.instantiate() + map_to_tree(), entities live on vertices
276
+ # and relation strings (like "p5") live on edges indexed by child node index.
277
+ # ---------------------------------------------------------------------------
278
+ # (structure, anchor_map, variable_map, relation_map)
279
+ _STRUCTURE_INFO = {
280
+ "1p": ("1p", {0: "a"}, {}, {0: "r1"}),
281
+ "2p": ("2p", {0: "a"}, {1: "v1"}, {0: "r1", 1: "r2"}),
282
+ "3p": ("3p", {0: "a"}, {1: "v1", 2: "v2"}, {0: "r1", 1: "r2", 2: "r3"}),
283
+ "2i": ("2i", {0: "a1", 2: "a2"}, {}, {0: "r1", 2: "r2"}),
284
+ "3i": ("3i", {0: "a1", 2: "a2", 4: "a3"}, {}, {0: "r1", 2: "r2", 4: "r3"}),
285
+ "ip": ("2i1p", {0: "a1", 2: "a2"}, {4: "v1"}, {0: "r1", 2: "r2", 4: "r3"}),
286
+ "pi": ("1p2i", {0: "a1", 3: "a2"}, {1: "v1"}, {0: "r1", 1: "r2", 3: "r3"}),
287
+ }
288
+
289
+
290
  class ModelRegistry:
291
  _instance = None
292
 
 
547
  items = [(int(idx), str(name)) for idx, name in inv_nodes.items()]
548
  if query:
549
  q = query.lower()
550
+ items = [
551
+ (eid, name) for eid, name in items
552
+ if q in name.lower() or q in clean_entity_name(name, dataset_id).lower()
553
+ ]
554
  total = len(items)
555
  start = (max(1, page) - 1) * page_size
556
  return items[start:start + page_size], total
 
564
  items = [(int(idx), str(name)) for idx, name in inv_relations.items()]
565
  if query:
566
  q = query.lower()
567
+ items = [
568
+ (rid, name) for rid, name in items
569
+ if q in name.lower() or q in clean_relation_name(name, dataset_id).lower()
570
+ ]
571
  total = len(items)
572
  start = (max(1, page) - 1) * page_size
573
  return items[start:start + page_size], total
 
603
  })
604
  return result
605
 
606
+ # ---- Query sampling ---------------------------------------------------
607
+
608
+ def sample_query(self, dataset_id, query_structure, count=1, seed=None):
609
+ """Sample structurally valid queries using Query.instantiate() from the research code.
610
+
611
+ Picks random answer entities, walks the training graph backward via
612
+ adj_t_to_s to produce fully-instantiated query trees, then extracts
613
+ anchor entities and relation IDs mapped to frontend slot names.
614
+ """
615
+ import numpy as np
616
+
617
+ loader = self.loaders.get(dataset_id)
618
+ if loader is None:
619
+ return []
620
+
621
+ info = _STRUCTURE_INFO.get(query_structure)
622
+ if info is None:
623
+ return []
624
+ structure_str, anchor_map, variable_map, relation_map = info
625
+
626
+ adj_t_to_s = loader.graph_indexes[2]
627
+ inv_nodes, _, inv_relations = loader.dataset.get_inverted_name_maps()
628
+
629
+ coins_root = str(Path(settings.COINS_DATA_DIR).parent)
630
+ original_cwd = os.getcwd()
631
+ try:
632
+ os.chdir(coins_root)
633
+ from graph_completion.graphs.queries import Query, query_edge_r_to_int
634
+ finally:
635
+ os.chdir(original_cwd)
636
+
637
+ query = Query(structure_str)
638
+ query.build_query_tree()
639
+
640
+ answer_candidates = list(adj_t_to_s.keys())
641
+ rng = random.Random(seed) if seed is not None else random
642
+
643
+ np_state = np.random.get_state()
644
+ if seed is not None:
645
+ np.random.seed(hash(seed) % (2**32))
646
+
647
+ def ent(eid):
648
+ name = str(inv_nodes.get(eid, eid))
649
+ return {"id": eid, "name": name, "label": clean_entity_name(name, dataset_id)}
650
+
651
+ def rel(rid):
652
+ name = str(inv_relations.get(rid, rid))
653
+ return {"id": rid, "name": name, "label": clean_relation_name(name, dataset_id)}
654
+
655
+ results = []
656
+ max_attempts = count * 200
657
+ try:
658
+ for _ in range(max_attempts):
659
+ if len(results) >= count:
660
+ break
661
+ answer = rng.choice(answer_candidates)
662
+ qi = next(
663
+ query.instantiate(adj_t_to_s, loader.num_nodes, loader.num_relations, answer, sample=True),
664
+ None,
665
+ )
666
+ if qi is None:
667
+ continue
668
+
669
+ qi_mapped = qi.map_to_tree(query.query_tree)
670
+
671
+ anchors = {}
672
+ for tree_idx, frontend_id in anchor_map.items():
673
+ anchors[frontend_id] = ent(int(qi_mapped.vs[tree_idx]["e"]))
674
+
675
+ variables = {}
676
+ for tree_idx, frontend_id in variable_map.items():
677
+ variables[frontend_id] = ent(int(qi_mapped.vs[tree_idx]["e"]))
678
+
679
+ relations = {}
680
+ for edge_idx, frontend_id in relation_map.items():
681
+ rel_id = query_edge_r_to_int(qi_mapped.es[edge_idx]["r"])
682
+ relations[frontend_id] = rel(rel_id)
683
+
684
+ target_id = int(qi_mapped.vs[query.query_answer]["e"])
685
+ q = {"anchors": anchors, "relations": relations, "target": ent(target_id)}
686
+ if variables:
687
+ q["variables"] = variables
688
+ results.append(q)
689
+ finally:
690
+ np.random.set_state(np_state)
691
+
692
+ return results
693
+
694
  # ---- Sample subgraph generation ------------------------------------
695
 
696
  def _generate_sample_subgraphs(self):
src/backend/api/urls.py CHANGED
@@ -7,6 +7,7 @@ from api.views.coins import (
7
  CoinsPredictView,
8
  CoinsQueryStructuresView,
9
  CoinsRelationsView,
 
10
  CoinsSampleTriplesView,
11
  )
12
  from api.views.graph_generation import (
@@ -31,6 +32,7 @@ urlpatterns = [
31
  path("coins/datasets/<str:dataset_id>/entities", CoinsEntitiesView.as_view()),
32
  path("coins/datasets/<str:dataset_id>/relations", CoinsRelationsView.as_view()),
33
  path("coins/datasets/<str:dataset_id>/sample-triples", CoinsSampleTriplesView.as_view()),
 
34
  path("coins/models", CoinsModelsView.as_view()),
35
  path("coins/query-structures", CoinsQueryStructuresView.as_view()),
36
  path("coins/predict", CoinsPredictView.as_view()),
 
7
  CoinsPredictView,
8
  CoinsQueryStructuresView,
9
  CoinsRelationsView,
10
+ CoinsSampleQueryView,
11
  CoinsSampleTriplesView,
12
  )
13
  from api.views.graph_generation import (
 
32
  path("coins/datasets/<str:dataset_id>/entities", CoinsEntitiesView.as_view()),
33
  path("coins/datasets/<str:dataset_id>/relations", CoinsRelationsView.as_view()),
34
  path("coins/datasets/<str:dataset_id>/sample-triples", CoinsSampleTriplesView.as_view()),
35
+ path("coins/datasets/<str:dataset_id>/sample-query", CoinsSampleQueryView.as_view()),
36
  path("coins/models", CoinsModelsView.as_view()),
37
  path("coins/query-structures", CoinsQueryStructuresView.as_view()),
38
  path("coins/predict", CoinsPredictView.as_view()),
src/backend/api/utils.py CHANGED
@@ -1,6 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  def clean_entity_name(name: str, dataset_id: str) -> str:
2
- """Apply dataset-specific entity name cleaning for display (from graph_generation/src/main.py)."""
3
  if dataset_id == "freebase":
 
 
 
4
  return name.replace("/m/", "")
5
  elif dataset_id == "wordnet":
6
  return name.split(".")[0]
 
1
+ from pathlib import Path
2
+
3
+ from django.conf import settings
4
+
5
+ _freebase_ent2name = None
6
+
7
+
8
+ def _get_freebase_ent2name():
9
+ global _freebase_ent2name
10
+ if _freebase_ent2name is None:
11
+ path = Path(settings.COINS_DATA_DIR) / "FB15k-237" / "ent2name.txt"
12
+ mapping = {}
13
+ if path.exists():
14
+ with open(path, encoding="utf-8") as f:
15
+ for line in f:
16
+ parts = line.rstrip("\n").split(None, 1)
17
+ if len(parts) == 2:
18
+ mapping[parts[0]] = parts[1].replace("_", " ")
19
+ _freebase_ent2name = mapping
20
+ return _freebase_ent2name
21
+
22
+
23
  def clean_entity_name(name: str, dataset_id: str) -> str:
24
+ """Apply dataset-specific entity name cleaning for display."""
25
  if dataset_id == "freebase":
26
+ resolved = _get_freebase_ent2name().get(name)
27
+ if resolved:
28
+ return resolved
29
  return name.replace("/m/", "")
30
  elif dataset_id == "wordnet":
31
  return name.split(".")[0]
src/backend/api/views/coins.py CHANGED
@@ -7,6 +7,7 @@ from api.services.constants import (
7
  QUERY_STRUCTURE_INTERNAL, QUERY_TREE_MAPPINGS,
8
  )
9
  from api.services.registry import ModelRegistry
 
10
 
11
 
12
  def _require_loader(dataset_id):
@@ -49,7 +50,10 @@ class CoinsEntitiesView(APIView):
49
  "total": total,
50
  "page": page,
51
  "page_size": page_size,
52
- "entities": [{"id": eid, "name": name} for eid, name in page_items],
 
 
 
53
  })
54
 
55
 
@@ -68,7 +72,10 @@ class CoinsRelationsView(APIView):
68
  "total": total,
69
  "page": page,
70
  "page_size": page_size,
71
- "relations": [{"id": rid, "name": name} for rid, name in page_items],
 
 
 
72
  })
73
 
74
 
@@ -87,6 +94,30 @@ class CoinsSampleTriplesView(APIView):
87
  })
88
 
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  class CoinsModelsView(APIView):
91
  def get(self, request):
92
  registry = ModelRegistry.get()
 
7
  QUERY_STRUCTURE_INTERNAL, QUERY_TREE_MAPPINGS,
8
  )
9
  from api.services.registry import ModelRegistry
10
+ from api.utils import clean_entity_name, clean_relation_name
11
 
12
 
13
  def _require_loader(dataset_id):
 
50
  "total": total,
51
  "page": page,
52
  "page_size": page_size,
53
+ "entities": [
54
+ {"id": eid, "name": name, "label": clean_entity_name(name, dataset_id)}
55
+ for eid, name in page_items
56
+ ],
57
  })
58
 
59
 
 
72
  "total": total,
73
  "page": page,
74
  "page_size": page_size,
75
+ "relations": [
76
+ {"id": rid, "name": name, "label": clean_relation_name(name, dataset_id)}
77
+ for rid, name in page_items
78
+ ],
79
  })
80
 
81
 
 
94
  })
95
 
96
 
97
+ class CoinsSampleQueryView(APIView):
98
+ def get(self, request, dataset_id):
99
+ registry = _require_loader(dataset_id)
100
+ query_structure = request.query_params.get("query_structure")
101
+ if not query_structure:
102
+ raise InvalidRequestError("Missing required parameter: query_structure")
103
+ valid_qs = {qs["id"] for qs in QUERY_STRUCTURES}
104
+ if query_structure not in valid_qs:
105
+ raise InvalidRequestError(
106
+ f"Unknown query_structure '{query_structure}'. Must be one of: {sorted(valid_qs)}"
107
+ )
108
+ count = int(request.query_params.get("count", 1))
109
+ count = max(1, min(10, count))
110
+ seed_raw = request.query_params.get("seed")
111
+ seed = seed_raw if seed_raw not in (None, "") else None
112
+
113
+ queries = registry.sample_query(dataset_id, query_structure, count, seed=seed)
114
+ return Response({
115
+ "dataset_id": dataset_id,
116
+ "query_structure": query_structure,
117
+ "queries": queries,
118
+ })
119
+
120
+
121
  class CoinsModelsView(APIView):
122
  def get(self, request):
123
  registry = ModelRegistry.get()
src/research/COINs-KGGeneration/data/FB15k-237/ent2name.txt ADDED
The diff for this file is too large to render. See raw diff