youssef commited on
Commit
0674654
·
1 Parent(s): 40e11b6

aba new backend api

Browse files
Files changed (3) hide show
  1. aba/main_test.py +1 -1
  2. aba/models.py +69 -0
  3. app.py +265 -46
aba/main_test.py CHANGED
@@ -64,7 +64,7 @@ def main():
64
  print("=" * 50)
65
 
66
  # Build framework from the given input specification file
67
- aba_framework = build_aba_framework("aba\examples\exemple.txt")
68
  print(f"\n ------- Original ABA framework -------\n{aba_framework}")
69
 
70
  base_framework = deepcopy(aba_framework)
 
64
  print("=" * 50)
65
 
66
  # Build framework from the given input specification file
67
+ aba_framework = build_aba_framework("aba/examples/exemple.txt")
68
  print(f"\n ------- Original ABA framework -------\n{aba_framework}")
69
 
70
  base_framework = deepcopy(aba_framework)
aba/models.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Optional, Dict, Tuple, Any
3
+
4
+
5
+ # === Basic DTOs ===
6
+
7
+ class RuleDTO(BaseModel):
8
+ """Represents a single inference rule in the ABA framework."""
9
+ id: str
10
+ head: str
11
+ body: List[str]
12
+
13
+
14
+ class FrameworkSnapshot(BaseModel):
15
+ """Snapshot of an ABA framework at a specific stage (original or transformed)."""
16
+ language: List[str]
17
+ assumptions: List[str]
18
+ rules: List[RuleDTO]
19
+ contraries: List[Tuple[str, str]]
20
+ preferences: Optional[Dict[str, List[str]]] = None
21
+
22
+
23
+ # === Transformation tracking ===
24
+
25
+ class TransformationStep(BaseModel):
26
+ """Represents one transformation step (non-circular, atomic, etc.)."""
27
+ step: str # 'non_circular' | 'atomic' | 'none'
28
+ applied: bool
29
+ reason: Optional[str] = None
30
+ description: Optional[str] = None
31
+ result_snapshot: Optional[FrameworkSnapshot] = None
32
+
33
+
34
+ # === ABA+ details ===
35
+
36
+ class ABAPlusDTO(BaseModel):
37
+ """Results specific to ABA+ semantics (extended attacks between assumption sets)."""
38
+ assumption_combinations: List[str]
39
+ normal_attacks: List[str]
40
+ reverse_attacks: List[str]
41
+
42
+
43
+ # === Meta info ===
44
+
45
+ class MetaInfo(BaseModel):
46
+ """Metadata about the ABA computation process."""
47
+ request_id: str
48
+ timestamp: str
49
+ transformed: bool
50
+ transformations_applied: List[str]
51
+ warnings: Optional[List[str]] = []
52
+ errors: Optional[List[str]] = []
53
+
54
+
55
+ # === Full API response ===
56
+
57
+ class ABAApiResponseModel(BaseModel):
58
+ """
59
+ Represents the full backend response for an ABA/ABA+ computation request.
60
+ Includes both the original and transformed frameworks, all transformation steps,
61
+ and computed results (arguments, attacks, ABA+ extensions).
62
+ """
63
+ meta: MetaInfo
64
+ original_framework: FrameworkSnapshot
65
+ transformations: List[TransformationStep]
66
+ final_framework: FrameworkSnapshot
67
+ arguments: List[str]
68
+ attacks: List[str]
69
+ aba_plus: ABAPlusDTO
app.py CHANGED
@@ -1,28 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
 
2
 
3
  cache_dir = "/tmp/hf_cache"
4
  os.environ["TRANSFORMERS_CACHE"] = cache_dir
5
  os.makedirs(cache_dir, exist_ok=True)
6
 
7
- from gradual.models import GradualInput, GradualOutput
8
- # from gradual.computations import compute_gradual_semantics
9
- from gradual.computations import compute_gradual_space
10
- from aba.aba_builder import prepare_aba_plus_framework, build_aba_framework_from_text
11
- from relations.predict_bert import predict_relation
12
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
13
- from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
14
- from fastapi.middleware.cors import CORSMiddleware
15
- from fastapi import FastAPI, UploadFile, File, Form, HTTPException
16
- import torch
17
- import pandas as pd
18
- from pathlib import Path
19
- import asyncio
20
- import json
21
- import io
 
 
 
 
 
 
 
22
 
23
 
 
 
 
 
 
 
 
 
 
24
  # -------------------- Config -------------------- #
25
 
 
26
  ABA_EXAMPLES_DIR = Path("./aba/examples")
27
  SAMPLES_DIR = Path("./relations/examples/samples")
28
  GRADUAL_EXAMPLES_DIR = Path("./gradual/examples")
