Skip to content

Commit d52f102

Browse files
committed
merge origin/main
2 parents f8316f6 + 7470db3 commit d52f102

38 files changed

Lines changed: 1211 additions & 211 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ backend/ingestion/data
1919
backend/inquiro-env/
2020

2121
# Claude Code
22-
.claude
22+
.claude/
2323

2424
# Evaluation data and results
2525
backend/evaluation_data/

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@ Frontend API types are generated from backend OpenAPI schema. When backend endpo
8181

8282
### Frontend
8383
- ESLint + Prettier
84-
- Husky + lint-staged for pre-commit hooks
84+
- Husky + lint-staged for pre-commit hooks

backend/.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
repos:
2-
- repo: https://github.com/astral-sh/ruff-pre-commit
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
33
rev: 'v0.5.0' # Use a recent ruff version
44
hooks:
5-
- id: ruff
6-
args: [--fix, --exit-non-zero-on-fix]
7-
- id: ruff-format
5+
- id: ruff
6+
args: [ --fix, --exit-non-zero-on-fix ]
7+
- id: ruff-format

backend/app/constants/database_constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@ class PaperType(Enum):
1818
WORKSHOP = "WORKSHOP"
1919
THESIS = "THESIS"
2020
OTHER = "OTHER"
21+
22+
23+
class PaperContentStatus(Enum):
24+
"""Lifecycle status of paper content parsing and storage."""
25+
PENDING = "PENDING"
26+
PROCESSING = "PROCESSING"
27+
SUCCEEDED = "SUCCEEDED"
28+
FAILED = "FAILED"

backend/app/core/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ class Settings(BaseSettings):
2929
# --- OpenAI ---
3030
OPENAI_API_KEY: Optional[str] = None
3131

32+
# --- Docling PDF Conversion ---
33+
DOCLING_WORKERS: int = 2
34+
DOCLING_TIMEOUT_SECONDS: int = 300
35+
DOCLING_MAX_RETRIES: int = 3
36+
DOCLING_RETRY_BASE_DELAY: int = 30 # seconds, doubles each attempt
37+
3238
model_config = SettingsConfigDict(
3339
env_file=os.getenv("ENV_FILE", "dev.env"),
3440
extra="ignore",

backend/app/core/database.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ async def init_db() -> None:
3636
"app.models.project",
3737
"app.models.paper",
3838
"app.models.project_paper",
39+
"app.models.paper_content",
3940
):
4041
import_module(module)
4142

backend/app/core/deps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def get_specter2_proximity_embedder() -> Specter2Embedder:
1515
@lru_cache(maxsize=1)
1616
def get_specter2_query_embedder() -> Specter2Embedder:
1717
"""Return a shared Specter2Embedder instance, initialized once."""
18-
return Specter2Embedder(model="allenai/specter2_adhoc_query")
18+
return Specter2Embedder(adapter="allenai/specter2_adhoc_query")
1919

2020

2121
@lru_cache(maxsize=1)

backend/app/core/security.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta]
1919

2020
to_encode = data.copy()
2121
expire = datetime.now(timezone.utc) + (
22-
expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
22+
expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
2323
)
2424
to_encode.update({"exp": expire})
2525
return jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=ALGORITHM)
@@ -30,7 +30,7 @@ def create_refresh_token(data: Dict[str, Any], expires_delta: Optional[timedelta
3030

3131
to_encode = data.copy()
3232
expire = datetime.now(timezone.utc) + (
33-
expires_delta or timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
33+
expires_delta or timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
3434
)
3535
to_encode.update({"exp": expire, "type": "refresh"})
3636
return jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=ALGORITHM)

backend/app/llm/embeddings/specter2.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from typing import List, Optional
33

44
import torch
5-
from transformers import AutoAdapterModel, AutoTokenizer
5+
from adapters import AutoAdapterModel
6+
from transformers import AutoTokenizer
67

78
logger = logging.getLogger(__name__)
89

@@ -20,31 +21,46 @@ def build_specter2_text(title: str, abstract: str, tokenizer: AutoTokenizer) ->
2021
class Specter2Embedder:
2122
"""Wrapper around the SPECTER2 retrieval (proximity) model."""
2223

23-
def __init__(self, model: str = "allenai/specter2", device: Optional[str] = None) -> None:
24+
def __init__(
25+
self, adapter: str = "allenai/specter2_base", device: Optional[str] = None
26+
) -> None:
2427
"""Load SPECTER2 tokenizer, base model, and proximity adapter."""
28+
2529
if device is None:
2630
device = "cuda" if torch.cuda.is_available() else "cpu"
2731

2832
self.device = torch.device(device)
29-
logger.info("Loading SPECTER2 base + proximity adapter on %s...", self.device)
33+
self.adapter = adapter
3034

31-
# Load tokenizer + base model
32-
self.tokenizer = AutoTokenizer.from_pretrained("allenai/specter2_base")
35+
# Tokenizer (shared across all variants)
36+
self.tokenizer = AutoTokenizer.from_pretrained(
37+
"allenai/specter2_base",
38+
local_files_only=False,
39+
trust_remote_code=False,
40+
)
3341

34-
self.model = AutoAdapterModel.from_pretrained("allenai/specter2_base")
42+
# Adapter-aware model
43+
self.model = AutoAdapterModel.from_pretrained(
44+
"allenai/specter2_base",
45+
local_files_only=False,
46+
trust_remote_code=False,
47+
)
3548

36-
# Load retrieval adapter (proximity)
49+
# Load and activate adapter
3750
self.model.load_adapter(
38-
model,
39-
load_as="specter2",
40-
set_active=True,
51+
adapter,
4152
source="hf",
53+
set_active=True,
4254
)
4355

4456
self.model.to(self.device)
4557
self.model.eval()
4658

47-
logger.info("SPECTER2 model + adapter loaded successfully.")
59+
logger.info(
60+
"Loaded SPECTER2 model with adapter '%s' on device '%s'",
61+
adapter,
62+
self.device,
63+
)
4864

4965
def embed_batch(self, texts: List[str]) -> List[Optional[List[float]]]:
5066
"""

backend/app/llm/evaluation/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ python -m app.llm.evaluation.dataset_generator \
3434
Note: The default output is `evaluation_data/dataset.json` (directories are created automatically).
3535

3636
This will create the dataset file with test cases containing:
37+
3738
- `ground_truth_keywords`: The original keywords
3839
- `user_input`: Generated natural language query
3940
- `metadata`: Generation info
@@ -58,6 +59,7 @@ python -m app.llm.evaluation.prompt_evaluator \
5859
Note: The default output is `evaluation_results/results.json` (directories are created automatically).
5960

6061
The results will include:
62+
6163
- `mean_jaccard`: Average Jaccard similarity score
6264
- `std_jaccard`: Standard deviation
6365
- `min_jaccard` / `max_jaccard`: Score range
@@ -66,6 +68,7 @@ The results will include:
6668
## Metrics
6769

6870
**Jaccard Similarity**: Measures overlap between extracted and ground truth keywords
71+
6972
- Formula: `|A ∩ B| / |A ∪ B|`
7073
- Range: 0.0 (no overlap) to 1.0 (perfect match)
7174
- Keywords are normalized (lowercase, stripped) before comparison
@@ -127,6 +130,7 @@ python -m app.llm.evaluation.evaluate_all_prompts \
127130
```
128131

129132
This will:
133+
130134
- Evaluate all prompts matching the pattern (default: `prompt_*.txt`)
131135
- Save individual results for each prompt
132136
- Generate a comparison report ranking prompts by mean Jaccard score

0 commit comments

Comments
 (0)