@@ -112,37 +153,225 @@ def get_sample(filename: str):
112
 
113
  @app.post("/aba-upload")
114
  async def aba_upload(file: UploadFile = File(...)):
 
 
 
 
 
 
 
 
 
115
  content = await file.read()
116
  text = content.decode("utf-8")
117
 
118
- aba_framework = build_aba_framework_from_text(text)
119
- aba_framework.generate_arguments()
120
- aba_framework.generate_attacks()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- results = {
123
- "assumptions": [str(a) for a in aba_framework.assumptions],
124
- "arguments": [str(arg) for arg in aba_framework.arguments],
125
- "attacks": [str(att) for att in aba_framework.attacks],
126
- }
127
- return results
128
 
129
 
130
- @app.post("/aba-plus-upload")
131
- async def aba_upload(file: UploadFile = File(...)):
 
 
 
 
 
 
 
 
132
  content = await file.read()
133
  text = content.decode("utf-8")
134
 
135
- aba_framework = build_aba_framework_from_text(text)
136
- aba_framework = prepare_aba_plus_framework(aba_framework)
137
- aba_framework.make_aba_plus()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
- results = {
140
- "assumptions": [str(a) for a in aba_framework.assumptions],
141
- "arguments": [str(arg) for arg in aba_framework.arguments],
142
- "attacks": [str(att) for att in aba_framework.attacks],
143
- "reverse_attacks": [str(ratt) for ratt in aba_framework.reverse_attacks],
144
- }
145
- return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
 
148
  @app.get("/aba-examples")
@@ -161,16 +390,6 @@ def get_aba_example(filename: str):
161
 
162
  # --- Gradual semantics --- #
163
 
164
- # @app.post("/gradual", response_model=GradualOutput)
165
- # def compute_gradual(input_data: GradualInput):
166
- # """API endpoint to compute Weighted h-Categorizer samples and convex hull."""
167
- # return compute_gradual_semantics(
168
- # A=input_data.A,
169
- # R=input_data.R,
170
- # n_samples=input_data.n_samples,
171
- # max_iter=input_data.max_iter
172
- # )
173
-
174
  @app.post("/gradual", response_model=GradualOutput)
175
  def compute_gradual(input_data: GradualInput):
176
  """
 
1
+ import io
2
+ import json
3
+ import asyncio
4
+ from pathlib import Path
5
+ import pandas as pd
6
+ import torch
7
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.responses import FileResponse, StreamingResponse, JSONResponse
10
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
11
+ from relations.predict_bert import predict_relation
12
+ from aba.aba_builder import prepare_aba_plus_framework, build_aba_framework_from_text
13
+ from aba.models import (
14
+ RuleDTO,
15
+ FrameworkSnapshot,
16
+ TransformationStep,
17
+ ABAApiResponseModel,
18
+ ABAPlusDTO,
19
+ MetaInfo,
20
+ )
21
+ from gradual.computations import compute_gradual_space
22
+ from gradual.models import GradualInput, GradualOutput
23
  import os
24
+ from copy import deepcopy
25
+ from datetime import datetime
26
 
27
  cache_dir = "/tmp/hf_cache"
28
  os.environ["TRANSFORMERS_CACHE"] = cache_dir
29
  os.makedirs(cache_dir, exist_ok=True)
30
 
31
+
32
+ def _make_snapshot(fw) -> FrameworkSnapshot:
33
+ return FrameworkSnapshot(
34
+ language=[str(l) for l in sorted(fw.language, key=str)],
35
+ assumptions=[str(a) for a in sorted(fw.assumptions, key=str)],
36
+ rules=[
37
+ RuleDTO(
38
+ id=r.rule_name,
39
+ head=str(r.head),
40
+ body=[str(b) for b in sorted(r.body, key=str)],
41
+ )
42
+ for r in sorted(fw.rules, key=lambda r: r.rule_name)
43
+ ],
44
+ contraries=[
45
+ (str(c.contraried_literal), str(c.contrary_attacker))
46
+ for c in sorted(fw.contraries, key=str)
47
+ ],
48
+ preferences={
49
+ str(k): [str(v) for v in sorted(vals, key=str)]
50
+ for k, vals in (fw.preferences or {}).items()
51
+ } if getattr(fw, "preferences", None) else None,
52
+ )
53
 
54
 
55
+ def _format_set(s) -> str:
56
+ # s may be a Python set/frozenset of Literal or strings.
57
+ try:
58
+ items = sorted([str(x) for x in s], key=str)
59
+ except Exception:
60
+ # fallback if s is already a string like "{a,b}"
61
+ return str(s)
62
+ return "{" + ",".join(items) + "}"
63
+
64
  # -------------------- Config -------------------- #
65
 
66
+
67
  ABA_EXAMPLES_DIR = Path("./aba/examples")
68
  SAMPLES_DIR = Path("./relations/examples/samples")
69
  GRADUAL_EXAMPLES_DIR = Path("./gradual/examples")
 
153
 
154
  @app.post("/aba-upload")
155
  async def aba_upload(file: UploadFile = File(...)):
156
+ """
157
+ Handle classical ABA generation.
158
+ Returns:
159
+ - original_framework: before transformations
160
+ - final_framework: after transformations
161
+ - transformations: steps applied (non-circular / atomic)
162
+ - arguments, attacks
163
+ - empty aba_plus section
164
+ """
165
  content = await file.read()
166
  text = content.decode("utf-8")
167
 
168
+ # === 1. Build original ABA framework ===
169
+ base_framework = build_aba_framework_from_text(text)
170
+ original_snapshot = _make_snapshot(base_framework)
171
+
172
+ # === 2. Transform the framework ===
173
+ copy_framework = deepcopy(base_framework)
174
+ transformed_framework = copy_framework.transform_aba()
175
+
176
+ was_circular = base_framework.is_aba_circular()
177
+ was_atomic = base_framework.is_aba_atomic()
178
+
179
+ transformed_framework = deepcopy(base_framework).transform_aba()
180
+
181
+ # Detect transformation type
182
+ transformations: list[TransformationStep] = []
183
+ if transformed_framework.language != base_framework.language or transformed_framework.rules != base_framework.rules:
184
+ # Some transformation happened
185
+ if was_circular:
186
+ transformations.append(
187
+ TransformationStep(
188
+ step="non_circular",
189
+ applied=True,
190
+ reason="The framework contained circular dependencies.",
191
+ description="Transformed into a non-circular version.",
192
+ result_snapshot=_make_snapshot(transformed_framework),
193
+ )
194
+ )
195
+ elif not was_atomic:
196
+ transformations.append(
197
+ TransformationStep(
198
+ step="atomic",
199
+ applied=True,
200
+ reason="The framework contained rules with non-assumption bodies.",
201
+ description="Transformed into an atomic version.",
202
+ result_snapshot=_make_snapshot(transformed_framework),
203
+ )
204
+ )
205
+ else:
206
+ # No transformation
207
+ transformations.append(
208
+ TransformationStep(
209
+ step="none",
210
+ applied=False,
211
+ reason="The framework was already non-circular and atomic.",
212
+ description="No transformation applied.",
213
+ result_snapshot=None,
214
+ )
215
+ )
216
+
217
+ # === 3. Generate arguments and attacks on transformed ===
218
+ transformed_framework.generate_arguments()
219
+ transformed_framework.generate_attacks()
220
+
221
+ final_snapshot = _make_snapshot(transformed_framework)
222
+
223
+ # === 4. Build response model ===
224
+ response = ABAApiResponseModel(
225
+ meta=MetaInfo(
226
+ request_id=f"req-{datetime.utcnow().timestamp()}",
227
+ timestamp=datetime.utcnow().isoformat(),
228
+ transformed=any(t.applied for t in transformations),
229
+ transformations_applied=[
230
+ t.step for t in transformations if t.applied
231
+ ],
232
+ warnings=[],
233
+ errors=[],
234
+ ),
235
+ original_framework=original_snapshot,
236
+ transformations=transformations,
237
+ final_framework=final_snapshot,
238
+ arguments=[str(arg) for arg in sorted(
239
+ transformed_framework.arguments, key=str)],
240
+ attacks=[str(att)
241
+ for att in sorted(transformed_framework.attacks, key=str)],
242
+ aba_plus=ABAPlusDTO(
243
+ assumption_combinations=[],
244
+ normal_attacks=[],
245
+ reverse_attacks=[],
246
+ ),
247
+ )
248
 
249
+ return response
 
 
 
 
 
250
 
251
 
252
+ @app.post("/aba-plus-upload", response_model=ABAApiResponseModel)
253
+ async def aba_plus_upload(file: UploadFile = File(...)):
254
+ """
255
+ Handle ABA+ generation.
256
+ Returns:
257
+ - original_framework / final_framework with snapshots
258
+ - transformations applied (non_circular / atomic)
259
+ - arguments, classical attacks (from transformed framework)
260
+ - aba_plus: assumption_combinations, normal_attacks, reverse_attacks (string lists)
261
+ """
262
  content = await file.read()
263
  text = content.decode("utf-8")
264
 
265
+ # 1) Build base framework + original snapshot
266
+ base_fw = build_aba_framework_from_text(text)
267
+ original_snapshot = _make_snapshot(base_fw)
268
+
269
+ was_circular = base_fw.is_aba_circular()
270
+ was_atomic = base_fw.is_aba_atomic()
271
+
272
+ # 2) Transform (deepcopy → transform_aba)
273
+ transformed = deepcopy(base_fw).transform_aba()
274
+
275
+ # Track transformation step(s)
276
+ transformations: list[TransformationStep] = []
277
+ if transformed.language != base_fw.language or transformed.rules != base_fw.rules:
278
+ if was_circular:
279
+ transformations.append(
280
+ TransformationStep(
281
+ step="non_circular",
282
+ applied=True,
283
+ reason="The framework contained circular dependencies.",
284
+ description="Transformed into a non-circular version.",
285
+ result_snapshot=_make_snapshot(transformed),
286
+ )
287
+ )
288
+ elif not was_atomic:
289
+ transformations.append(
290
+ TransformationStep(
291
+ step="atomic",
292
+ applied=True,
293
+ reason="The framework contained non-atomic rules.",
294
+ description="Transformed into an atomic version.",
295
+ result_snapshot=_make_snapshot(transformed),
296
+ )
297
+ )
298
+ else:
299
+ transformations.append(
300
+ TransformationStep(
301
+ step="none",
302
+ applied=False,
303
+ reason="The framework was already non-circular and atomic.",
304
+ description="No transformation applied.",
305
+ result_snapshot=None,
306
+ )
307
+ )
308
+
309
+ # 3) Prepare for ABA+ (on the transformed copy) and compute
310
+ # generates arguments + classical attacks
311
+ fw_plus = prepare_aba_plus_framework(transformed)
312
+ fw_plus.make_aba_plus() # fills assumption_combinations, normal_attacks, reverse_attacks
313
+
314
+ warnings = []
315
+ if fw_plus.preferences:
316
+ all_assumpptions = {str(a) for a in fw_plus.assumptions}
317
+ pref_keys = {str(k) for k in fw_plus.preferences.keys()}
318
+ if not pref_keys.issubset(all_assumpptions):
319
+ warnings.append(
320
+ "Incomplete preference relation detected: not all assumptions appear in the preference mapping."
321
+ )
322
+
323
+ # 4) Final snapshot
324
+ final_snapshot = _make_snapshot(fw_plus)
325
+
326
+ # 5) Serialize ABA+ pieces as strings
327
+ assumption_sets = sorted(
328
+ [_format_set(s)
329
+ for s in getattr(fw_plus, "assumption_combinations", [])],
330
+ key=lambda x: (len(x), x)
331
+ )
332
 
333
+ normal_str = [
334
+ f"{_format_set(src)} {_format_set(dst)}"
335
+ for (src, dst) in sorted(
336
+ getattr(fw_plus, "normal_attacks", []),
337
+ key=lambda p: (str(p[0]), str(p[1])),
338
+ )
339
+ ]
340
+
341
+ reverse_str = [
342
+ f"{_format_set(src)} → {_format_set(dst)}"
343
+ for (src, dst) in sorted(
344
+ getattr(fw_plus, "reverse_attacks", []),
345
+ key=lambda p: (str(p[0]), str(p[1])),
346
+ )
347
+ ]
348
+ # arguments/attacks from transformed framework (already prepared)
349
+ arguments = [str(arg) for arg in sorted(fw_plus.arguments, key=str)]
350
+ attacks = [str(att) for att in sorted(fw_plus.attacks, key=str)]
351
+
352
+ # 6) Build response
353
+ resp = ABAApiResponseModel(
354
+ meta=MetaInfo(
355
+ request_id=f"req-{datetime.utcnow().timestamp()}",
356
+ timestamp=datetime.utcnow().isoformat(),
357
+ transformed=any(t.applied for t in transformations),
358
+ transformations_applied=[
359
+ t.step for t in transformations if t.applied],
360
+ warnings=warnings,
361
+ errors=[],
362
+ ),
363
+ original_framework=original_snapshot,
364
+ transformations=transformations,
365
+ final_framework=final_snapshot,
366
+ arguments=arguments,
367
+ attacks=attacks,
368
+ aba_plus=ABAPlusDTO(
369
+ assumption_combinations=assumption_sets,
370
+ normal_attacks=normal_str,
371
+ reverse_attacks=reverse_str,
372
+ ),
373
+ )
374
+ return resp
375
 
376
 
377
  @app.get("/aba-examples")
 
390
 
391
  # --- Gradual semantics --- #
392
 
 
 
 
 
 
 
 
 
 
 
393
  @app.post("/gradual", response_model=GradualOutput)
394
  def compute_gradual(input_data: GradualInput):
395
  """