Spaces:
Running
Running
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +17 -0
- .gitattributes +1 -0
- .streamlit/config.toml +22 -0
- .streamlit/credentials.toml +2 -0
- Deep-Citation/Data/acl.tsv +0 -0
- Deep-Citation/Data/class_def.json +23 -0
- Deep-Citation/Model/__init__.py +1 -0
- Deep-Citation/Model/model.py +89 -0
- Deep-Citation/Workspace/acl_scicite_wksp_trl/args.txt +21 -0
- Deep-Citation/Workspace/acl_scicite_wksp_trl/best_model.pt +3 -0
- Deep-Citation/data.py +211 -0
- Dockerfile +36 -0
- README.md +226 -3
- app.py +5 -0
- docker-compose.neo4j.yml +19 -0
- hf_space/build_replay_traces.py +305 -0
- hf_space/neo4j_workflow.py +150 -0
- hf_space/replay.py +130 -0
- hf_space/requirements.txt +28 -0
- hf_space/runner.py +333 -0
- hf_space/scipath_live/README.md +38 -0
- hf_space/scipath_live/__init__.py +19 -0
- hf_space/scipath_live/agent.py +227 -0
- hf_space/scipath_live/cli.py +69 -0
- hf_space/scipath_live/data/example_claim.json +124 -0
- hf_space/scipath_live/judge.py +305 -0
- hf_space/scipath_live/pipeline.py +101 -0
- hf_space/scipath_live/prompts.py +170 -0
- hf_space/scipath_live/tools.py +170 -0
- hf_space/streamlit_app.py +0 -0
- hf_space/streamlit_config.py +50 -0
- hf_space/system_live_runner.py +317 -0
- hf_space/system_run_cases/case_study_analysis.json +829 -0
- hf_space/system_run_data.py +444 -0
- hf_space/workflow_graph.py +1197 -0
- replay_traces/2208.00329/input_ids.json +7 -0
- replay_traces/2208.00329/logs/step_01.log +18 -0
- replay_traces/2208.00329/logs/step_02.log +5 -0
- replay_traces/2208.00329/logs/step_03.log +4 -0
- replay_traces/2208.00329/logs/step_04.log +6 -0
- replay_traces/2208.00329/logs/step_05.log +14 -0
- replay_traces/2208.00329/logs/step_06.log +7 -0
- replay_traces/2208.00329/logs/step_07.log +28 -0
- replay_traces/2208.00329/processed_papers/2208.00329/paper_metadata.json +55 -0
- replay_traces/2208.00329/processed_papers/2208.00329/usage_citing_paragraphs.json +0 -0
- replay_traces/2208.00329/processed_papers/2208.00329/usage_context_labels.json +292 -0
- replay_traces/2208.00329/processed_papers/2208.00329/usage_contexts.json +326 -0
- replay_traces/2208.00329/processed_papers/2208.00329/usage_contributions.json +43 -0
- replay_traces/2208.00329/processed_papers/2208.00329/usage_discovery_from_contributions.json +38 -0
- replay_traces/2208.00329/processed_papers/2208.00329/usage_uses_extends_verified.json +150 -0
.dockerignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.venv
|
| 3 |
+
venv
|
| 4 |
+
__pycache__
|
| 5 |
+
**/__pycache__
|
| 6 |
+
*.pyc
|
| 7 |
+
**/*.pyc
|
| 8 |
+
.DS_Store
|
| 9 |
+
.env
|
| 10 |
+
.streamlit/secrets.toml
|
| 11 |
+
.playwright
|
| 12 |
+
hf_space/runs
|
| 13 |
+
runs
|
| 14 |
+
*.zip
|
| 15 |
+
**/.pytest_cache
|
| 16 |
+
**/node_modules
|
| 17 |
+
agent-transcripts
|
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
replay_traces/2505.17978/processed_papers/2505.17978/usage_citing_paragraphs.json filter=lfs diff=lfs merge=lfs -text
|
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[browser]
|
| 2 |
+
gatherUsageStats = false
|
| 3 |
+
|
| 4 |
+
[server]
|
| 5 |
+
headless = true
|
| 6 |
+
# Hugging Face Spaces / Docker bind on all interfaces; local overrides via CLI are fine.
|
| 7 |
+
address = "0.0.0.0"
|
| 8 |
+
port = 7860
|
| 9 |
+
fileWatcherType = "auto"
|
| 10 |
+
runOnSave = false
|
| 11 |
+
|
| 12 |
+
[client]
|
| 13 |
+
toolbarMode = "viewer"
|
| 14 |
+
showSidebarNavigation = false
|
| 15 |
+
|
| 16 |
+
[theme]
|
| 17 |
+
base = "light"
|
| 18 |
+
primaryColor = "#0f6b5c"
|
| 19 |
+
backgroundColor = "#e7eef0"
|
| 20 |
+
secondaryBackgroundColor = "#f4f8f7"
|
| 21 |
+
textColor = "#13201d"
|
| 22 |
+
font = "sans serif"
|
.streamlit/credentials.toml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[general]
|
| 2 |
+
email = ""
|
Deep-Citation/Data/acl.tsv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Deep-Citation/Data/class_def.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"acl":
|
| 3 |
+
{
|
| 4 |
+
"BACKGROUND": "The citation provides relevant information for the domain that the present paper discusses.",
|
| 5 |
+
"MOTIVATION": "The citation illustrates the need for data, goals, methods, etc that is proposed in the present paper.",
|
| 6 |
+
"USES": "The present paper uses data, methods, etc., from the paper associated with the citation.",
|
| 7 |
+
"EXTENDS": "The present paper extends the data, methods, etc. from the paper associated with the citation.",
|
| 8 |
+
"COMPAREORCONTRAST": "The present paper expresses similarity / differences to the citation.",
|
| 9 |
+
"FUTURE": "The citation is a potential avenue for future work of the present paper."
|
| 10 |
+
},
|
| 11 |
+
"kim":
|
| 12 |
+
{
|
| 13 |
+
"Used": "The present paper uses at least one method that is proposed in the paper associated with the citation.",
|
| 14 |
+
"Not used": "The present paper does not use or extend any methods that is proposed in the paper associated with the citation.",
|
| 15 |
+
"Extended": "The present paper uses an extended / modified version of the method proposed in the paper associated with the citation."
|
| 16 |
+
},
|
| 17 |
+
"scicite":
|
| 18 |
+
{
|
| 19 |
+
"Background": "The citation states, mentions, or points to the background information giving more context about a problem, concept, approach, topic, or importance of the problem that is discussed in the present paper.",
|
| 20 |
+
"Method": "The present paper uses a method, tool, approach or dataset that is proposed in the paper associated with the citation.",
|
| 21 |
+
"Result": "The present paper compares its results/findings with the results/findings of the paper associated with the citation."
|
| 22 |
+
}
|
| 23 |
+
}
|
Deep-Citation/Model/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .model import LanguageModel, MultiHeadLanguageModel
|
Deep-Citation/Model/model.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
|
| 5 |
+
from typing import List
|
| 6 |
+
from transformers import AutoModel
|
| 7 |
+
|
| 8 |
+
def mask_pooling(model_output, attention_mask):
|
| 9 |
+
token_embeddings = model_output[0] #First element of model_output contains all token embeddings
|
| 10 |
+
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
|
| 11 |
+
return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
|
| 12 |
+
|
| 13 |
+
class LanguageModel(nn.Module):
|
| 14 |
+
def __init__(self,
|
| 15 |
+
modelname: str,
|
| 16 |
+
device: str,
|
| 17 |
+
readout: str
|
| 18 |
+
):
|
| 19 |
+
super(LanguageModel, self).__init__()
|
| 20 |
+
self.device = device
|
| 21 |
+
self.modelname = modelname
|
| 22 |
+
self.readout_fn = readout
|
| 23 |
+
|
| 24 |
+
self.model = AutoModel.from_pretrained(modelname)
|
| 25 |
+
self.hidden_size = self.model.config.hidden_size
|
| 26 |
+
|
| 27 |
+
def readout(self, model_inputs, model_outputs, readout_masks=None):
|
| 28 |
+
if self.readout_fn == 'cls':
|
| 29 |
+
if 'bert' in self.modelname or 'deberta' in self.modelname:
|
| 30 |
+
text_representations = model_outputs.last_hidden_state[:, 0]
|
| 31 |
+
elif 'xlnet' in self.modelname:
|
| 32 |
+
text_representations = model_outputs.last_hidden_state[:, -1]
|
| 33 |
+
else:
|
| 34 |
+
raise ValueError('Invalid model name {} for the cls readout.'.format(self.modelname))
|
| 35 |
+
elif self.readout_fn == 'mean':
|
| 36 |
+
text_representations = mask_pooling(model_outputs, model_inputs['attention_mask'])
|
| 37 |
+
elif self.readout_fn == 'ch' and readout_masks is not None:
|
| 38 |
+
text_representations = mask_pooling(model_outputs, readout_masks)
|
| 39 |
+
else:
|
| 40 |
+
raise ValueError('Invalid readout function.')
|
| 41 |
+
return text_representations
|
| 42 |
+
|
| 43 |
+
def _lm_forward(self, tokens):
|
| 44 |
+
tokens = tokens.to(self.device)
|
| 45 |
+
if 'readout_mask' in tokens:
|
| 46 |
+
readout_mask = tokens.pop('readout_mask')
|
| 47 |
+
else:
|
| 48 |
+
readout_mask = None
|
| 49 |
+
outputs = self.model(**tokens)
|
| 50 |
+
return self.readout(tokens, outputs, readout_mask)
|
| 51 |
+
|
| 52 |
+
def forward(self):
|
| 53 |
+
raise NotImplementedError
|
| 54 |
+
|
| 55 |
+
def save_pretrained(self, modeldir):
|
| 56 |
+
model_filename = os.path.join(modeldir, 'checkpoint.pt')
|
| 57 |
+
torch.save(self.state_dict(), model_filename)
|
| 58 |
+
|
| 59 |
+
def load_pretrained(self, modeldir):
|
| 60 |
+
model_filename = os.path.join(modeldir, 'checkpoint.pt')
|
| 61 |
+
self.load_state_dict(torch.load(model_filename))
|
| 62 |
+
|
| 63 |
+
class MultiHeadLanguageModel(LanguageModel):
|
| 64 |
+
def __init__(self,
|
| 65 |
+
modelname: str,
|
| 66 |
+
device: str,
|
| 67 |
+
readout: str,
|
| 68 |
+
num_classes: List
|
| 69 |
+
):
|
| 70 |
+
super().__init__(
|
| 71 |
+
modelname,
|
| 72 |
+
device,
|
| 73 |
+
readout
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
self.num_classes = num_classes
|
| 77 |
+
self.lns = nn.ModuleList([nn.Linear(self.hidden_size, num_class) for num_class in num_classes])
|
| 78 |
+
|
| 79 |
+
def forward(self, input_tokens, input_head_indices, class_tokens, class_head_indices):
|
| 80 |
+
head_indices = torch.unique(input_head_indices)
|
| 81 |
+
text_representations = self._lm_forward(input_tokens)
|
| 82 |
+
|
| 83 |
+
final_preds = {}
|
| 84 |
+
for i in head_indices:
|
| 85 |
+
if torch.any(input_head_indices == i):
|
| 86 |
+
final_preds[i.item()] = self.lns[i.item()](text_representations[input_head_indices == i])
|
| 87 |
+
else:
|
| 88 |
+
final_preds[i.item()] = torch.tensor([]).to(self.device)
|
| 89 |
+
return final_preds
|
Deep-Citation/Workspace/acl_scicite_wksp_trl/args.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Namespace(dataset='acl-scicite',
|
| 2 |
+
lambdas='1-0.063',
|
| 3 |
+
data_dir='Data',
|
| 4 |
+
workspace='Workspace/acl_scicite_wksp_trl',
|
| 5 |
+
class_definition='Data/class_def.json',
|
| 6 |
+
batch_size=32,
|
| 7 |
+
lr=5e-05,
|
| 8 |
+
decay_rate=0.5,
|
| 9 |
+
decay_step=5,
|
| 10 |
+
num_epochs=10,
|
| 11 |
+
scheduler='slanted',
|
| 12 |
+
dropout_rate=0.2,
|
| 13 |
+
l2=0.0,
|
| 14 |
+
device='cuda',
|
| 15 |
+
tol=10,
|
| 16 |
+
inference_only=False,
|
| 17 |
+
seed=1,
|
| 18 |
+
lm='scibert',
|
| 19 |
+
max_length=512,
|
| 20 |
+
batch_size_factor=2,
|
| 21 |
+
readout='ch')
|
Deep-Citation/Workspace/acl_scicite_wksp_trl/best_model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e45ab11942439f80a121dad5b2d9da392470e0cedf6a7335991fa0a1f616dcb2
|
| 3 |
+
size 439784777
|
Deep-Citation/data.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import copy
|
| 4 |
+
import torch
|
| 5 |
+
import scipy
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pandas as pd
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
from scipy.special import softmax
|
| 10 |
+
|
| 11 |
+
from transformers import AutoTokenizer
|
| 12 |
+
|
| 13 |
+
class CollateFn(object):
|
| 14 |
+
def __init__(self, modelname, class_definitions=None, instance_weights=False):
|
| 15 |
+
self.instance_weights = instance_weights
|
| 16 |
+
use_fast = False if 'deberta' in modelname else True
|
| 17 |
+
self.tokenizer = AutoTokenizer.from_pretrained(modelname, use_fast=use_fast)
|
| 18 |
+
cited_ids = self.tokenizer.encode('<CITED HERE>', add_special_tokens=False)
|
| 19 |
+
self.cited_here_tokens = torch.tensor(cited_ids, dtype=torch.long)
|
| 20 |
+
|
| 21 |
+
if class_definitions is not None:
|
| 22 |
+
self.class_definitions = []
|
| 23 |
+
self.class_head_indices = []
|
| 24 |
+
for i, defs in enumerate(class_definitions):
|
| 25 |
+
self.class_definitions += defs
|
| 26 |
+
self.class_head_indices.append(i * torch.ones(len(defs), dtype=torch.long))
|
| 27 |
+
self.class_head_indices = torch.cat(self.class_head_indices, dim=0)
|
| 28 |
+
self.class_tokens = self.tokenizer(
|
| 29 |
+
self.class_definitions,
|
| 30 |
+
return_tensors="pt",
|
| 31 |
+
max_length=512,
|
| 32 |
+
truncation=True,
|
| 33 |
+
padding=True
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
def _get_readout_mask(self, tokens):
|
| 37 |
+
# cited_here_tokens = torch.tensor([962, 8412, 1530, 1374])
|
| 38 |
+
readout_mask = torch.zeros_like(tokens['input_ids'], dtype=torch.bool)
|
| 39 |
+
|
| 40 |
+
batch_size = tokens['input_ids'].size(0)
|
| 41 |
+
l = tokens['input_ids'].size(1)
|
| 42 |
+
ctk_l = self.cited_here_tokens.size(0)
|
| 43 |
+
for b in range(batch_size):
|
| 44 |
+
for i in range(1, l - ctk_l):
|
| 45 |
+
if torch.equal(tokens['input_ids'][b, i:i+ctk_l], self.cited_here_tokens):
|
| 46 |
+
readout_mask[b, i:i+ctk_l] = True
|
| 47 |
+
if not readout_mask[b].any():
|
| 48 |
+
# Fallback to CLS if the citation marker isn't matched.
|
| 49 |
+
readout_mask[b, 0] = True
|
| 50 |
+
return readout_mask
|
| 51 |
+
|
| 52 |
+
def _tokenize_context(self, context):
|
| 53 |
+
tokens = self.tokenizer(
|
| 54 |
+
context,
|
| 55 |
+
return_tensors="pt",
|
| 56 |
+
max_length=512,
|
| 57 |
+
truncation=True,
|
| 58 |
+
padding=True
|
| 59 |
+
)
|
| 60 |
+
tokens['readout_mask'] = self._get_readout_mask(
|
| 61 |
+
tokens
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
return tokens
|
| 65 |
+
|
| 66 |
+
def __call__(self, samples):
|
| 67 |
+
if self.instance_weights:
|
| 68 |
+
text, labels, ds_indices, instance_weights = list(map(list, zip(*samples)))
|
| 69 |
+
batched_text = self._tokenize_context(text)
|
| 70 |
+
labels = torch.stack(labels)
|
| 71 |
+
ds_indices = torch.stack(ds_indices)
|
| 72 |
+
instance_weights = torch.stack(instance_weights)
|
| 73 |
+
return batched_text, labels, ds_indices, instance_weights
|
| 74 |
+
else:
|
| 75 |
+
text, labels, ds_indices = list(map(list, zip(*samples)))
|
| 76 |
+
batched_text = self._tokenize_context(text)
|
| 77 |
+
labels = torch.stack(labels)
|
| 78 |
+
ds_indices = torch.stack(ds_indices)
|
| 79 |
+
|
| 80 |
+
return batched_text, labels, ds_indices, copy.deepcopy(self.class_tokens), self.class_head_indices
|
| 81 |
+
|
| 82 |
+
class Dataset(object):
|
| 83 |
+
def __init__(self, dataframe, class_definitions, lmbd=1.0):
|
| 84 |
+
self.class_definitions = class_definitions
|
| 85 |
+
self.lmbd = lmbd
|
| 86 |
+
self._load_data(dataframe)
|
| 87 |
+
|
| 88 |
+
def __len__(self):
|
| 89 |
+
return len(self.labels)
|
| 90 |
+
|
| 91 |
+
def __getitem__(self, idx):
|
| 92 |
+
'''Get datapoint with index'''
|
| 93 |
+
return (self.text[idx], self.labels[idx], self.ds_index[idx])
|
| 94 |
+
|
| 95 |
+
def _load_data(self, annotated_data):
|
| 96 |
+
self.labels = torch.LongTensor(annotated_data['label'].tolist())
|
| 97 |
+
self.original_labels = torch.LongTensor(annotated_data['label'].tolist())
|
| 98 |
+
self.ds_index = torch.zeros_like(self.original_labels)
|
| 99 |
+
self.text = annotated_data['context'].tolist()
|
| 100 |
+
|
| 101 |
+
class MultiHeadDatasets(object):
|
| 102 |
+
def __init__(self, datasets, batch_size_factor=2):
|
| 103 |
+
self.text = []
|
| 104 |
+
self.ds_index = []
|
| 105 |
+
self.labels = []
|
| 106 |
+
self.class_definitions = []
|
| 107 |
+
self.lambdas = []
|
| 108 |
+
|
| 109 |
+
self.dataset_sizes = [len(d.labels) for d in datasets]
|
| 110 |
+
if len(self.dataset_sizes) > 1:
|
| 111 |
+
if sum(self.dataset_sizes) / self.dataset_sizes[0] <= batch_size_factor:
|
| 112 |
+
self.sample_auxiliary = False
|
| 113 |
+
self.adjusted_batch_size_factor = sum(self.dataset_sizes) / self.dataset_sizes[0]
|
| 114 |
+
else:
|
| 115 |
+
self.sample_auxiliary = True
|
| 116 |
+
self.sample_distribution = np.array([d.lmbd for d in datasets[1:]]) / sum([d.lmbd for d in datasets[1:]])
|
| 117 |
+
self.adjusted_batch_size_factor = batch_size_factor
|
| 118 |
+
else:
|
| 119 |
+
self.sample_auxiliary = False
|
| 120 |
+
self.adjusted_batch_size_factor = 1
|
| 121 |
+
|
| 122 |
+
for i, d in enumerate(datasets):
|
| 123 |
+
self.text += d.text
|
| 124 |
+
self.ds_index.append(i * torch.ones(len(d.text), dtype=torch.long))
|
| 125 |
+
self.labels.append(d.labels)
|
| 126 |
+
self.class_definitions.append(d.class_definitions)
|
| 127 |
+
self.lambdas.append(d.lmbd)
|
| 128 |
+
self.labels = torch.cat(self.labels, dim=0)
|
| 129 |
+
self.ds_index = torch.cat(self.ds_index, dim=0)
|
| 130 |
+
|
| 131 |
+
def sample_auxiliary_instace(self):
|
| 132 |
+
sampled_dataset_idx = np.random.choice(
|
| 133 |
+
np.arange(1, len(self.dataset_sizes)),
|
| 134 |
+
p=self.sample_distribution
|
| 135 |
+
)
|
| 136 |
+
instance_idx = np.random.choice(
|
| 137 |
+
self.dataset_sizes[sampled_dataset_idx]
|
| 138 |
+
) + sum(self.dataset_sizes[:sampled_dataset_idx])
|
| 139 |
+
return instance_idx
|
| 140 |
+
|
| 141 |
+
def __len__(self):
|
| 142 |
+
if self.sample_auxiliary: # if the auxiliary dataset is larger than the main dataset
|
| 143 |
+
return self.dataset_sizes[0] * self.adjusted_batch_size_factor
|
| 144 |
+
return len(self.labels)
|
| 145 |
+
|
| 146 |
+
def __getitem__(self, idx):
|
| 147 |
+
'''Get datapoint with index'''
|
| 148 |
+
if idx < self.dataset_sizes[0] or not self.sample_auxiliary:
|
| 149 |
+
return (self.text[idx], self.labels[idx], self.ds_index[idx])
|
| 150 |
+
else:
|
| 151 |
+
real_idx = self.sample_auxiliary_instace()
|
| 152 |
+
return (self.text[real_idx], self.labels[real_idx], self.ds_index[real_idx])
|
| 153 |
+
|
| 154 |
+
def load_class_definitions(filename):
|
| 155 |
+
with open(filename, 'r') as f:
|
| 156 |
+
class_definitions = json.load(f)
|
| 157 |
+
|
| 158 |
+
results = {k:{} for k in class_definitions.keys()}
|
| 159 |
+
for k, v in class_definitions.items():
|
| 160 |
+
for kk, vv in v.items():
|
| 161 |
+
results[k][kk.lower()] = vv
|
| 162 |
+
return results
|
| 163 |
+
|
| 164 |
+
def create_data_channels(filename, class_definition_filename, split=None, lmbd=1.0):
|
| 165 |
+
data = pd.read_csv(filename, sep='\t')
|
| 166 |
+
data = data.fillna(' ')
|
| 167 |
+
|
| 168 |
+
print('Number of data instance: {}'.format(data.shape[0]))
|
| 169 |
+
|
| 170 |
+
# map labels to ids
|
| 171 |
+
unique_labels = data['label'].unique().tolist()
|
| 172 |
+
label2id = {lb: i for i, lb in enumerate(unique_labels)}
|
| 173 |
+
|
| 174 |
+
data['label'] = data['label'].apply(
|
| 175 |
+
lambda x: label2id[x])
|
| 176 |
+
|
| 177 |
+
data_train = data[data['split'] == 'train'].reset_index()
|
| 178 |
+
data_val = data[data['split'] == 'val'].reset_index()
|
| 179 |
+
data_test = data[data['split'] == 'test'].reset_index()
|
| 180 |
+
|
| 181 |
+
class_definitions = load_class_definitions(class_definition_filename)
|
| 182 |
+
dataname = filename.split('/')[-1].split('.')[0]
|
| 183 |
+
data_class_definitions = [class_definitions[dataname][lb.lower()] for lb in unique_labels]
|
| 184 |
+
|
| 185 |
+
train_data = Dataset(data_train, data_class_definitions, lmbd=lmbd)
|
| 186 |
+
val_data = Dataset(data_val, data_class_definitions, lmbd=lmbd)
|
| 187 |
+
test_data = Dataset(data_test, data_class_definitions, lmbd=lmbd)
|
| 188 |
+
|
| 189 |
+
return train_data, val_data, test_data, unique_labels
|
| 190 |
+
|
| 191 |
+
def create_single_data_object(filename, class_definition_filename, split=None, lmbd=1.0):
|
| 192 |
+
data = pd.read_csv(filename, sep='\t')
|
| 193 |
+
data = data.fillna(' ')
|
| 194 |
+
|
| 195 |
+
print('Number of data instance: {}'.format(data.shape[0]))
|
| 196 |
+
|
| 197 |
+
# map labels to ids
|
| 198 |
+
unique_labels = data['label'].unique()
|
| 199 |
+
label2id = {lb: i for i, lb in enumerate(unique_labels)}
|
| 200 |
+
|
| 201 |
+
data['label'] = data['label'].apply(
|
| 202 |
+
lambda x: label2id[x])
|
| 203 |
+
|
| 204 |
+
class_definitions = load_class_definitions(class_definition_filename)
|
| 205 |
+
dataname = filename.split('/')[-1].split('.')[0]
|
| 206 |
+
data_class_definitions = [class_definitions[dataname][lb.lower()] for lb in unique_labels]
|
| 207 |
+
|
| 208 |
+
if split is None:
|
| 209 |
+
return Dataset(data, data_class_definitions, lmbd=lmbd), unique_labels
|
| 210 |
+
else:
|
| 211 |
+
return Dataset(data[data['split'] == split].reset_index(), data_class_definitions, lmbd=lmbd), unique_labels
|
Dockerfile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 4 |
+
PYTHONUNBUFFERED=1 \
|
| 5 |
+
PIP_NO_CACHE_DIR=1 \
|
| 6 |
+
STREAMLIT_SERVER_HEADLESS=true \
|
| 7 |
+
STREAMLIT_BROWSER_GATHER_USAGE_STATS=false \
|
| 8 |
+
PLAYWRIGHT_BROWSERS_PATH=/app/.playwright \
|
| 9 |
+
HOME=/tmp
|
| 10 |
+
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 14 |
+
git \
|
| 15 |
+
build-essential \
|
| 16 |
+
curl \
|
| 17 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 18 |
+
|
| 19 |
+
COPY requirements.txt /app/requirements.txt
|
| 20 |
+
COPY hf_space/requirements.txt /app/hf_space/requirements.txt
|
| 21 |
+
RUN python -m pip install --upgrade pip && \
|
| 22 |
+
pip install -r requirements.txt
|
| 23 |
+
|
| 24 |
+
# Chromium for SciPath Live Run (websearch_deep crawl tool).
|
| 25 |
+
RUN mkdir -p /app/.playwright && \
|
| 26 |
+
python -m playwright install --with-deps chromium
|
| 27 |
+
|
| 28 |
+
COPY . /app
|
| 29 |
+
|
| 30 |
+
# HF Space secrets (GEMINI_API_KEY, etc.) are injected as env vars at runtime.
|
| 31 |
+
EXPOSE 7860
|
| 32 |
+
|
| 33 |
+
CMD ["streamlit", "run", "hf_space/streamlit_app.py", \
|
| 34 |
+
"--server.address", "0.0.0.0", \
|
| 35 |
+
"--server.port", "7860", \
|
| 36 |
+
"--browser.gatherUsageStats", "false"]
|
README.md
CHANGED
|
@@ -1,10 +1,233 @@
|
|
| 1 |
---
|
| 2 |
title: SciPaths Demo
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: blue
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: SciPaths Demo
|
| 3 |
+
emoji: 🔬
|
| 4 |
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
app_port: 7860
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# SciPaths Demo
|
| 12 |
+
|
| 13 |
+
Self-contained demo of **SciPaths** (annotation process) and **SciFy CodeAgent** (system run / live run) on SciPaths claims.
|
| 14 |
+
|
| 15 |
+
The Hugging Face Space launches Streamlit from `hf_space/streamlit_app.py`. This folder is deployable on its own — it does not depend on sibling repos such as `dryrun/`.
|
| 16 |
+
|
| 17 |
+
## Citation
|
| 18 |
+
|
| 19 |
+
If you find this useful, please cite our paper as:
|
| 20 |
+
|
| 21 |
+
```bibtex
|
| 22 |
+
@misc{chamoun2026scipathsforecastingpathwaysscientific,
|
| 23 |
+
title={SciPaths: Forecasting Pathways to Scientific Discovery},
|
| 24 |
+
author={Eric Chamoun and Yizhou Chi and Yulong Chen and Rui Cao and Zifeng Ding and Michalis Korakakis and Andreas Vlachos},
|
| 25 |
+
year={2026},
|
| 26 |
+
eprint={2605.14600},
|
| 27 |
+
archivePrefix={arXiv},
|
| 28 |
+
primaryClass={cs.CL},
|
| 29 |
+
url={https://arxiv.org/abs/2605.14600},
|
| 30 |
+
}
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
Paper URL: https://arxiv.org/abs/2605.14600
|
| 34 |
+
|
| 35 |
+
## Required Secrets
|
| 36 |
+
|
| 37 |
+
Set this in the Space settings before publishing:
|
| 38 |
+
|
| 39 |
+
```text
|
| 40 |
+
GEMINI_API_KEY=<Google Gemini API key>
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
Optional, for saving completed run artifacts to a Hugging Face Dataset:
|
| 44 |
+
|
| 45 |
+
```text
|
| 46 |
+
HF_WRITE_TOKEN=<Hugging Face write token>
|
| 47 |
+
RUNS_REPO_ID=<owner/dataset-name>
|
| 48 |
+
RUNS_REPO_TYPE=dataset
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
Optional, for higher Semantic Scholar limits:
|
| 52 |
+
|
| 53 |
+
```text
|
| 54 |
+
SEMANTIC_SCHOLAR_API_KEY=<Semantic Scholar API key>
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
## Run The Demo Locally
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
pip install -r requirements.txt
|
| 61 |
+
streamlit run hf_space/streamlit_app.py
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Then enter an arXiv URL or ID, for example:
|
| 65 |
+
|
| 66 |
+
```text
|
| 67 |
+
https://arxiv.org/abs/2211.08788
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
The app writes each run under:
|
| 71 |
+
|
| 72 |
+
```text
|
| 73 |
+
hf_space/runs/<job_id>/
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
## Run One Example From The Command Line
|
| 77 |
+
|
| 78 |
+
This example stores all intermediate files under `runs/example/processed_papers`.
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
mkdir -p runs/example
|
| 82 |
+
printf '[{"id":"2211.08788","title":"","id_type":"ArXiv"}]\n' > runs/example/input_ids.json
|
| 83 |
+
|
| 84 |
+
python src/step_01_fetch/fetch_metadata.py \
|
| 85 |
+
--ids runs/example/input_ids.json \
|
| 86 |
+
--outdir runs/example/processed_papers
|
| 87 |
+
|
| 88 |
+
python src/step_02_mark_citations/replace_citation_markers.py \
|
| 89 |
+
--root runs/example/processed_papers
|
| 90 |
+
|
| 91 |
+
python src/step_03_usage_contexts/build_usage_contexts.py \
|
| 92 |
+
--root runs/example/processed_papers \
|
| 93 |
+
--out-name usage_contexts.json
|
| 94 |
+
|
| 95 |
+
python src/step_04_label_citations/label_citation_functions.py \
|
| 96 |
+
--root runs/example/processed_papers \
|
| 97 |
+
--model-path Deep-Citation/Workspace/acl_scicite_wksp_trl/best_model.pt \
|
| 98 |
+
--model-data-dir Deep-Citation/Data \
|
| 99 |
+
--model-class-def Deep-Citation/Data/class_def.json \
|
| 100 |
+
--model-lm scibert \
|
| 101 |
+
--device cpu
|
| 102 |
+
|
| 103 |
+
python src/step_05_verify_uses_extends/verify_uses_extends.py \
|
| 104 |
+
--root runs/example/processed_papers \
|
| 105 |
+
--k 0 \
|
| 106 |
+
--batch-size 25
|
| 107 |
+
|
| 108 |
+
python src/step_06_extract_paragraphs/extract_arxiv_paragraphs.py \
|
| 109 |
+
--root runs/example/processed_papers
|
| 110 |
+
|
| 111 |
+
python src/step_07_extract_and_refine/extract_contributions_from_citations.py \
|
| 112 |
+
--root runs/example/processed_papers
|
| 113 |
+
|
| 114 |
+
python src/step_07_extract_and_refine/refine_and_filter_clusters_llm.py \
|
| 115 |
+
--root runs/example/processed_papers \
|
| 116 |
+
--inplace \
|
| 117 |
+
--overwrite
|
| 118 |
+
|
| 119 |
+
PYTHONPATH=src \
|
| 120 |
+
python -m step_08_annotation.cli run \
|
| 121 |
+
--paper-dir runs/example/processed_papers/2211.08788 \
|
| 122 |
+
--provider gemini \
|
| 123 |
+
--model gemini/gemini-3.1-pro-preview \
|
| 124 |
+
--formatter-model gemini/gemini-3.1-pro-preview \
|
| 125 |
+
--judge-model gemini/gemini-3.1-pro-preview \
|
| 126 |
+
--candidate-count 3 \
|
| 127 |
+
--output-root runs/example/two_pass_outputs
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
The final UI payload is written as `pass_2_ui_payload.json` inside the annotation run directory printed by the last command.
|
| 131 |
+
|
| 132 |
+
## Run Each Step On A Set Of Papers
|
| 133 |
+
|
| 134 |
+
Create an ID file with one entry per paper:
|
| 135 |
+
|
| 136 |
+
```json
|
| 137 |
+
[
|
| 138 |
+
{"id": "2211.08788", "title": "", "id_type": "ArXiv"},
|
| 139 |
+
{"id": "2311.14919", "title": "", "id_type": "ArXiv"}
|
| 140 |
+
]
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
Save it as `runs/batch/input_ids.json`, then run:
|
| 144 |
+
|
| 145 |
+
```bash
|
| 146 |
+
mkdir -p runs/batch
|
| 147 |
+
|
| 148 |
+
# 1. Fetch metadata + LaTeX for each input paper.
|
| 149 |
+
python src/step_01_fetch/fetch_metadata.py \
|
| 150 |
+
--ids runs/batch/input_ids.json \
|
| 151 |
+
--outdir runs/batch/processed_papers
|
| 152 |
+
|
| 153 |
+
# 2. Add explicit citation markers to the target-paper text.
|
| 154 |
+
python src/step_02_mark_citations/replace_citation_markers.py \
|
| 155 |
+
--root runs/batch/processed_papers
|
| 156 |
+
|
| 157 |
+
# 3. Build downstream citation usage contexts.
|
| 158 |
+
python src/step_03_usage_contexts/build_usage_contexts.py \
|
| 159 |
+
--root runs/batch/processed_papers \
|
| 160 |
+
--out-name usage_contexts.json
|
| 161 |
+
|
| 162 |
+
# 4. Label citation functions with the bundled Deep-Citation classifier.
|
| 163 |
+
python src/step_04_label_citations/label_citation_functions.py \
|
| 164 |
+
--root runs/batch/processed_papers \
|
| 165 |
+
--model-path Deep-Citation/Workspace/acl_scicite_wksp_trl/best_model.pt \
|
| 166 |
+
--model-data-dir Deep-Citation/Data \
|
| 167 |
+
--model-class-def Deep-Citation/Data/class_def.json \
|
| 168 |
+
--model-lm scibert \
|
| 169 |
+
--device cpu
|
| 170 |
+
|
| 171 |
+
# 5. Verify USES/EXTENDS citations with an LLM.
|
| 172 |
+
python src/step_05_verify_uses_extends/verify_uses_extends.py \
|
| 173 |
+
--root runs/batch/processed_papers \
|
| 174 |
+
--k 0 \
|
| 175 |
+
--batch-size 25
|
| 176 |
+
|
| 177 |
+
# 6. Extract arXiv paragraphs from downstream citing papers.
|
| 178 |
+
python src/step_06_extract_paragraphs/extract_arxiv_paragraphs.py \
|
| 179 |
+
--root runs/batch/processed_papers
|
| 180 |
+
|
| 181 |
+
# 7. Extract downstream contribution clusters, then merge/filter them.
|
| 182 |
+
python src/step_07_extract_and_refine/extract_contributions_from_citations.py \
|
| 183 |
+
--root runs/batch/processed_papers
|
| 184 |
+
|
| 185 |
+
python src/step_07_extract_and_refine/refine_and_filter_clusters_llm.py \
|
| 186 |
+
--root runs/batch/processed_papers \
|
| 187 |
+
--inplace \
|
| 188 |
+
--overwrite
|
| 189 |
+
|
| 190 |
+
# 8. Annotate each ready paper: target contributions, enabling contributions, and groundings.
|
| 191 |
+
for paper_dir in runs/batch/processed_papers/*; do
|
| 192 |
+
[ -d "$paper_dir" ] || continue
|
| 193 |
+
[ -f "$paper_dir/usage_discovery_from_contributions.json" ] || continue
|
| 194 |
+
PYTHONPATH=src \
|
| 195 |
+
python -m step_08_annotation.cli run \
|
| 196 |
+
--paper-dir "$paper_dir" \
|
| 197 |
+
--provider gemini \
|
| 198 |
+
--model gemini/gemini-3.1-pro-preview \
|
| 199 |
+
--formatter-model gemini/gemini-3.1-pro-preview \
|
| 200 |
+
--judge-model gemini/gemini-3.1-pro-preview \
|
| 201 |
+
--candidate-count 3 \
|
| 202 |
+
--output-root runs/batch/two_pass_outputs
|
| 203 |
+
done
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
## Pipeline Steps
|
| 207 |
+
|
| 208 |
+
1. **Fetch metadata + LaTeX.** Downloads target-paper metadata, references, citing-paper metadata, and arXiv source where available.
|
| 209 |
+
2. **Add citation markers.** Inserts normalized citation markers into the target paper so downstream citation contexts can be aligned.
|
| 210 |
+
3. **Build usage contexts.** Collects text windows around downstream citations to the target paper.
|
| 211 |
+
4. **Label citation functions.** Uses the bundled Deep-Citation classifier to label citation contexts as background, use, extension, comparison, and related categories.
|
| 212 |
+
5. **Verify USES/EXTENDS.** Uses an LLM to check whether candidate downstream citations genuinely use or extend the target paper.
|
| 213 |
+
6. **Extract arXiv paragraphs.** Retrieves fuller paragraphs from citing papers so the system has enough context for contribution extraction.
|
| 214 |
+
7. **Extract and refine target-contribution clusters.** Extracts what downstream papers use the target paper for, clusters near-duplicates, and filters weak/non-usage evidence.
|
| 215 |
+
8. **Annotate pathways.** Derives target contributions from the refined clusters, decomposes each into enabling contributions, selects primary groundings, and records additional grounding studies.
|
| 216 |
+
|
| 217 |
+
## Important Files
|
| 218 |
+
|
| 219 |
+
```text
|
| 220 |
+
hf_space/streamlit_app.py Streamlit UI
|
| 221 |
+
hf_space/runner.py Orchestrates steps 1-7 for the UI
|
| 222 |
+
hf_space/streamlit_config.py Example papers and tab names
|
| 223 |
+
src/common/ Shared LLM and paper-package utilities
|
| 224 |
+
src/step_01_fetch/ Metadata, references, citations, and LaTeX
|
| 225 |
+
src/step_02_mark_citations/ Citation-marker insertion
|
| 226 |
+
src/step_03_usage_contexts/ Downstream usage-context construction
|
| 227 |
+
src/step_04_label_citations/ Deep-Citation citation-function labeling
|
| 228 |
+
src/step_05_verify_uses_extends/ LLM verification of USES/EXTENDS citations
|
| 229 |
+
src/step_06_extract_paragraphs/ ArXiv paragraph extraction from citing papers
|
| 230 |
+
src/step_07_extract_and_refine/ Contribution extraction and cluster refinement
|
| 231 |
+
src/step_08_annotation/ Target/enabling contribution annotation and grounding
|
| 232 |
+
Deep-Citation/ Bundled citation-function classifier assets
|
| 233 |
+
```
|
app.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from hf_space.streamlit_app import main
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
if __name__ == "__main__":
|
| 5 |
+
main()
|
docker-compose.neo4j.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Optional dedicated Neo4j if ports 7474/7687 are free.
|
| 2 |
+
# By default the app uses whatever answers bolt://localhost:7687
|
| 3 |
+
# (NEO4J_URI / NEO4J_USER / NEO4J_PASSWORD).
|
| 4 |
+
services:
|
| 5 |
+
neo4j:
|
| 6 |
+
image: neo4j:5.26-community
|
| 7 |
+
container_name: scipaths-neo4j
|
| 8 |
+
ports:
|
| 9 |
+
- "7475:7474"
|
| 10 |
+
- "7688:7687"
|
| 11 |
+
environment:
|
| 12 |
+
NEO4J_AUTH: neo4j/scipaths123
|
| 13 |
+
NEO4J_PLUGINS: '["apoc"]'
|
| 14 |
+
NEO4J_dbms_memory_heap_max__size: 1G
|
| 15 |
+
volumes:
|
| 16 |
+
- scipaths_neo4j_data:/data
|
| 17 |
+
|
| 18 |
+
volumes:
|
| 19 |
+
scipaths_neo4j_data:
|
hf_space/build_replay_traces.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Run the demo example papers end-to-end and save replay traces."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import shutil
|
| 9 |
+
import sys
|
| 10 |
+
import time
|
| 11 |
+
from datetime import datetime, timezone
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
SRC = Path(__file__).resolve().parent
|
| 15 |
+
REPO_ROOT = SRC.parent
|
| 16 |
+
for extra in (SRC, REPO_ROOT / "src"):
|
| 17 |
+
extra_str = str(extra)
|
| 18 |
+
if extra_str not in sys.path:
|
| 19 |
+
sys.path.insert(0, extra_str)
|
| 20 |
+
|
| 21 |
+
from dotenv import load_dotenv
|
| 22 |
+
|
| 23 |
+
load_dotenv(REPO_ROOT / ".env")
|
| 24 |
+
load_dotenv(REPO_ROOT.parent / "dryrun" / ".env", override=False)
|
| 25 |
+
|
| 26 |
+
# Align Gemini env aliases used across the repo.
|
| 27 |
+
if not os.getenv("GEMINI_API_KEY"):
|
| 28 |
+
for alt in ("GOOGLE_GENAI_API_KEY", "GOOGLE_API_KEY"):
|
| 29 |
+
if os.getenv(alt):
|
| 30 |
+
os.environ["GEMINI_API_KEY"] = os.environ[alt]
|
| 31 |
+
break
|
| 32 |
+
|
| 33 |
+
import runner as runner_module
|
| 34 |
+
from runner import PipelineConfig
|
| 35 |
+
from common.paper_package import load_paper_package
|
| 36 |
+
from step_08_annotation.pipeline import TwoPassAnnotationPipeline
|
| 37 |
+
from streamlit_config import EXAMPLES
|
| 38 |
+
|
| 39 |
+
REPLAY_ROOT = REPO_ROOT / "replay_traces"
|
| 40 |
+
WORK_ROOT = REPO_ROOT / "hf_space" / "runs" / "replay_build"
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _env(name: str, default: str) -> str:
|
| 44 |
+
return (os.getenv(name) or default).strip()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _load_json(path: Path):
|
| 48 |
+
if not path.exists():
|
| 49 |
+
return None
|
| 50 |
+
try:
|
| 51 |
+
return json.loads(path.read_text(encoding="utf-8"))
|
| 52 |
+
except Exception:
|
| 53 |
+
return None
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _copy_if_exists(src: Path, dst: Path) -> bool:
|
| 57 |
+
if not src.exists():
|
| 58 |
+
return False
|
| 59 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 60 |
+
if src.is_dir():
|
| 61 |
+
if dst.exists():
|
| 62 |
+
shutil.rmtree(dst)
|
| 63 |
+
shutil.copytree(src, dst)
|
| 64 |
+
else:
|
| 65 |
+
shutil.copy2(src, dst)
|
| 66 |
+
return True
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _run_annotation(paper_dir: Path, annotation_root: Path) -> tuple[dict | None, Path | None, str | None]:
|
| 70 |
+
discovery = _load_json(paper_dir / "usage_discovery_from_contributions.json") or {}
|
| 71 |
+
clusters = discovery.get("clusters") or []
|
| 72 |
+
if not clusters:
|
| 73 |
+
return None, None, "No refined downstream usage clusters; annotation skipped."
|
| 74 |
+
|
| 75 |
+
llm_provider = _env("LLM_PROVIDER", "gemini")
|
| 76 |
+
llm_model = _env("LLM_MODEL", "gemini-3.1-pro-preview")
|
| 77 |
+
formatter_model = _env("ANNOTATION_FORMATTER_MODEL", "gemini/gemini-3.1-pro-preview")
|
| 78 |
+
judge_model = _env("ANNOTATION_JUDGE_MODEL", "gemini/gemini-3.1-pro-preview")
|
| 79 |
+
candidate_count = int(_env("ANNOTATION_CANDIDATE_COUNT", "3"))
|
| 80 |
+
|
| 81 |
+
paper = load_paper_package(paper_dir)
|
| 82 |
+
pipeline = TwoPassAnnotationPipeline(
|
| 83 |
+
provider=llm_provider,
|
| 84 |
+
model=llm_model,
|
| 85 |
+
formatter_model=formatter_model or None,
|
| 86 |
+
judge_model=judge_model or None,
|
| 87 |
+
output_root=annotation_root,
|
| 88 |
+
annotator_id="replay_trace_builder",
|
| 89 |
+
candidate_count=max(1, candidate_count),
|
| 90 |
+
formatter_max_attempts=3,
|
| 91 |
+
include_reference_examples=True,
|
| 92 |
+
prompt_profile="full",
|
| 93 |
+
)
|
| 94 |
+
result = pipeline.run(paper)
|
| 95 |
+
return result.result, result.run_dir, None
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _package_trace(
|
| 99 |
+
*,
|
| 100 |
+
label: str,
|
| 101 |
+
arxiv_id: str,
|
| 102 |
+
paper_input: str,
|
| 103 |
+
job_dir: Path,
|
| 104 |
+
paper_dir: Path,
|
| 105 |
+
events: list[str],
|
| 106 |
+
status: str,
|
| 107 |
+
annotation_run_dir: Path | None,
|
| 108 |
+
annotation_skipped_reason: str | None,
|
| 109 |
+
pipeline_failed_reason: str | None,
|
| 110 |
+
pipeline_stopped_reason: str | None,
|
| 111 |
+
) -> Path:
|
| 112 |
+
out_dir = REPLAY_ROOT / arxiv_id
|
| 113 |
+
if out_dir.exists():
|
| 114 |
+
shutil.rmtree(out_dir)
|
| 115 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 116 |
+
|
| 117 |
+
paper_out = out_dir / "processed_papers" / arxiv_id
|
| 118 |
+
paper_out.mkdir(parents=True, exist_ok=True)
|
| 119 |
+
|
| 120 |
+
# Core replay payloads (keep disk footprint manageable).
|
| 121 |
+
keep_files = [
|
| 122 |
+
"paper_metadata.json",
|
| 123 |
+
"usage_contexts.json",
|
| 124 |
+
"usage_context_labels.json",
|
| 125 |
+
"usage_uses_extends_verified.json",
|
| 126 |
+
"usage_citing_paragraphs.json",
|
| 127 |
+
"usage_contributions.json",
|
| 128 |
+
"usage_discovery_from_contributions.json",
|
| 129 |
+
]
|
| 130 |
+
for name in keep_files:
|
| 131 |
+
_copy_if_exists(paper_dir / name, paper_out / name)
|
| 132 |
+
|
| 133 |
+
_copy_if_exists(job_dir / "logs", out_dir / "logs")
|
| 134 |
+
_copy_if_exists(job_dir / "summary.txt", out_dir / "summary.txt")
|
| 135 |
+
_copy_if_exists(job_dir / "run_config.json", out_dir / "run_config.json")
|
| 136 |
+
_copy_if_exists(job_dir / "input_ids.json", out_dir / "input_ids.json")
|
| 137 |
+
|
| 138 |
+
annotation_payload_path = None
|
| 139 |
+
if annotation_run_dir and annotation_run_dir.exists():
|
| 140 |
+
ann_dst = out_dir / "two_pass_outputs" / annotation_run_dir.name
|
| 141 |
+
_copy_if_exists(annotation_run_dir, ann_dst)
|
| 142 |
+
payload = ann_dst / "pass_2_ui_payload.json"
|
| 143 |
+
if payload.exists():
|
| 144 |
+
annotation_payload_path = str(payload.relative_to(out_dir))
|
| 145 |
+
|
| 146 |
+
discovery = _load_json(paper_out / "usage_discovery_from_contributions.json") or {}
|
| 147 |
+
contributions = _load_json(paper_out / "usage_contributions.json") or {}
|
| 148 |
+
payload = None
|
| 149 |
+
if annotation_payload_path:
|
| 150 |
+
payload = _load_json(out_dir / annotation_payload_path)
|
| 151 |
+
|
| 152 |
+
public_export = {
|
| 153 |
+
"citation_clusters": (discovery or {}).get("clusters") or [],
|
| 154 |
+
"target_contribution_decompositions": (payload or {}).get("claims") or [],
|
| 155 |
+
}
|
| 156 |
+
(out_dir / "scipaths_run_results.json").write_text(
|
| 157 |
+
json.dumps(public_export, indent=2, ensure_ascii=False),
|
| 158 |
+
encoding="utf-8",
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
meta = {
|
| 162 |
+
"label": label,
|
| 163 |
+
"arxiv_id": arxiv_id,
|
| 164 |
+
"paper_input": paper_input,
|
| 165 |
+
"status": status,
|
| 166 |
+
"built_at": datetime.now(timezone.utc).isoformat(),
|
| 167 |
+
"source_job_dir": str(job_dir),
|
| 168 |
+
"paper_dir": str((out_dir / "processed_papers" / arxiv_id).relative_to(out_dir)),
|
| 169 |
+
"annotation_payload_path": annotation_payload_path,
|
| 170 |
+
"annotation_skipped_reason": annotation_skipped_reason,
|
| 171 |
+
"pipeline_failed_reason": pipeline_failed_reason,
|
| 172 |
+
"pipeline_stopped_reason": pipeline_stopped_reason,
|
| 173 |
+
"events": events,
|
| 174 |
+
"cluster_count": len((discovery or {}).get("clusters") or []),
|
| 175 |
+
"contribution_count": len((contributions or {}).get("contributions") or []),
|
| 176 |
+
"claim_count": len((payload or {}).get("claims") or []) if isinstance(payload, dict) else 0,
|
| 177 |
+
}
|
| 178 |
+
(out_dir / "replay_meta.json").write_text(json.dumps(meta, indent=2, ensure_ascii=False), encoding="utf-8")
|
| 179 |
+
return out_dir
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def run_one(label: str, paper_input: str) -> dict:
|
| 183 |
+
if not os.getenv("GEMINI_API_KEY"):
|
| 184 |
+
raise SystemExit("GEMINI_API_KEY is required to build replay traces.")
|
| 185 |
+
|
| 186 |
+
arxiv_id = runner_module.parse_arxiv_id(paper_input)
|
| 187 |
+
print(f"\n=== Building replay trace for {label} ({arxiv_id}) ===", flush=True)
|
| 188 |
+
|
| 189 |
+
cfg = PipelineConfig(
|
| 190 |
+
repo_root=REPO_ROOT,
|
| 191 |
+
source_root=REPO_ROOT / "src" / "processed_papers",
|
| 192 |
+
paper_input=paper_input,
|
| 193 |
+
llm_provider=_env("LLM_PROVIDER", "gemini"),
|
| 194 |
+
llm_model=_env("LLM_MODEL", "gemini-3.1-pro-preview"),
|
| 195 |
+
llm_model_step4=_env("LLM_MODEL_STEP4", "gemini-3-flash-preview"),
|
| 196 |
+
model_path="Deep-Citation/Workspace/acl_scicite_wksp_trl/best_model.pt",
|
| 197 |
+
model_data_dir="Deep-Citation/Data",
|
| 198 |
+
model_class_def="Deep-Citation/Data/class_def.json",
|
| 199 |
+
model_lm="scibert",
|
| 200 |
+
device="cpu",
|
| 201 |
+
embedding_model="sentence-transformers/all-mpnet-base-v2",
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
events: list[str] = []
|
| 205 |
+
artifact_path = None
|
| 206 |
+
pipeline_failed_reason = None
|
| 207 |
+
pipeline_stopped_reason = None
|
| 208 |
+
t0 = time.time()
|
| 209 |
+
|
| 210 |
+
for line, maybe_artifact in runner_module.run_pipeline(cfg, WORK_ROOT):
|
| 211 |
+
if line:
|
| 212 |
+
print(line, flush=True)
|
| 213 |
+
events.append(line)
|
| 214 |
+
if line.startswith("Pipeline stopped:"):
|
| 215 |
+
pipeline_stopped_reason = line
|
| 216 |
+
if "failed" in line.lower():
|
| 217 |
+
pipeline_failed_reason = line
|
| 218 |
+
if maybe_artifact:
|
| 219 |
+
artifact_path = maybe_artifact
|
| 220 |
+
|
| 221 |
+
if not artifact_path:
|
| 222 |
+
raise RuntimeError(f"Pipeline produced no artifact for {arxiv_id}")
|
| 223 |
+
|
| 224 |
+
job_dir = Path(str(artifact_path)).with_suffix("")
|
| 225 |
+
paper_dir = job_dir / "processed_papers" / arxiv_id
|
| 226 |
+
annotation_run_dir = None
|
| 227 |
+
annotation_skipped_reason = None
|
| 228 |
+
|
| 229 |
+
if pipeline_failed_reason:
|
| 230 |
+
status = "Failed"
|
| 231 |
+
annotation_skipped_reason = f"{pipeline_failed_reason} Annotation was not run."
|
| 232 |
+
elif pipeline_stopped_reason:
|
| 233 |
+
status = "Stopped"
|
| 234 |
+
annotation_skipped_reason = f"{pipeline_stopped_reason} Annotation was not run."
|
| 235 |
+
else:
|
| 236 |
+
print("[annotation] starting", flush=True)
|
| 237 |
+
try:
|
| 238 |
+
_run_output, annotation_run_dir, skip = _run_annotation(
|
| 239 |
+
paper_dir=paper_dir,
|
| 240 |
+
annotation_root=job_dir / "two_pass_outputs",
|
| 241 |
+
)
|
| 242 |
+
if skip:
|
| 243 |
+
annotation_skipped_reason = skip
|
| 244 |
+
status = "Completed"
|
| 245 |
+
print(f"[annotation] skipped: {skip}", flush=True)
|
| 246 |
+
else:
|
| 247 |
+
status = "Completed"
|
| 248 |
+
events.append(f"[annotation] complete: {annotation_run_dir}")
|
| 249 |
+
print(f"[annotation] complete: {annotation_run_dir}", flush=True)
|
| 250 |
+
except Exception as exc:
|
| 251 |
+
status = "Failed"
|
| 252 |
+
pipeline_failed_reason = f"Annotation failed: {exc}"
|
| 253 |
+
annotation_skipped_reason = pipeline_failed_reason
|
| 254 |
+
events.append(pipeline_failed_reason)
|
| 255 |
+
print(pipeline_failed_reason, flush=True)
|
| 256 |
+
|
| 257 |
+
out_dir = _package_trace(
|
| 258 |
+
label=label,
|
| 259 |
+
arxiv_id=arxiv_id,
|
| 260 |
+
paper_input=paper_input,
|
| 261 |
+
job_dir=job_dir,
|
| 262 |
+
paper_dir=paper_dir,
|
| 263 |
+
events=events,
|
| 264 |
+
status=status,
|
| 265 |
+
annotation_run_dir=annotation_run_dir,
|
| 266 |
+
annotation_skipped_reason=annotation_skipped_reason,
|
| 267 |
+
pipeline_failed_reason=pipeline_failed_reason,
|
| 268 |
+
pipeline_stopped_reason=pipeline_stopped_reason,
|
| 269 |
+
)
|
| 270 |
+
elapsed = time.time() - t0
|
| 271 |
+
print(f"Saved replay trace -> {out_dir} ({status}, {elapsed/60:.1f} min)", flush=True)
|
| 272 |
+
return {
|
| 273 |
+
"label": label,
|
| 274 |
+
"arxiv_id": arxiv_id,
|
| 275 |
+
"status": status,
|
| 276 |
+
"trace_dir": str(out_dir),
|
| 277 |
+
"elapsed_sec": elapsed,
|
| 278 |
+
"annotation_skipped_reason": annotation_skipped_reason,
|
| 279 |
+
"pipeline_failed_reason": pipeline_failed_reason,
|
| 280 |
+
"pipeline_stopped_reason": pipeline_stopped_reason,
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def main() -> int:
|
| 285 |
+
REPLAY_ROOT.mkdir(parents=True, exist_ok=True)
|
| 286 |
+
WORK_ROOT.mkdir(parents=True, exist_ok=True)
|
| 287 |
+
|
| 288 |
+
results = []
|
| 289 |
+
for label, paper_input in EXAMPLES.items():
|
| 290 |
+
results.append(run_one(label, paper_input))
|
| 291 |
+
|
| 292 |
+
index = {
|
| 293 |
+
"built_at": datetime.now(timezone.utc).isoformat(),
|
| 294 |
+
"examples": results,
|
| 295 |
+
}
|
| 296 |
+
index_path = REPLAY_ROOT / "index.json"
|
| 297 |
+
index_path.write_text(json.dumps(index, indent=2, ensure_ascii=False), encoding="utf-8")
|
| 298 |
+
print(f"\nWrote index -> {index_path}", flush=True)
|
| 299 |
+
for item in results:
|
| 300 |
+
print(f"- {item['arxiv_id']}: {item['status']} -> {item['trace_dir']}", flush=True)
|
| 301 |
+
return 0 if all(item["status"] == "Completed" for item in results) else 1
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
if __name__ == "__main__":
|
| 305 |
+
raise SystemExit(main())
|
hf_space/neo4j_workflow.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Neo4j sync for SciPaths workflow graph animation."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from typing import Any, Optional
|
| 7 |
+
|
| 8 |
+
_DRIVER = None
|
| 9 |
+
|
| 10 |
+
NEO4J_URI = os.getenv("NEO4J_URI", "bolt://localhost:7687")
|
| 11 |
+
NEO4J_USER = os.getenv("NEO4J_USER", "neo4j")
|
| 12 |
+
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD", "graphscout123")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def neo4j_available() -> bool:
|
| 16 |
+
try:
|
| 17 |
+
driver = get_driver()
|
| 18 |
+
if driver is None:
|
| 19 |
+
return False
|
| 20 |
+
driver.verify_connectivity()
|
| 21 |
+
return True
|
| 22 |
+
except Exception:
|
| 23 |
+
return False
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def get_driver():
|
| 27 |
+
global _DRIVER
|
| 28 |
+
if _DRIVER is not None:
|
| 29 |
+
return _DRIVER
|
| 30 |
+
try:
|
| 31 |
+
from neo4j import GraphDatabase
|
| 32 |
+
except ImportError:
|
| 33 |
+
return None
|
| 34 |
+
try:
|
| 35 |
+
_DRIVER = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD))
|
| 36 |
+
_DRIVER.verify_connectivity()
|
| 37 |
+
return _DRIVER
|
| 38 |
+
except Exception:
|
| 39 |
+
_DRIVER = None
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def reset_run(run_id: str) -> bool:
|
| 44 |
+
driver = get_driver()
|
| 45 |
+
if driver is None or not run_id:
|
| 46 |
+
return False
|
| 47 |
+
try:
|
| 48 |
+
with driver.session() as session:
|
| 49 |
+
session.run(
|
| 50 |
+
"MATCH (n:SciPathsNode {run_id: $run_id}) DETACH DELETE n",
|
| 51 |
+
run_id=run_id,
|
| 52 |
+
)
|
| 53 |
+
return True
|
| 54 |
+
except Exception:
|
| 55 |
+
return False
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def upsert_graph(run_id: str, nodes: list[dict[str, Any]], edges: list[dict[str, Any]]) -> bool:
|
| 59 |
+
"""Replace the run's graph snapshot with the current cumulative nodes/edges."""
|
| 60 |
+
driver = get_driver()
|
| 61 |
+
if driver is None or not run_id:
|
| 62 |
+
return False
|
| 63 |
+
try:
|
| 64 |
+
with driver.session() as session:
|
| 65 |
+
session.execute_write(_upsert_tx, run_id, nodes, edges)
|
| 66 |
+
return True
|
| 67 |
+
except Exception:
|
| 68 |
+
return False
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _upsert_tx(tx, run_id: str, nodes: list[dict[str, Any]], edges: list[dict[str, Any]]) -> None:
|
| 72 |
+
tx.run("MATCH (n:SciPathsNode {run_id: $run_id}) DETACH DELETE n", run_id=run_id)
|
| 73 |
+
for node in nodes:
|
| 74 |
+
tx.run(
|
| 75 |
+
"""
|
| 76 |
+
CREATE (n:SciPathsNode {
|
| 77 |
+
run_id: $run_id,
|
| 78 |
+
id: $id,
|
| 79 |
+
kind: $kind,
|
| 80 |
+
label: $label,
|
| 81 |
+
title: $title,
|
| 82 |
+
detail: $detail,
|
| 83 |
+
step_added: $step_added,
|
| 84 |
+
pulse: $pulse,
|
| 85 |
+
color: $color,
|
| 86 |
+
size: $size
|
| 87 |
+
})
|
| 88 |
+
""",
|
| 89 |
+
run_id=run_id,
|
| 90 |
+
id=str(node.get("id") or ""),
|
| 91 |
+
kind=str(node.get("kind") or "node"),
|
| 92 |
+
label=str(node.get("label") or ""),
|
| 93 |
+
title=str(node.get("title") or node.get("label") or ""),
|
| 94 |
+
detail=str(node.get("detail") or ""),
|
| 95 |
+
step_added=int(node.get("step_added") or 0),
|
| 96 |
+
pulse=bool(node.get("pulse")),
|
| 97 |
+
color=str(node.get("color") or "#6a7a74"),
|
| 98 |
+
size=int(node.get("size") or 16),
|
| 99 |
+
)
|
| 100 |
+
for edge in edges:
|
| 101 |
+
tx.run(
|
| 102 |
+
"""
|
| 103 |
+
MATCH (a:SciPathsNode {run_id: $run_id, id: $source})
|
| 104 |
+
MATCH (b:SciPathsNode {run_id: $run_id, id: $target})
|
| 105 |
+
CREATE (a)-[:SCI_REL {
|
| 106 |
+
run_id: $run_id,
|
| 107 |
+
id: $id,
|
| 108 |
+
kind: $kind,
|
| 109 |
+
pulse: $pulse,
|
| 110 |
+
muted: $muted
|
| 111 |
+
}]->(b)
|
| 112 |
+
""",
|
| 113 |
+
run_id=run_id,
|
| 114 |
+
source=str(edge.get("source") or ""),
|
| 115 |
+
target=str(edge.get("target") or ""),
|
| 116 |
+
id=str(edge.get("id") or ""),
|
| 117 |
+
kind=str(edge.get("kind") or "rel"),
|
| 118 |
+
pulse=bool(edge.get("pulse")),
|
| 119 |
+
muted=bool(edge.get("muted")),
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def fetch_graph(run_id: str) -> Optional[dict[str, Any]]:
|
| 124 |
+
driver = get_driver()
|
| 125 |
+
if driver is None or not run_id:
|
| 126 |
+
return None
|
| 127 |
+
try:
|
| 128 |
+
with driver.session() as session:
|
| 129 |
+
nodes = session.run(
|
| 130 |
+
"""
|
| 131 |
+
MATCH (n:SciPathsNode {run_id: $run_id})
|
| 132 |
+
RETURN n.id AS id, n.kind AS kind, n.label AS label, n.title AS title,
|
| 133 |
+
n.detail AS detail, n.step_added AS step_added, n.pulse AS pulse,
|
| 134 |
+
n.color AS color, n.size AS size
|
| 135 |
+
""",
|
| 136 |
+
run_id=run_id,
|
| 137 |
+
).data()
|
| 138 |
+
edges = session.run(
|
| 139 |
+
"""
|
| 140 |
+
MATCH (a:SciPathsNode {run_id: $run_id})-[r:SCI_REL]->(b:SciPathsNode {run_id: $run_id})
|
| 141 |
+
RETURN r.id AS id, a.id AS source, b.id AS target, r.kind AS kind,
|
| 142 |
+
r.pulse AS pulse, r.muted AS muted
|
| 143 |
+
""",
|
| 144 |
+
run_id=run_id,
|
| 145 |
+
).data()
|
| 146 |
+
if not nodes:
|
| 147 |
+
return None
|
| 148 |
+
return {"nodes": nodes, "edges": edges}
|
| 149 |
+
except Exception:
|
| 150 |
+
return None
|
hf_space/replay.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Load precomputed demo traces for Streamlit replay mode."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import re
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any, Optional
|
| 9 |
+
|
| 10 |
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
| 11 |
+
REPLAY_ROOT = REPO_ROOT / "replay_traces"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _load_json(path: Path) -> Any | None:
|
| 15 |
+
if not path.exists():
|
| 16 |
+
return None
|
| 17 |
+
try:
|
| 18 |
+
return json.loads(path.read_text(encoding="utf-8"))
|
| 19 |
+
except Exception:
|
| 20 |
+
return None
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def parse_arxiv_id(paper_input: str) -> str | None:
|
| 24 |
+
s = (paper_input or "").strip()
|
| 25 |
+
if not s:
|
| 26 |
+
return None
|
| 27 |
+
if "arxiv.org" in s:
|
| 28 |
+
m = re.search(r"arxiv\.org/(abs|pdf)/([^/?#]+)", s)
|
| 29 |
+
if not m:
|
| 30 |
+
return None
|
| 31 |
+
s = m.group(2)
|
| 32 |
+
s = s.replace(".pdf", "")
|
| 33 |
+
s = re.sub(r"v\d+$", "", s)
|
| 34 |
+
if not re.match(r"^[0-9]{4}\.[0-9]{4,5}$", s):
|
| 35 |
+
return None
|
| 36 |
+
return s
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def load_index() -> dict:
|
| 40 |
+
data = _load_json(REPLAY_ROOT / "index.json")
|
| 41 |
+
return data if isinstance(data, dict) else {"examples": []}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def list_replay_examples() -> list[dict]:
|
| 45 |
+
examples = load_index().get("examples") or []
|
| 46 |
+
out = []
|
| 47 |
+
for item in examples:
|
| 48 |
+
if not isinstance(item, dict):
|
| 49 |
+
continue
|
| 50 |
+
arxiv_id = item.get("arxiv_id")
|
| 51 |
+
if not arxiv_id:
|
| 52 |
+
continue
|
| 53 |
+
meta_path = REPLAY_ROOT / str(arxiv_id) / "replay_meta.json"
|
| 54 |
+
if not meta_path.exists():
|
| 55 |
+
continue
|
| 56 |
+
out.append(item)
|
| 57 |
+
return out
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def find_trace_dir(paper_input: str) -> Optional[Path]:
|
| 61 |
+
arxiv_id = parse_arxiv_id(paper_input)
|
| 62 |
+
if not arxiv_id:
|
| 63 |
+
return None
|
| 64 |
+
trace_dir = REPLAY_ROOT / arxiv_id
|
| 65 |
+
if (trace_dir / "replay_meta.json").exists():
|
| 66 |
+
return trace_dir
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def load_trace(paper_input: str) -> Optional[dict]:
|
| 71 |
+
"""Return a session-ready payload for a saved demo trace, or None."""
|
| 72 |
+
trace_dir = find_trace_dir(paper_input)
|
| 73 |
+
if trace_dir is None:
|
| 74 |
+
return None
|
| 75 |
+
|
| 76 |
+
meta = _load_json(trace_dir / "replay_meta.json")
|
| 77 |
+
if not isinstance(meta, dict):
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
paper_rel = meta.get("paper_dir") or f"processed_papers/{meta.get('arxiv_id')}"
|
| 81 |
+
paper_dir = (trace_dir / paper_rel).resolve()
|
| 82 |
+
if not paper_dir.exists():
|
| 83 |
+
return None
|
| 84 |
+
|
| 85 |
+
payload_rel = meta.get("annotation_payload_path")
|
| 86 |
+
payload_path = (trace_dir / payload_rel).resolve() if payload_rel else None
|
| 87 |
+
if payload_path and not payload_path.exists():
|
| 88 |
+
payload_path = None
|
| 89 |
+
|
| 90 |
+
events = []
|
| 91 |
+
for line in meta.get("events") or []:
|
| 92 |
+
text = str(line).strip()
|
| 93 |
+
if not text:
|
| 94 |
+
continue
|
| 95 |
+
# Normalize absolute annotation paths for display.
|
| 96 |
+
if text.startswith("[annotation] complete:"):
|
| 97 |
+
events.append("Step 8 complete")
|
| 98 |
+
events.append("Pipeline completed successfully.")
|
| 99 |
+
continue
|
| 100 |
+
if text == "Pipeline completed successfully.":
|
| 101 |
+
# Keep a single completion marker after annotation normalization.
|
| 102 |
+
if events and events[-1] == "Pipeline completed successfully.":
|
| 103 |
+
continue
|
| 104 |
+
events.append(text)
|
| 105 |
+
|
| 106 |
+
# Ensure a readable end marker for completed traces.
|
| 107 |
+
status = str(meta.get("status") or "Completed")
|
| 108 |
+
if status == "Completed" and "Pipeline completed successfully." not in events:
|
| 109 |
+
events.append("Pipeline completed successfully.")
|
| 110 |
+
|
| 111 |
+
return {
|
| 112 |
+
"label": meta.get("label") or meta.get("arxiv_id"),
|
| 113 |
+
"arxiv_id": meta.get("arxiv_id"),
|
| 114 |
+
"paper_input": meta.get("paper_input") or f"https://arxiv.org/abs/{meta.get('arxiv_id')}",
|
| 115 |
+
"status": status,
|
| 116 |
+
"run_status": status,
|
| 117 |
+
"run_events": events,
|
| 118 |
+
"run_logs": list(events),
|
| 119 |
+
"run_dir_path": str(trace_dir),
|
| 120 |
+
"paper_dir_path": str(paper_dir),
|
| 121 |
+
"annotation_payload_path": str(payload_path) if payload_path else None,
|
| 122 |
+
"annotation_skipped_reason": meta.get("annotation_skipped_reason"),
|
| 123 |
+
"pipeline_failed_reason": meta.get("pipeline_failed_reason"),
|
| 124 |
+
"pipeline_stopped_reason": meta.get("pipeline_stopped_reason"),
|
| 125 |
+
"artifact_path": str(trace_dir / "summary.txt") if (trace_dir / "summary.txt").exists() else None,
|
| 126 |
+
"remote_artifact_ref": "",
|
| 127 |
+
"replay_mode": True,
|
| 128 |
+
"cluster_count": meta.get("cluster_count"),
|
| 129 |
+
"claim_count": meta.get("claim_count"),
|
| 130 |
+
}
|
hf_space/requirements.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.36.0
|
| 2 |
+
arxiv==2.2.0
|
| 3 |
+
requests==2.32.5
|
| 4 |
+
google-generativeai
|
| 5 |
+
litellm
|
| 6 |
+
rapidfuzz
|
| 7 |
+
bibtexparser
|
| 8 |
+
sentence-transformers
|
| 9 |
+
# SciPath Live Run (parametric / websearch_deep CodeAgent)
|
| 10 |
+
smolagents
|
| 11 |
+
ddgs
|
| 12 |
+
playwright
|
| 13 |
+
PyPDF2
|
| 14 |
+
python-dotenv
|
| 15 |
+
openai
|
| 16 |
+
# Pin stack for Deep-Citation step 4 on macOS x86_64:
|
| 17 |
+
# torch 2.2.x needs numpy<2; newer torch wheels are unavailable on this platform,
|
| 18 |
+
# and transformers 5.x refuses torch<2.4.
|
| 19 |
+
transformers==4.46.3
|
| 20 |
+
torch==2.2.2
|
| 21 |
+
huggingface_hub>=0.23,<1.0
|
| 22 |
+
typer
|
| 23 |
+
tqdm
|
| 24 |
+
pydantic
|
| 25 |
+
numpy==1.26.4
|
| 26 |
+
pandas
|
| 27 |
+
scipy==1.11.4
|
| 28 |
+
scikit-learn==1.4.2
|
hf_space/runner.py
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
import shutil
|
| 5 |
+
import subprocess
|
| 6 |
+
import sys
|
| 7 |
+
import time
|
| 8 |
+
import uuid
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Generator, List, Optional, Tuple
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class PipelineConfig:
|
| 16 |
+
repo_root: Path
|
| 17 |
+
source_root: Path
|
| 18 |
+
paper_input: str
|
| 19 |
+
llm_provider: str
|
| 20 |
+
llm_model: str
|
| 21 |
+
llm_model_step4: str
|
| 22 |
+
model_path: str
|
| 23 |
+
model_data_dir: str
|
| 24 |
+
model_class_def: str
|
| 25 |
+
model_lm: str
|
| 26 |
+
device: str
|
| 27 |
+
embedding_model: str
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass
|
| 31 |
+
class PipelineResult:
|
| 32 |
+
job_id: str
|
| 33 |
+
job_dir: Path
|
| 34 |
+
paper_dir: Path
|
| 35 |
+
zip_path: Path
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
STEP_LABELS = {
|
| 39 |
+
1: "Fetch metadata + LaTeX for input paper",
|
| 40 |
+
2: "Add citation markers",
|
| 41 |
+
3: "Build usage contexts",
|
| 42 |
+
4: "Label citation functions",
|
| 43 |
+
5: "Verify USES/EXTENDS",
|
| 44 |
+
6: "Extract arXiv paragraphs",
|
| 45 |
+
7: "Extract target contributions and refine clusters",
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
FULL_STEPS = [1, 2, 3, 4, 5, 6, 7]
|
| 49 |
+
STOP_PREFIX = "Pipeline stopped:"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def parse_arxiv_id(paper_input: str) -> str:
|
| 53 |
+
s = (paper_input or "").strip()
|
| 54 |
+
if not s:
|
| 55 |
+
raise ValueError("paper_input is required")
|
| 56 |
+
if "arxiv.org" in s:
|
| 57 |
+
m = re.search(r"arxiv\.org/(abs|pdf)/([^/?#]+)", s)
|
| 58 |
+
if not m:
|
| 59 |
+
raise ValueError(f"Could not parse arXiv ID from URL: {s}")
|
| 60 |
+
s = m.group(2)
|
| 61 |
+
s = s.replace(".pdf", "")
|
| 62 |
+
s = re.sub(r"v\d+$", "", s)
|
| 63 |
+
if not re.match(r"^[0-9]{4}\.[0-9]{4,5}$", s):
|
| 64 |
+
raise ValueError(f"Invalid arXiv ID format: {s}")
|
| 65 |
+
return s
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _build_commands(
|
| 69 |
+
cfg: PipelineConfig,
|
| 70 |
+
step: int,
|
| 71 |
+
job_processed_root: Path,
|
| 72 |
+
paper_id: str,
|
| 73 |
+
ids_path: Optional[Path],
|
| 74 |
+
) -> List[List[str]]:
|
| 75 |
+
py = sys.executable
|
| 76 |
+
if step == 1:
|
| 77 |
+
assert ids_path is not None
|
| 78 |
+
return [[
|
| 79 |
+
py,
|
| 80 |
+
"src/step_01_fetch/fetch_metadata.py",
|
| 81 |
+
"--ids",
|
| 82 |
+
str(ids_path),
|
| 83 |
+
"--outdir",
|
| 84 |
+
str(job_processed_root),
|
| 85 |
+
]]
|
| 86 |
+
if step == 2:
|
| 87 |
+
return [[py, "src/step_02_mark_citations/replace_citation_markers.py", "--root", str(job_processed_root)]]
|
| 88 |
+
if step == 3:
|
| 89 |
+
return [[py, "src/step_03_usage_contexts/build_usage_contexts.py", "--root", str(job_processed_root), "--out-name", "usage_contexts.json"]]
|
| 90 |
+
if step == 4:
|
| 91 |
+
return [[
|
| 92 |
+
py,
|
| 93 |
+
"src/step_04_label_citations/label_citation_functions.py",
|
| 94 |
+
"--root",
|
| 95 |
+
str(job_processed_root),
|
| 96 |
+
"--model-path",
|
| 97 |
+
cfg.model_path,
|
| 98 |
+
"--model-data-dir",
|
| 99 |
+
cfg.model_data_dir,
|
| 100 |
+
"--model-class-def",
|
| 101 |
+
cfg.model_class_def,
|
| 102 |
+
"--model-lm",
|
| 103 |
+
cfg.model_lm,
|
| 104 |
+
"--device",
|
| 105 |
+
cfg.device,
|
| 106 |
+
]]
|
| 107 |
+
if step == 5:
|
| 108 |
+
return [[
|
| 109 |
+
py,
|
| 110 |
+
"src/step_05_verify_uses_extends/verify_uses_extends.py",
|
| 111 |
+
"--root",
|
| 112 |
+
str(job_processed_root),
|
| 113 |
+
"--k",
|
| 114 |
+
"0",
|
| 115 |
+
"--batch-size",
|
| 116 |
+
"25",
|
| 117 |
+
]]
|
| 118 |
+
if step == 6:
|
| 119 |
+
return [[py, "src/step_06_extract_paragraphs/extract_arxiv_paragraphs.py", "--root", str(job_processed_root)]]
|
| 120 |
+
if step == 7:
|
| 121 |
+
return [
|
| 122 |
+
[py, "src/step_07_extract_and_refine/extract_contributions_from_citations.py", "--root", str(job_processed_root)],
|
| 123 |
+
[py, "src/step_07_extract_and_refine/refine_and_filter_clusters_llm.py", "--root", str(job_processed_root), "--inplace", "--overwrite"],
|
| 124 |
+
]
|
| 125 |
+
raise ValueError(f"Unknown step: {step}")
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def _write_single_id_file(job_dir: Path, arxiv_id: str) -> Path:
|
| 129 |
+
ids_path = job_dir / "input_ids.json"
|
| 130 |
+
payload = [{"id": arxiv_id, "title": "", "id_type": "ArXiv"}]
|
| 131 |
+
ids_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
| 132 |
+
return ids_path
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _write_run_metadata(cfg: PipelineConfig, job_dir: Path, paper_id: str, arxiv_id: str) -> None:
|
| 136 |
+
payload = {
|
| 137 |
+
"paper_input": cfg.paper_input,
|
| 138 |
+
"paper_id": paper_id,
|
| 139 |
+
"arxiv_id": arxiv_id,
|
| 140 |
+
"source_root": str(cfg.source_root),
|
| 141 |
+
"steps": FULL_STEPS + ["annotation"],
|
| 142 |
+
"llm_provider": cfg.llm_provider,
|
| 143 |
+
"llm_model": cfg.llm_model,
|
| 144 |
+
"llm_model_step4": cfg.llm_model_step4,
|
| 145 |
+
"device": cfg.device,
|
| 146 |
+
"embedding_model": cfg.embedding_model,
|
| 147 |
+
"timestamp": int(time.time()),
|
| 148 |
+
}
|
| 149 |
+
(job_dir / "run_config.json").write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _zip_job_dir(job_dir: Path) -> Path:
|
| 153 |
+
zip_base = job_dir.parent / job_dir.name
|
| 154 |
+
archive = shutil.make_archive(str(zip_base), "zip", root_dir=str(job_dir))
|
| 155 |
+
return Path(archive)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def _tail_log(path: Path, max_lines: int = 60) -> str:
|
| 159 |
+
try:
|
| 160 |
+
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
| 161 |
+
except Exception:
|
| 162 |
+
return ""
|
| 163 |
+
if not lines:
|
| 164 |
+
return ""
|
| 165 |
+
return "\n".join(lines[-max_lines:])
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _load_json(path: Path, default=None):
|
| 169 |
+
try:
|
| 170 |
+
return json.loads(path.read_text(encoding="utf-8"))
|
| 171 |
+
except Exception:
|
| 172 |
+
return default
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def _write_summary_and_zip(job_dir: Path, summary_lines: List[str]) -> Path:
|
| 176 |
+
(job_dir / "summary.txt").write_text("\n".join(summary_lines), encoding="utf-8")
|
| 177 |
+
return _zip_job_dir(job_dir)
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def _count_verified_uses_extends(payload: dict) -> int:
|
| 181 |
+
records = payload.get("confirmed") or payload.get("verified_contexts") or payload.get("contexts") or payload.get("items") or []
|
| 182 |
+
if not isinstance(records, list):
|
| 183 |
+
return 0
|
| 184 |
+
accepted = {"USES", "EXTENDS", "Uses", "Extends"}
|
| 185 |
+
return sum(1 for item in records if isinstance(item, dict) and item.get("label") in accepted)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _stop_reason_after_step(step: int, paper_dir: Path) -> str | None:
|
| 189 |
+
if step == 1:
|
| 190 |
+
if not paper_dir.exists():
|
| 191 |
+
return "metadata could not be fetched for this paper"
|
| 192 |
+
if not (paper_dir / "processed_main.tex").exists():
|
| 193 |
+
return "arXiv source could not be retrieved or converted for this paper"
|
| 194 |
+
citations = _load_json(paper_dir / "citations_metadata.json", [])
|
| 195 |
+
if not isinstance(citations, list) or not citations:
|
| 196 |
+
return "Semantic Scholar returned no citing papers for this target paper"
|
| 197 |
+
|
| 198 |
+
if step == 3:
|
| 199 |
+
usage = _load_json(paper_dir / "usage_contexts.json", {})
|
| 200 |
+
if not isinstance(usage, dict):
|
| 201 |
+
return "citation usage contexts could not be built"
|
| 202 |
+
if int(usage.get("num_contexts") or 0) == 0:
|
| 203 |
+
return "no citation usage contexts were found"
|
| 204 |
+
|
| 205 |
+
if step == 4:
|
| 206 |
+
labels = _load_json(paper_dir / "usage_context_labels.json", {})
|
| 207 |
+
contexts = labels.get("labels") if isinstance(labels, dict) else None
|
| 208 |
+
if not isinstance(contexts, list) or not contexts:
|
| 209 |
+
return "citation-function labeling produced no labeled contexts"
|
| 210 |
+
|
| 211 |
+
if step == 5:
|
| 212 |
+
verified = _load_json(paper_dir / "usage_uses_extends_verified.json", {})
|
| 213 |
+
if not isinstance(verified, dict):
|
| 214 |
+
return "USES/EXTENDS verification did not produce an output file"
|
| 215 |
+
if _count_verified_uses_extends(verified) == 0:
|
| 216 |
+
return "no downstream citations were verified as USES or EXTENDS"
|
| 217 |
+
|
| 218 |
+
if step == 6:
|
| 219 |
+
paragraphs = _load_json(paper_dir / "usage_citing_paragraphs.json", {})
|
| 220 |
+
citing = paragraphs.get("citing_papers") if isinstance(paragraphs, dict) else None
|
| 221 |
+
if not isinstance(citing, list) or not citing:
|
| 222 |
+
return "no citing-paper paragraphs could be extracted from arXiv"
|
| 223 |
+
usable = [
|
| 224 |
+
item for item in citing
|
| 225 |
+
if isinstance(item, dict)
|
| 226 |
+
and not item.get("error")
|
| 227 |
+
and (item.get("matched_paragraphs") or item.get("target_citing_paragraphs"))
|
| 228 |
+
]
|
| 229 |
+
if not usable:
|
| 230 |
+
return "arXiv paragraph extraction returned no usable citing-paper text"
|
| 231 |
+
|
| 232 |
+
if step == 7:
|
| 233 |
+
contributions = _load_json(paper_dir / "usage_contributions.json", {})
|
| 234 |
+
items = contributions.get("contributions") if isinstance(contributions, dict) else None
|
| 235 |
+
if not isinstance(items, list) or not items:
|
| 236 |
+
return "no downstream target-contribution evidence could be extracted"
|
| 237 |
+
refined = _load_json(paper_dir / "usage_discovery_from_contributions.json", {})
|
| 238 |
+
clusters = refined.get("clusters") if isinstance(refined, dict) else None
|
| 239 |
+
if not isinstance(clusters, list) or not clusters:
|
| 240 |
+
return "no valid downstream usage clusters survived refinement"
|
| 241 |
+
|
| 242 |
+
return None
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def run_pipeline(cfg: PipelineConfig, output_root: Path) -> Generator[Tuple[str, Optional[str]], None, PipelineResult]:
|
| 246 |
+
output_root.mkdir(parents=True, exist_ok=True)
|
| 247 |
+
job_id = f"job_{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
| 248 |
+
job_dir = output_root / job_id
|
| 249 |
+
job_processed_root = job_dir / "processed_papers"
|
| 250 |
+
job_logs = job_dir / "logs"
|
| 251 |
+
|
| 252 |
+
job_processed_root.mkdir(parents=True, exist_ok=True)
|
| 253 |
+
job_logs.mkdir(parents=True, exist_ok=True)
|
| 254 |
+
|
| 255 |
+
arxiv_id = parse_arxiv_id(cfg.paper_input)
|
| 256 |
+
paper_id = arxiv_id
|
| 257 |
+
ids_path = _write_single_id_file(job_dir, arxiv_id)
|
| 258 |
+
_write_run_metadata(cfg, job_dir, paper_id, arxiv_id)
|
| 259 |
+
|
| 260 |
+
base_env = os.environ.copy()
|
| 261 |
+
base_env["LLM_PROVIDER"] = cfg.llm_provider
|
| 262 |
+
base_env["LLM_MODEL"] = cfg.llm_model
|
| 263 |
+
|
| 264 |
+
summary_lines: List[str] = []
|
| 265 |
+
paper_dir = job_processed_root / paper_id
|
| 266 |
+
|
| 267 |
+
max_step = 8
|
| 268 |
+
for step in FULL_STEPS:
|
| 269 |
+
label = STEP_LABELS[step]
|
| 270 |
+
log_file = job_logs / f"step_{step:02d}.log"
|
| 271 |
+
summary_lines.append(f"[{step}] {label}")
|
| 272 |
+
yield (f"Step {step}/{max_step}: {label}", None)
|
| 273 |
+
|
| 274 |
+
env = base_env.copy()
|
| 275 |
+
if step == 5 and cfg.llm_model_step4:
|
| 276 |
+
env["LLM_MODEL"] = cfg.llm_model_step4
|
| 277 |
+
|
| 278 |
+
with log_file.open("w", encoding="utf-8") as lf:
|
| 279 |
+
return_code = 0
|
| 280 |
+
failed_cmd: List[str] | None = None
|
| 281 |
+
for cmd in _build_commands(cfg, step, job_processed_root, paper_id, ids_path):
|
| 282 |
+
lf.write(f"$ {' '.join(cmd)}\n\n")
|
| 283 |
+
proc = subprocess.Popen(
|
| 284 |
+
cmd,
|
| 285 |
+
cwd=str(cfg.repo_root),
|
| 286 |
+
stdout=subprocess.PIPE,
|
| 287 |
+
stderr=subprocess.STDOUT,
|
| 288 |
+
text=True,
|
| 289 |
+
encoding="utf-8",
|
| 290 |
+
errors="ignore",
|
| 291 |
+
env=env,
|
| 292 |
+
)
|
| 293 |
+
assert proc.stdout is not None
|
| 294 |
+
for line in proc.stdout:
|
| 295 |
+
lf.write(line)
|
| 296 |
+
return_code = proc.wait()
|
| 297 |
+
if return_code != 0:
|
| 298 |
+
failed_cmd = cmd
|
| 299 |
+
break
|
| 300 |
+
|
| 301 |
+
if return_code != 0:
|
| 302 |
+
summary_lines.append(f"FAILED at step {step}")
|
| 303 |
+
zip_path = _write_summary_and_zip(job_dir, summary_lines)
|
| 304 |
+
tail = _tail_log(log_file)
|
| 305 |
+
if tail:
|
| 306 |
+
yield (
|
| 307 |
+
f"Step {step} failed.\n\nCommand: {' '.join(failed_cmd or [])}\n\nLast log lines:\n{tail}",
|
| 308 |
+
str(zip_path),
|
| 309 |
+
)
|
| 310 |
+
else:
|
| 311 |
+
yield (f"Step {step} failed. Command: {' '.join(failed_cmd or [])}", str(zip_path))
|
| 312 |
+
return PipelineResult(job_id=job_id, job_dir=job_dir, paper_dir=paper_dir, zip_path=zip_path)
|
| 313 |
+
else:
|
| 314 |
+
yield (f"Step {step} complete", None)
|
| 315 |
+
|
| 316 |
+
if step == 1 and not paper_dir.exists():
|
| 317 |
+
summary_lines.append("FAILED: fetch_metadata did not create paper directory")
|
| 318 |
+
zip_path = _write_summary_and_zip(job_dir, summary_lines)
|
| 319 |
+
yield (f"Step 1 finished but paper dir missing: {paper_dir}", str(zip_path))
|
| 320 |
+
return PipelineResult(job_id=job_id, job_dir=job_dir, paper_dir=paper_dir, zip_path=zip_path)
|
| 321 |
+
|
| 322 |
+
stop_reason = _stop_reason_after_step(step, paper_dir)
|
| 323 |
+
if stop_reason:
|
| 324 |
+
message = f"{STOP_PREFIX} {stop_reason}."
|
| 325 |
+
summary_lines.append(message)
|
| 326 |
+
zip_path = _write_summary_and_zip(job_dir, summary_lines)
|
| 327 |
+
yield (message, str(zip_path))
|
| 328 |
+
return PipelineResult(job_id=job_id, job_dir=job_dir, paper_dir=paper_dir, zip_path=zip_path)
|
| 329 |
+
|
| 330 |
+
summary_lines.append("SUCCESS")
|
| 331 |
+
zip_path = _write_summary_and_zip(job_dir, summary_lines)
|
| 332 |
+
yield ("Pipeline completed successfully.", str(zip_path))
|
| 333 |
+
return PipelineResult(job_id=job_id, job_dir=job_dir, paper_dir=paper_dir, zip_path=zip_path)
|
hf_space/scipath_live/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SciPath Live (self-contained)
|
| 2 |
+
|
| 3 |
+
Minimal SciPath Task A CodeAgent used by **System Run → Live Run**.
|
| 4 |
+
|
| 5 |
+
## Included methods
|
| 6 |
+
|
| 7 |
+
| Method | Tools |
|
| 8 |
+
|--------|--------|
|
| 9 |
+
| `parametric` | `check_answer_format` |
|
| 10 |
+
| `websearch_deep` | `web_search_tool`, `deep_web_search_tool`, `check_answer_format` |
|
| 11 |
+
|
| 12 |
+
## Demo claim
|
| 13 |
+
|
| 14 |
+
`data/example_claim.json` — one claim from AVerImaTeC (`2505.17978`, claim C1), including gold enabling contributions for Task A evaluation.
|
| 15 |
+
|
| 16 |
+
## CLI
|
| 17 |
+
|
| 18 |
+
From `hf_space/`:
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
python -m scipath_live.cli run --method parametric --output-dir runs/system_live/test
|
| 22 |
+
python -m scipath_live.cli evaluate --result-json runs/system_live/test/result.json
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
## Env
|
| 26 |
+
|
| 27 |
+
- `GOOGLE_GENAI_API_KEY` or `GEMINI_API_KEY` (generation + judge)
|
| 28 |
+
- For `websearch_deep`: install Chromium into the demo-local cache:
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
# from demo_2026_07/
|
| 32 |
+
export PLAYWRIGHT_BROWSERS_PATH="$(pwd)/.playwright"
|
| 33 |
+
python -m playwright install chromium
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
`system_live_runner` / `tools.py` always force `PLAYWRIGHT_BROWSERS_PATH` to `demo_2026_07/.playwright` so Cursor sandbox temp paths are not used.
|
| 37 |
+
|
| 38 |
+
This package does **not** depend on `dryrun/` or other repos.
|
hf_space/scipath_live/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Self-contained SciPath Task A CodeAgent (parametric + websearch_deep)."""
|
| 2 |
+
|
| 3 |
+
from .pipeline import load_example_claim
|
| 4 |
+
|
| 5 |
+
__all__ = ["load_example_claim", "run_method", "evaluate_task_a"]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def run_method(*args, **kwargs):
|
| 9 |
+
from .pipeline import run_method as _run
|
| 10 |
+
|
| 11 |
+
return _run(*args, **kwargs)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def evaluate_task_a(*args, **kwargs):
|
| 15 |
+
from .pipeline import evaluate_task_a as _eval
|
| 16 |
+
|
| 17 |
+
return _eval(*args, **kwargs)
|
| 18 |
+
|
| 19 |
+
__all__ = ["evaluate_task_a", "load_example_claim", "run_method"]
|
hf_space/scipath_live/agent.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Slim SciPath CodeAgent runner (parametric + websearch_deep only)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import time
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Any, Optional
|
| 11 |
+
|
| 12 |
+
from dotenv import load_dotenv
|
| 13 |
+
from smolagents import CodeAgent, LiteLLMModel
|
| 14 |
+
|
| 15 |
+
from .prompts import get_scipath_controller_prompt, parse_ingredients_from_answer
|
| 16 |
+
from .tools import check_answer_format, deep_web_search_tool, web_search_tool
|
| 17 |
+
|
| 18 |
+
GEMINI_MODEL_IDS = {
|
| 19 |
+
"gemini-2.0-flash",
|
| 20 |
+
"gemini-2.5-flash",
|
| 21 |
+
"gemini-2.5-pro",
|
| 22 |
+
"gemini-3-flash-preview",
|
| 23 |
+
"gemini-3.1-pro-preview",
|
| 24 |
+
"gemini-3.5-flash",
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _memory_steps_to_list(agent: CodeAgent) -> list[dict[str, Any]]:
|
| 29 |
+
steps_list: list[dict[str, Any]] = []
|
| 30 |
+
for i, step in enumerate(getattr(agent.memory, "steps", []) or []):
|
| 31 |
+
item: dict[str, Any] = {
|
| 32 |
+
"step_type": type(step).__name__,
|
| 33 |
+
"step_index": i,
|
| 34 |
+
"timestamp": datetime.now().isoformat(),
|
| 35 |
+
}
|
| 36 |
+
for attr in ("step_number", "task", "code_action", "observations", "model_output"):
|
| 37 |
+
if hasattr(step, attr):
|
| 38 |
+
value = getattr(step, attr)
|
| 39 |
+
item[attr] = str(value) if value is not None and not isinstance(value, (str, int, float, bool, list, dict)) else value
|
| 40 |
+
if getattr(step, "error", None):
|
| 41 |
+
item["error"] = str(step.error)
|
| 42 |
+
steps_list.append(item)
|
| 43 |
+
return steps_list
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _clip(text: Any, limit: int = 1200) -> str:
|
| 47 |
+
s = str(text or "").strip()
|
| 48 |
+
if len(s) <= limit:
|
| 49 |
+
return s
|
| 50 |
+
return s[: limit - 1] + "…"
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def format_agent_trace(steps: list[dict[str, Any]]) -> list[str]:
|
| 54 |
+
"""Human-readable SciFy CodeAgent step trace for the Live Run terminal."""
|
| 55 |
+
lines: list[str] = []
|
| 56 |
+
lines.append("=== SciFy CodeAgent trace ===")
|
| 57 |
+
action_steps = [s for s in steps if s.get("step_type") == "ActionStep"]
|
| 58 |
+
if not action_steps:
|
| 59 |
+
lines.append("(no action steps recorded)")
|
| 60 |
+
return lines
|
| 61 |
+
|
| 62 |
+
for step in action_steps:
|
| 63 |
+
n = step.get("step_number")
|
| 64 |
+
if n is None:
|
| 65 |
+
n = int(step.get("step_index") or 0) + 1
|
| 66 |
+
lines.append("")
|
| 67 |
+
lines.append(f"── Step {n} ──")
|
| 68 |
+
thought = _clip(step.get("model_output"), 1600)
|
| 69 |
+
if thought:
|
| 70 |
+
lines.append("Thought / model output:")
|
| 71 |
+
lines.extend(thought.splitlines())
|
| 72 |
+
code = _clip(step.get("code_action"), 1600)
|
| 73 |
+
if code:
|
| 74 |
+
lines.append("Code action:")
|
| 75 |
+
lines.extend(code.splitlines())
|
| 76 |
+
obs = _clip(step.get("observations"), 1200)
|
| 77 |
+
if obs:
|
| 78 |
+
lines.append("Observations:")
|
| 79 |
+
lines.extend(obs.splitlines())
|
| 80 |
+
err = _clip(step.get("error"), 600)
|
| 81 |
+
if err:
|
| 82 |
+
lines.append(f"Error: {err}")
|
| 83 |
+
lines.append("")
|
| 84 |
+
lines.append("=== end trace ===")
|
| 85 |
+
return lines
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _print_trace(steps: list[dict[str, Any]]) -> None:
|
| 89 |
+
for line in format_agent_trace(steps):
|
| 90 |
+
print(line, flush=True)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _ensure_api_keys() -> None:
|
| 94 |
+
load_dotenv()
|
| 95 |
+
google = (
|
| 96 |
+
os.getenv("GOOGLE_GENAI_API_KEY")
|
| 97 |
+
or os.getenv("GEMINI_API_KEY")
|
| 98 |
+
or os.getenv("GOOGLE_API_KEY")
|
| 99 |
+
or ""
|
| 100 |
+
).strip()
|
| 101 |
+
if google:
|
| 102 |
+
os.environ.setdefault("GEMINI_API_KEY", google)
|
| 103 |
+
os.environ.setdefault("GOOGLE_API_KEY", google)
|
| 104 |
+
os.environ.setdefault("GOOGLE_GENAI_API_KEY", google)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def run_agent(
|
| 108 |
+
query: str,
|
| 109 |
+
*,
|
| 110 |
+
model_name: str = "gemini-3-flash-preview",
|
| 111 |
+
method: str = "parametric",
|
| 112 |
+
max_steps: int = 40,
|
| 113 |
+
) -> tuple[Any, list[dict[str, Any]]]:
|
| 114 |
+
"""Run SciPath Task A CodeAgent for one claim."""
|
| 115 |
+
if method not in {"parametric", "websearch_deep"}:
|
| 116 |
+
raise ValueError(f"Unsupported method for demo live run: {method}")
|
| 117 |
+
|
| 118 |
+
_ensure_api_keys()
|
| 119 |
+
litellm_model_id = (
|
| 120 |
+
f"gemini/{model_name}" if model_name in GEMINI_MODEL_IDS else model_name
|
| 121 |
+
)
|
| 122 |
+
model = LiteLLMModel(
|
| 123 |
+
model_id=litellm_model_id,
|
| 124 |
+
temperature=0.1,
|
| 125 |
+
max_tokens=16384,
|
| 126 |
+
timeout=2400,
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
if method == "parametric":
|
| 130 |
+
tool_list = [check_answer_format]
|
| 131 |
+
else:
|
| 132 |
+
tool_list = [web_search_tool, deep_web_search_tool, check_answer_format]
|
| 133 |
+
|
| 134 |
+
agent = CodeAgent(
|
| 135 |
+
tools=tool_list,
|
| 136 |
+
model=model,
|
| 137 |
+
verbosity_level=2,
|
| 138 |
+
additional_authorized_imports=["json", "os", "typing", "pprint"],
|
| 139 |
+
max_steps=max_steps,
|
| 140 |
+
executor_kwargs={"timeout_seconds": None},
|
| 141 |
+
)
|
| 142 |
+
prompt = get_scipath_controller_prompt(query, method)
|
| 143 |
+
result = agent.run(prompt)
|
| 144 |
+
logs = _memory_steps_to_list(agent)
|
| 145 |
+
return result, logs
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def run_claim_to_dir(
|
| 149 |
+
*,
|
| 150 |
+
claim: str,
|
| 151 |
+
paper_id: str,
|
| 152 |
+
claim_idx: int,
|
| 153 |
+
paper_title: str,
|
| 154 |
+
method: str,
|
| 155 |
+
output_dir: Path,
|
| 156 |
+
model_name: str = "gemini-3-flash-preview",
|
| 157 |
+
) -> dict[str, Any]:
|
| 158 |
+
"""Run one claim and write answers.jsonl + logs.jsonl into output_dir."""
|
| 159 |
+
output_dir = Path(output_dir)
|
| 160 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 161 |
+
|
| 162 |
+
problem_id = f"{paper_id}__claim{claim_idx}"
|
| 163 |
+
claim_row = {
|
| 164 |
+
"type": "problem",
|
| 165 |
+
"format_version": "1.0",
|
| 166 |
+
"problem_id": problem_id,
|
| 167 |
+
"problem_version": "1.0",
|
| 168 |
+
"domain": "ai",
|
| 169 |
+
"subdomain": "scipath",
|
| 170 |
+
"claim": claim,
|
| 171 |
+
}
|
| 172 |
+
index_row = {
|
| 173 |
+
"problem_id": problem_id,
|
| 174 |
+
"paper_id": paper_id,
|
| 175 |
+
"claim_idx": claim_idx,
|
| 176 |
+
"paper_title": paper_title,
|
| 177 |
+
"claim": claim,
|
| 178 |
+
"split": "dev",
|
| 179 |
+
}
|
| 180 |
+
(output_dir / "claim.jsonl").write_text(
|
| 181 |
+
json.dumps(claim_row, ensure_ascii=False) + "\n", encoding="utf-8"
|
| 182 |
+
)
|
| 183 |
+
(output_dir / "task_index.jsonl").write_text(
|
| 184 |
+
json.dumps(index_row, ensure_ascii=False) + "\n", encoding="utf-8"
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
print(f"[scipath_live] Starting CodeAgent method={method} model={model_name}", flush=True)
|
| 188 |
+
print(f"[scipath_live] Claim: {claim}", flush=True)
|
| 189 |
+
t0 = time.time()
|
| 190 |
+
answer, logs = run_agent(claim, model_name=model_name, method=method)
|
| 191 |
+
wall = time.time() - t0
|
| 192 |
+
print(f"[scipath_live] Agent finished in {wall:.1f}s ({len(logs)} memory steps)", flush=True)
|
| 193 |
+
_print_trace(logs)
|
| 194 |
+
|
| 195 |
+
ingredients = parse_ingredients_from_answer(answer)
|
| 196 |
+
if not ingredients and isinstance(answer, dict):
|
| 197 |
+
ingredients = parse_ingredients_from_answer(answer)
|
| 198 |
+
print(f"[scipath_live] Parsed {len(ingredients)} ingredients", flush=True)
|
| 199 |
+
|
| 200 |
+
out = {
|
| 201 |
+
"type": "scipath_task_a",
|
| 202 |
+
"format_version": "1.0",
|
| 203 |
+
"problem_id": problem_id,
|
| 204 |
+
"problem_version": "1.0",
|
| 205 |
+
"team": "scify",
|
| 206 |
+
"run_id": f"live_{method}",
|
| 207 |
+
"ingredients": ingredients,
|
| 208 |
+
"raw_answer": answer if isinstance(answer, (dict, list, str)) else str(answer),
|
| 209 |
+
"wall_clock_time": wall,
|
| 210 |
+
"method": method,
|
| 211 |
+
"model_name": model_name,
|
| 212 |
+
"paper_id": paper_id,
|
| 213 |
+
"claim_idx": claim_idx,
|
| 214 |
+
"paper_title": paper_title,
|
| 215 |
+
"claim": claim,
|
| 216 |
+
}
|
| 217 |
+
(output_dir / "answers.jsonl").write_text(
|
| 218 |
+
json.dumps(out, ensure_ascii=False) + "\n", encoding="utf-8"
|
| 219 |
+
)
|
| 220 |
+
(output_dir / "logs.jsonl").write_text(
|
| 221 |
+
json.dumps({"problem_id": problem_id, "steps": logs}, ensure_ascii=False) + "\n",
|
| 222 |
+
encoding="utf-8",
|
| 223 |
+
)
|
| 224 |
+
(output_dir / "result.json").write_text(
|
| 225 |
+
json.dumps(out, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
| 226 |
+
)
|
| 227 |
+
return out
|
hf_space/scipath_live/cli.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""CLI entry for SciPath Live Run (used as a subprocess from Streamlit)."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import sys
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
# Allow `python -m scipath_live.cli` and `python scipath_live/cli.py`
|
| 12 |
+
PACKAGE_ROOT = Path(__file__).resolve().parent
|
| 13 |
+
HF_SPACE = PACKAGE_ROOT.parent
|
| 14 |
+
if str(HF_SPACE) not in sys.path:
|
| 15 |
+
sys.path.insert(0, str(HF_SPACE))
|
| 16 |
+
|
| 17 |
+
from scipath_live.pipeline import evaluate_task_a, load_example_claim, run_method # noqa: E402
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def main() -> int:
|
| 21 |
+
parser = argparse.ArgumentParser(description="SciPath Live Run CLI")
|
| 22 |
+
parser.add_argument(
|
| 23 |
+
"action",
|
| 24 |
+
choices=["run", "evaluate"],
|
| 25 |
+
help="run a method or evaluate an existing run result JSON",
|
| 26 |
+
)
|
| 27 |
+
parser.add_argument(
|
| 28 |
+
"--method",
|
| 29 |
+
default="parametric",
|
| 30 |
+
choices=["parametric", "websearch_deep", "codeagent_parametric", "codeagent_websearch_deep"],
|
| 31 |
+
)
|
| 32 |
+
parser.add_argument("--output-dir", default="")
|
| 33 |
+
parser.add_argument("--model", default="gemini-3-flash-preview")
|
| 34 |
+
parser.add_argument("--judge-model", default="gemini/gemini-3.1-pro-preview")
|
| 35 |
+
parser.add_argument(
|
| 36 |
+
"--result-json",
|
| 37 |
+
default="",
|
| 38 |
+
help="For evaluate: path to result.json from a prior run",
|
| 39 |
+
)
|
| 40 |
+
args = parser.parse_args()
|
| 41 |
+
|
| 42 |
+
if args.action == "run":
|
| 43 |
+
out = Path(args.output_dir) if args.output_dir else None
|
| 44 |
+
result = run_method(
|
| 45 |
+
args.method,
|
| 46 |
+
output_dir=out,
|
| 47 |
+
model_name=args.model,
|
| 48 |
+
)
|
| 49 |
+
print(json.dumps({"ok": True, "result": result}, ensure_ascii=False))
|
| 50 |
+
return 0
|
| 51 |
+
|
| 52 |
+
if not args.result_json:
|
| 53 |
+
print(json.dumps({"ok": False, "error": "--result-json is required for evaluate"}))
|
| 54 |
+
return 2
|
| 55 |
+
run_result = json.loads(Path(args.result_json).read_text(encoding="utf-8"))
|
| 56 |
+
# Ensure output_dir is set for writing judged JSON.
|
| 57 |
+
if not run_result.get("output_dir"):
|
| 58 |
+
run_result["output_dir"] = str(Path(args.result_json).parent)
|
| 59 |
+
judged = evaluate_task_a(
|
| 60 |
+
run_result=run_result,
|
| 61 |
+
claim_record=load_example_claim(),
|
| 62 |
+
judge_model=args.judge_model,
|
| 63 |
+
)
|
| 64 |
+
print(json.dumps({"ok": True, "judged": judged}, ensure_ascii=False))
|
| 65 |
+
return 0
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
if __name__ == "__main__":
|
| 69 |
+
raise SystemExit(main())
|
hf_space/scipath_live/data/example_claim.json
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"paper_id": "2505.17978",
|
| 3 |
+
"claim_idx": 0,
|
| 4 |
+
"claim_id": "C1",
|
| 5 |
+
"paper_title": "AVerImaTeC: A Dataset for Automatic Verification of Image-Text Claims with Evidence from the Web",
|
| 6 |
+
"claim": "Dataset: A real-world image-text claim verification dataset annotated with question-answer reasoning paths and web evidence, enabling the training and evaluation of multimodal fact-checking systems.",
|
| 7 |
+
"enabling_contributions": [
|
| 8 |
+
{
|
| 9 |
+
"enabling_contribution_id": "2505.17978::ingredient::01",
|
| 10 |
+
"enabling_contribution": "Source fact-checking articles containing multimodal claims",
|
| 11 |
+
"canonical_ref_id": "__NONE__",
|
| 12 |
+
"canonical_grounding": null,
|
| 13 |
+
"additional_ref_ids": [
|
| 14 |
+
"ref_averitec",
|
| 15 |
+
"ref_ammeba"
|
| 16 |
+
],
|
| 17 |
+
"additional_groundings": [
|
| 18 |
+
{
|
| 19 |
+
"ref_id": "ref_averitec",
|
| 20 |
+
"bib_key": "",
|
| 21 |
+
"paper_id": "",
|
| 22 |
+
"external_ids": {},
|
| 23 |
+
"ref_title": "AVeriTeC: A Dataset for Real-world Claim Verification with Evidence from the Web",
|
| 24 |
+
"ref_year": "",
|
| 25 |
+
"ref_authors": ""
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"ref_id": "ref_ammeba",
|
| 29 |
+
"bib_key": "",
|
| 30 |
+
"paper_id": "",
|
| 31 |
+
"external_ids": {},
|
| 32 |
+
"ref_title": "AMMeBa: A Large-Scale Survey and Dataset of Media-Based Misinformation In-The-Wild",
|
| 33 |
+
"ref_year": "",
|
| 34 |
+
"ref_authors": ""
|
| 35 |
+
}
|
| 36 |
+
],
|
| 37 |
+
"canonical_annotation": {
|
| 38 |
+
"role": "DATA_SOURCE",
|
| 39 |
+
"roles": [
|
| 40 |
+
"DATA_SOURCE"
|
| 41 |
+
],
|
| 42 |
+
"contribution": "Provides the raw real-world image-text claims that are normalized and annotated to form the dataset.",
|
| 43 |
+
"rationale": "The dataset is a composite of claims extracted from multiple prior fact-checking resources. No single prior study fully represents this substrate, making NONE the correct canonical grounding, with the primary sources listed as additional groundings.",
|
| 44 |
+
"evidence_span": "we incorporated partially annotated articles from three sources: 1) filtered articles from AVeriTec... 2) articles verifying image-related claims from AMMEBA"
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"enabling_contribution_id": "2505.17978::ingredient::02",
|
| 49 |
+
"enabling_contribution": "Question-answering representation for verification rationale",
|
| 50 |
+
"canonical_ref_id": "ref_complex",
|
| 51 |
+
"canonical_grounding": {
|
| 52 |
+
"ref_id": "ref_complex",
|
| 53 |
+
"bib_key": "",
|
| 54 |
+
"paper_id": "",
|
| 55 |
+
"external_ids": {},
|
| 56 |
+
"ref_title": "Complex Claim Verification with Evidence Retrieved in the Wild",
|
| 57 |
+
"ref_year": "",
|
| 58 |
+
"ref_authors": ""
|
| 59 |
+
},
|
| 60 |
+
"additional_ref_ids": [],
|
| 61 |
+
"additional_groundings": [],
|
| 62 |
+
"canonical_annotation": {
|
| 63 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 64 |
+
"roles": [
|
| 65 |
+
"CONCEPTUAL_FRAMEWORK"
|
| 66 |
+
],
|
| 67 |
+
"contribution": "Provides the QA-based reasoning representation used to structure the evidence retrieval and verification process.",
|
| 68 |
+
"rationale": "The paper explicitly adopts QA as the reasoning representation for fact-checking, citing prior work that established this paradigm. Chen et al. (CLAIMDECOMP) is a clean representative for QA-based claim decomposition.",
|
| 69 |
+
"evidence_span": "To capture the rationale in claim verification, a complex reasoning task, various reasoning representations have been explored... and QA"
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"enabling_contribution_id": "2505.17978::ingredient::03",
|
| 74 |
+
"enabling_contribution": "Four-way veracity labeling schema",
|
| 75 |
+
"canonical_ref_id": "ref_averitec",
|
| 76 |
+
"canonical_grounding": {
|
| 77 |
+
"ref_id": "ref_averitec",
|
| 78 |
+
"bib_key": "",
|
| 79 |
+
"paper_id": "",
|
| 80 |
+
"external_ids": {},
|
| 81 |
+
"ref_title": "AVeriTeC: A Dataset for Real-world Claim Verification with Evidence from the Web",
|
| 82 |
+
"ref_year": "",
|
| 83 |
+
"ref_authors": ""
|
| 84 |
+
},
|
| 85 |
+
"additional_ref_ids": [],
|
| 86 |
+
"additional_groundings": [],
|
| 87 |
+
"canonical_annotation": {
|
| 88 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 89 |
+
"roles": [
|
| 90 |
+
"CONCEPTUAL_FRAMEWORK"
|
| 91 |
+
],
|
| 92 |
+
"contribution": "Provides the specific four-way label space (supported, refuted, not enough evidence, conflicting) used to annotate the claims.",
|
| 93 |
+
"rationale": "The paper explicitly adopts the exact four-way veracity labeling schema introduced in AVeriTeC.",
|
| 94 |
+
"evidence_span": "We follow the four-way veracity labeling schema from [Schlichtkrull et al., 2023]: supported, refuted, not enough evidence, and conflicting/cherry-picking."
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"enabling_contribution_id": "2505.17978::ingredient::04",
|
| 99 |
+
"enabling_contribution": "Multi-stage annotation pipeline with evidence sufficiency checks",
|
| 100 |
+
"canonical_ref_id": "ref_averitec",
|
| 101 |
+
"canonical_grounding": {
|
| 102 |
+
"ref_id": "ref_averitec",
|
| 103 |
+
"bib_key": "",
|
| 104 |
+
"paper_id": "",
|
| 105 |
+
"external_ids": {},
|
| 106 |
+
"ref_title": "AVeriTeC: A Dataset for Real-world Claim Verification with Evidence from the Web",
|
| 107 |
+
"ref_year": "",
|
| 108 |
+
"ref_authors": ""
|
| 109 |
+
},
|
| 110 |
+
"additional_ref_ids": [],
|
| 111 |
+
"additional_groundings": [],
|
| 112 |
+
"canonical_annotation": {
|
| 113 |
+
"role": "EVALUATION_PROTOCOL",
|
| 114 |
+
"roles": [
|
| 115 |
+
"EVALUATION_PROTOCOL"
|
| 116 |
+
],
|
| 117 |
+
"contribution": "Provides the foundational multi-stage annotation and verification workflow that the paper extends for image-text claims.",
|
| 118 |
+
"rationale": "The paper explicitly states that its annotation pipeline is an extension of the process proposed in AVeriTeC, making it the canonical grounding for this workflow.",
|
| 119 |
+
"evidence_span": "extending the annotation process proposed in [Schlichtkrull et al., 2023] to the domain of image-text claims."
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
],
|
| 123 |
+
"source": "AVerImaTeC"
|
| 124 |
+
}
|
hf_space/scipath_live/judge.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Task A ingredient matching judge (demo-local, single-claim)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import asyncio
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import re
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
import litellm
|
| 12 |
+
from dotenv import load_dotenv
|
| 13 |
+
|
| 14 |
+
MATCHING_JUDGE_PROMPT = """\
|
| 15 |
+
You are evaluating whether predicted enabling contributions match reference enabling contributions for a scientific discovery.
|
| 16 |
+
|
| 17 |
+
A full match requires that the predicted item express the same functional requirement needed to realize the discovery. It may use different wording, but it must preserve the key resource, method, process, role, and level of specificity.
|
| 18 |
+
|
| 19 |
+
A partial match means the predicted item is related but too broad, too narrow, missing an important role/detail, or merges multiple distinct reference requirements.
|
| 20 |
+
|
| 21 |
+
Do not mark broad umbrella items as full matches for multiple distinct reference contributions.
|
| 22 |
+
|
| 23 |
+
Discovery claim:
|
| 24 |
+
{claim}
|
| 25 |
+
|
| 26 |
+
Reference enabling contributions:
|
| 27 |
+
{references}
|
| 28 |
+
|
| 29 |
+
Predicted enabling contributions:
|
| 30 |
+
{predicted}
|
| 31 |
+
|
| 32 |
+
Return candidate matches only where match is "full" or "partial". Omit unrelated pairs.
|
| 33 |
+
|
| 34 |
+
Respond with JSON only (no markdown):
|
| 35 |
+
{{"matches": [{{"reference_idx": <1-based index>, "predicted_idx": <1-based index>, "match": "full"|"partial", "reasoning": "<one sentence>"}}]}}\
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _format_reference_ingredient(silver_ingredient: dict) -> str:
|
| 40 |
+
annotation = silver_ingredient.get("canonical_annotation") or {}
|
| 41 |
+
roles = annotation.get("roles")
|
| 42 |
+
if isinstance(roles, list):
|
| 43 |
+
role_text = ", ".join(str(r) for r in roles)
|
| 44 |
+
else:
|
| 45 |
+
role_text = annotation.get("role", "") or silver_ingredient.get("role", "")
|
| 46 |
+
|
| 47 |
+
ingredient = (
|
| 48 |
+
silver_ingredient.get("enabling_contribution")
|
| 49 |
+
or silver_ingredient.get("ingredient")
|
| 50 |
+
or silver_ingredient.get("description")
|
| 51 |
+
or ""
|
| 52 |
+
)
|
| 53 |
+
lines = [f"Ingredient: {ingredient}"]
|
| 54 |
+
if role_text:
|
| 55 |
+
lines.append(f"Role: {role_text}")
|
| 56 |
+
if annotation.get("contribution"):
|
| 57 |
+
lines.append(f"Contribution: {annotation['contribution']}")
|
| 58 |
+
if annotation.get("rationale"):
|
| 59 |
+
lines.append(f"Rationale: {annotation['rationale']}")
|
| 60 |
+
return "\n".join(lines)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _format_predicted_ingredient(idx: int, ingredient: dict) -> str:
|
| 64 |
+
lines = [f"[{idx}] {ingredient.get('description', '')}"]
|
| 65 |
+
if ingredient.get("role"):
|
| 66 |
+
lines.append(f" Role: {ingredient['role']}")
|
| 67 |
+
if ingredient.get("rationale"):
|
| 68 |
+
lines.append(f" Rationale: {ingredient['rationale']}")
|
| 69 |
+
return "\n".join(lines)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _parse_matching_judgment(raw: str, n_references: int, n_predicted: int) -> dict | None:
|
| 73 |
+
raw = re.sub(r"^```(?:json)?\s*", "", raw.strip())
|
| 74 |
+
raw = re.sub(r"\s*```$", "", raw)
|
| 75 |
+
try:
|
| 76 |
+
data = json.loads(raw)
|
| 77 |
+
except json.JSONDecodeError:
|
| 78 |
+
return None
|
| 79 |
+
raw_matches = data.get("matches")
|
| 80 |
+
if not isinstance(raw_matches, list):
|
| 81 |
+
return None
|
| 82 |
+
matches = []
|
| 83 |
+
for match in raw_matches:
|
| 84 |
+
if not isinstance(match, dict):
|
| 85 |
+
continue
|
| 86 |
+
ref_idx = match.get("reference_idx")
|
| 87 |
+
pred_idx = match.get("predicted_idx")
|
| 88 |
+
label = str(match.get("match", "")).lower()
|
| 89 |
+
if label not in {"full", "partial"}:
|
| 90 |
+
continue
|
| 91 |
+
if not isinstance(ref_idx, int) or not isinstance(pred_idx, int):
|
| 92 |
+
continue
|
| 93 |
+
if not (1 <= ref_idx <= n_references and 1 <= pred_idx <= n_predicted):
|
| 94 |
+
continue
|
| 95 |
+
matches.append(
|
| 96 |
+
{
|
| 97 |
+
"reference_idx": ref_idx,
|
| 98 |
+
"predicted_idx": pred_idx,
|
| 99 |
+
"match": label,
|
| 100 |
+
"reasoning": str(match.get("reasoning", "")),
|
| 101 |
+
}
|
| 102 |
+
)
|
| 103 |
+
return {"matches": matches}
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def maximum_full_matching(
|
| 107 |
+
matches: list[dict], n_references: int, n_predicted: int
|
| 108 |
+
) -> list[dict]:
|
| 109 |
+
edges: dict[int, list[dict]] = {i: [] for i in range(1, n_references + 1)}
|
| 110 |
+
seen: set[tuple[int, int]] = set()
|
| 111 |
+
for match in matches:
|
| 112 |
+
if match.get("match") != "full":
|
| 113 |
+
continue
|
| 114 |
+
ref_idx = match.get("reference_idx")
|
| 115 |
+
pred_idx = match.get("predicted_idx")
|
| 116 |
+
if not isinstance(ref_idx, int) or not isinstance(pred_idx, int):
|
| 117 |
+
continue
|
| 118 |
+
if not (1 <= ref_idx <= n_references and 1 <= pred_idx <= n_predicted):
|
| 119 |
+
continue
|
| 120 |
+
key = (ref_idx, pred_idx)
|
| 121 |
+
if key in seen:
|
| 122 |
+
continue
|
| 123 |
+
seen.add(key)
|
| 124 |
+
edges[ref_idx].append(match)
|
| 125 |
+
for ref_edges in edges.values():
|
| 126 |
+
ref_edges.sort(key=lambda m: m["predicted_idx"])
|
| 127 |
+
|
| 128 |
+
pred_to_match: dict[int, dict] = {}
|
| 129 |
+
|
| 130 |
+
def _try_match(ref_idx: int, visited: set[int]) -> bool:
|
| 131 |
+
for edge in edges.get(ref_idx, []):
|
| 132 |
+
pred_idx = edge["predicted_idx"]
|
| 133 |
+
if pred_idx in visited:
|
| 134 |
+
continue
|
| 135 |
+
visited.add(pred_idx)
|
| 136 |
+
previous = pred_to_match.get(pred_idx)
|
| 137 |
+
if previous is None or _try_match(previous["reference_idx"], visited):
|
| 138 |
+
pred_to_match[pred_idx] = edge
|
| 139 |
+
return True
|
| 140 |
+
return False
|
| 141 |
+
|
| 142 |
+
for ref_idx in range(1, n_references + 1):
|
| 143 |
+
_try_match(ref_idx, set())
|
| 144 |
+
return sorted(
|
| 145 |
+
pred_to_match.values(), key=lambda m: (m["reference_idx"], m["predicted_idx"])
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
async def judge_ingredient_matching(
|
| 150 |
+
model: str,
|
| 151 |
+
claim: str,
|
| 152 |
+
reference_ingredients: list[dict],
|
| 153 |
+
predicted_ingredients: list[dict],
|
| 154 |
+
max_retries: int = 3,
|
| 155 |
+
) -> dict:
|
| 156 |
+
references_text = "\n\n".join(
|
| 157 |
+
f"[{i + 1}] " + _format_reference_ingredient(ing).replace("\n", "\n ")
|
| 158 |
+
for i, ing in enumerate(reference_ingredients)
|
| 159 |
+
) or "(none)"
|
| 160 |
+
predicted_text = "\n\n".join(
|
| 161 |
+
_format_predicted_ingredient(i + 1, ing)
|
| 162 |
+
for i, ing in enumerate(predicted_ingredients)
|
| 163 |
+
) or "(none)"
|
| 164 |
+
prompt = MATCHING_JUDGE_PROMPT.format(
|
| 165 |
+
claim=claim, references=references_text, predicted=predicted_text
|
| 166 |
+
)
|
| 167 |
+
for _ in range(max_retries):
|
| 168 |
+
msg = await litellm.acompletion(
|
| 169 |
+
model=model,
|
| 170 |
+
max_tokens=8192,
|
| 171 |
+
messages=[{"role": "user", "content": prompt}],
|
| 172 |
+
)
|
| 173 |
+
raw = msg.choices[0].message.content.strip()
|
| 174 |
+
result = _parse_matching_judgment(
|
| 175 |
+
raw, len(reference_ingredients), len(predicted_ingredients)
|
| 176 |
+
)
|
| 177 |
+
if result is not None:
|
| 178 |
+
return result
|
| 179 |
+
raise ValueError("Failed to parse valid matching judgment")
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def _gold_to_ui_ingredients(gold: list[dict]) -> list[dict[str, str]]:
|
| 183 |
+
out = []
|
| 184 |
+
for g in gold:
|
| 185 |
+
ann = g.get("canonical_annotation") or {}
|
| 186 |
+
role = ""
|
| 187 |
+
if isinstance(ann.get("roles"), list) and ann["roles"]:
|
| 188 |
+
role = str(ann["roles"][0])
|
| 189 |
+
else:
|
| 190 |
+
role = str(ann.get("role") or g.get("role") or "")
|
| 191 |
+
out.append(
|
| 192 |
+
{
|
| 193 |
+
"description": str(
|
| 194 |
+
g.get("enabling_contribution") or g.get("description") or ""
|
| 195 |
+
),
|
| 196 |
+
"role": role,
|
| 197 |
+
}
|
| 198 |
+
)
|
| 199 |
+
return out
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def evaluate_prediction(
|
| 203 |
+
*,
|
| 204 |
+
claim: str,
|
| 205 |
+
gold_ingredients: list[dict],
|
| 206 |
+
predicted_ingredients: list[dict],
|
| 207 |
+
judge_model: str = "gemini/gemini-3.1-pro-preview",
|
| 208 |
+
) -> dict[str, Any]:
|
| 209 |
+
"""Sync wrapper: judge one claim and compute Task A scores."""
|
| 210 |
+
load_dotenv()
|
| 211 |
+
google = (
|
| 212 |
+
os.getenv("GOOGLE_GENAI_API_KEY")
|
| 213 |
+
or os.getenv("GEMINI_API_KEY")
|
| 214 |
+
or os.getenv("GOOGLE_API_KEY")
|
| 215 |
+
or ""
|
| 216 |
+
).strip()
|
| 217 |
+
if google:
|
| 218 |
+
os.environ.setdefault("GEMINI_API_KEY", google)
|
| 219 |
+
os.environ.setdefault("GOOGLE_API_KEY", google)
|
| 220 |
+
|
| 221 |
+
matching = asyncio.run(
|
| 222 |
+
judge_ingredient_matching(
|
| 223 |
+
judge_model, claim, gold_ingredients, predicted_ingredients
|
| 224 |
+
)
|
| 225 |
+
)
|
| 226 |
+
matches = matching.get("matches") or []
|
| 227 |
+
full_pairs = maximum_full_matching(
|
| 228 |
+
matches, len(gold_ingredients), len(predicted_ingredients)
|
| 229 |
+
)
|
| 230 |
+
n_matched = len(full_pairs)
|
| 231 |
+
n_ref = len(gold_ingredients)
|
| 232 |
+
n_pred = len(predicted_ingredients)
|
| 233 |
+
recall = (n_matched / n_ref) if n_ref else 0.0
|
| 234 |
+
precision = (n_matched / n_pred) if n_pred else 0.0
|
| 235 |
+
f1 = (
|
| 236 |
+
(2 * precision * recall / (precision + recall))
|
| 237 |
+
if (precision + recall)
|
| 238 |
+
else 0.0
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
# Build recall_judgments for UI gold column.
|
| 242 |
+
full_by_ref = {m["reference_idx"]: m for m in full_pairs}
|
| 243 |
+
partial_by_ref: dict[int, list[dict]] = {}
|
| 244 |
+
for m in matches:
|
| 245 |
+
if m.get("match") == "partial":
|
| 246 |
+
partial_by_ref.setdefault(m["reference_idx"], []).append(m)
|
| 247 |
+
|
| 248 |
+
judgments = []
|
| 249 |
+
for i, g in enumerate(gold_ingredients, start=1):
|
| 250 |
+
desc = str(g.get("enabling_contribution") or g.get("description") or "")
|
| 251 |
+
ann = g.get("canonical_annotation") or {}
|
| 252 |
+
role = ""
|
| 253 |
+
if isinstance(ann.get("roles"), list) and ann["roles"]:
|
| 254 |
+
role = str(ann["roles"][0])
|
| 255 |
+
else:
|
| 256 |
+
role = str(ann.get("role") or "")
|
| 257 |
+
full = full_by_ref.get(i)
|
| 258 |
+
partials = partial_by_ref.get(i) or []
|
| 259 |
+
if full:
|
| 260 |
+
pred = predicted_ingredients[full["predicted_idx"] - 1]
|
| 261 |
+
judgments.append(
|
| 262 |
+
{
|
| 263 |
+
"reference_idx": i,
|
| 264 |
+
"reference_ingredient": desc,
|
| 265 |
+
"reference_role": role,
|
| 266 |
+
"covered": True,
|
| 267 |
+
"best_match_idx": full["predicted_idx"],
|
| 268 |
+
"reasoning": full.get("reasoning", ""),
|
| 269 |
+
"best_match": pred,
|
| 270 |
+
"partial_matches": [],
|
| 271 |
+
}
|
| 272 |
+
)
|
| 273 |
+
else:
|
| 274 |
+
judgments.append(
|
| 275 |
+
{
|
| 276 |
+
"reference_idx": i,
|
| 277 |
+
"reference_ingredient": desc,
|
| 278 |
+
"reference_role": role,
|
| 279 |
+
"covered": False,
|
| 280 |
+
"best_match_idx": None,
|
| 281 |
+
"reasoning": "",
|
| 282 |
+
"best_match": None,
|
| 283 |
+
"partial_matches": [
|
| 284 |
+
{
|
| 285 |
+
"reference_idx": p["reference_idx"],
|
| 286 |
+
"predicted_idx": p["predicted_idx"],
|
| 287 |
+
"match": "partial",
|
| 288 |
+
"reasoning": p.get("reasoning", ""),
|
| 289 |
+
}
|
| 290 |
+
for p in partials
|
| 291 |
+
],
|
| 292 |
+
}
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
return {
|
| 296 |
+
"recall": recall,
|
| 297 |
+
"precision": precision,
|
| 298 |
+
"f1": f1,
|
| 299 |
+
"matched_pairs": full_pairs,
|
| 300 |
+
"partial_matches": [m for m in matches if m.get("match") == "partial"],
|
| 301 |
+
"recall_judgments": judgments,
|
| 302 |
+
"gold_ingredients": _gold_to_ui_ingredients(gold_ingredients),
|
| 303 |
+
"predicted_ingredients": predicted_ingredients,
|
| 304 |
+
"judge_model": judge_model,
|
| 305 |
+
}
|
hf_space/scipath_live/pipeline.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""High-level Live Run API used by Streamlit (self-contained)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import uuid
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any, Optional
|
| 9 |
+
|
| 10 |
+
PACKAGE_DIR = Path(__file__).resolve().parent
|
| 11 |
+
DATA_DIR = PACKAGE_DIR / "data"
|
| 12 |
+
EXAMPLE_CLAIM_PATH = DATA_DIR / "example_claim.json"
|
| 13 |
+
DEFAULT_RUNS_ROOT = PACKAGE_DIR.parent / "runs" / "system_live"
|
| 14 |
+
|
| 15 |
+
METHOD_CLI = {
|
| 16 |
+
"codeagent_parametric": "parametric",
|
| 17 |
+
"parametric": "parametric",
|
| 18 |
+
"codeagent_websearch_deep": "websearch_deep",
|
| 19 |
+
"websearch_deep": "websearch_deep",
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def load_example_claim(path: Optional[Path] = None) -> dict[str, Any]:
|
| 24 |
+
claim_path = Path(path) if path else EXAMPLE_CLAIM_PATH
|
| 25 |
+
data = json.loads(claim_path.read_text(encoding="utf-8"))
|
| 26 |
+
if not isinstance(data, dict) or not data.get("claim"):
|
| 27 |
+
raise ValueError(f"Invalid example claim at {claim_path}")
|
| 28 |
+
return data
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def new_job_dir(runs_root: Optional[Path] = None, method: str = "parametric") -> Path:
|
| 32 |
+
root = Path(runs_root) if runs_root else DEFAULT_RUNS_ROOT
|
| 33 |
+
job = root / f"job_{method}_{uuid.uuid4().hex[:10]}"
|
| 34 |
+
job.mkdir(parents=True, exist_ok=True)
|
| 35 |
+
return job
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def run_method(
|
| 39 |
+
method_id: str,
|
| 40 |
+
*,
|
| 41 |
+
claim_record: Optional[dict[str, Any]] = None,
|
| 42 |
+
output_dir: Optional[Path] = None,
|
| 43 |
+
model_name: str = "gemini-3-flash-preview",
|
| 44 |
+
) -> dict[str, Any]:
|
| 45 |
+
"""Run parametric or websearch_deep on the demo example claim (or provided record)."""
|
| 46 |
+
method = METHOD_CLI.get(method_id)
|
| 47 |
+
if not method:
|
| 48 |
+
raise ValueError(f"Unknown method id: {method_id}")
|
| 49 |
+
from .agent import run_claim_to_dir
|
| 50 |
+
|
| 51 |
+
record = claim_record or load_example_claim()
|
| 52 |
+
out_dir = Path(output_dir) if output_dir else new_job_dir(method=method)
|
| 53 |
+
result = run_claim_to_dir(
|
| 54 |
+
claim=str(record["claim"]),
|
| 55 |
+
paper_id=str(record.get("paper_id") or "example"),
|
| 56 |
+
claim_idx=int(record.get("claim_idx") or 0),
|
| 57 |
+
paper_title=str(record.get("paper_title") or ""),
|
| 58 |
+
method=method,
|
| 59 |
+
output_dir=out_dir,
|
| 60 |
+
model_name=model_name,
|
| 61 |
+
)
|
| 62 |
+
result["output_dir"] = str(out_dir)
|
| 63 |
+
result["method_id"] = (
|
| 64 |
+
"codeagent_parametric" if method == "parametric" else "codeagent_websearch_deep"
|
| 65 |
+
)
|
| 66 |
+
return result
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def evaluate_task_a(
|
| 70 |
+
*,
|
| 71 |
+
run_result: dict[str, Any],
|
| 72 |
+
claim_record: Optional[dict[str, Any]] = None,
|
| 73 |
+
judge_model: str = "gemini/gemini-3.1-pro-preview",
|
| 74 |
+
) -> dict[str, Any]:
|
| 75 |
+
"""Judge a finished live run against gold enabling contributions."""
|
| 76 |
+
from .judge import evaluate_prediction
|
| 77 |
+
|
| 78 |
+
record = claim_record or load_example_claim()
|
| 79 |
+
gold = list(record.get("enabling_contributions") or [])
|
| 80 |
+
predicted = list(run_result.get("ingredients") or [])
|
| 81 |
+
judged = evaluate_prediction(
|
| 82 |
+
claim=str(record.get("claim") or run_result.get("claim") or ""),
|
| 83 |
+
gold_ingredients=gold,
|
| 84 |
+
predicted_ingredients=predicted,
|
| 85 |
+
judge_model=judge_model,
|
| 86 |
+
)
|
| 87 |
+
payload = {
|
| 88 |
+
"paper_id": record.get("paper_id"),
|
| 89 |
+
"claim_idx": record.get("claim_idx"),
|
| 90 |
+
"paper_title": record.get("paper_title"),
|
| 91 |
+
"claim": record.get("claim"),
|
| 92 |
+
"method_id": run_result.get("method_id"),
|
| 93 |
+
"method": run_result.get("method"),
|
| 94 |
+
**judged,
|
| 95 |
+
}
|
| 96 |
+
out_dir = run_result.get("output_dir")
|
| 97 |
+
if out_dir:
|
| 98 |
+
path = Path(out_dir) / "task_a_judged.json"
|
| 99 |
+
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
| 100 |
+
payload["judged_path"] = str(path)
|
| 101 |
+
return payload
|
hf_space/scipath_live/prompts.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""SciPath Task A prompts and answer validation (demo-local copy)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
SCIPATH_ROLES = {
|
| 9 |
+
"CORE_METHOD",
|
| 10 |
+
"CONCEPTUAL_FRAMEWORK",
|
| 11 |
+
"DATA_SOURCE",
|
| 12 |
+
"TRAINING_DATA",
|
| 13 |
+
"MODEL_INITIALIZATION",
|
| 14 |
+
"EVALUATION_PROTOCOL",
|
| 15 |
+
"IMPLEMENTATION_TOOLING",
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
TOOLS_GUIDE_PARAMETRIC = ""
|
| 19 |
+
|
| 20 |
+
TOOLS_GUIDE_WEBSEARCH_DEEP = """
|
| 21 |
+
You have access to web search and must use it to get relevant information about the claim.
|
| 22 |
+
<tools>
|
| 23 |
+
- web_search_tool(): get a list of (url,summary of the webpage) from the WWW
|
| 24 |
+
- deep_web_search_tool(): retrieve content from a website given the url. The content is a dictionary with keys 'title', 'url' and 'content'.
|
| 25 |
+
</tools>
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
PROCEDURE_PARAMETRIC = """
|
| 29 |
+
<procedure>
|
| 30 |
+
Step 1. Analyze the discovery claim and identify necessary enabling contributions from your knowledge.
|
| 31 |
+
Step 2. Validate JSON with check_answer_format(), then return final_answer().
|
| 32 |
+
</procedure>
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
PROCEDURE_WEBSEARCH_DEEP = """
|
| 36 |
+
<procedure>
|
| 37 |
+
Step 1. Decompose the claim into a set of searchable short queries.
|
| 38 |
+
Step 2. Start from the first query. Use web_search_tool() to get relevant information about the claim.
|
| 39 |
+
Step 2.1. Based on the snippets, select the most relevant urls from search results and use deep_web_search_tool() on each url to get more relevant context.
|
| 40 |
+
Step 2.2. If the collected information is sufficient, proceed to Step 3. Otherwise, move to the next query and repeat Step 2.
|
| 41 |
+
Step 3. Summarize all the information retrieved from the web.
|
| 42 |
+
Step 4. Synthesize the minimal set of enabling contributions as JSON ingredients.
|
| 43 |
+
Step 5. Validate JSON with check_answer_format(), then return final_answer() with the ingredients object.
|
| 44 |
+
</procedure>
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
SCIPATH_OUTPUT_SPEC = """
|
| 48 |
+
<output>
|
| 49 |
+
Return a JSON object with exactly one top-level key: "ingredients".
|
| 50 |
+
|
| 51 |
+
Each ingredient must have:
|
| 52 |
+
- "description": concise functional description of the enabling contribution
|
| 53 |
+
- "role": one of CORE_METHOD, CONCEPTUAL_FRAMEWORK, DATA_SOURCE, TRAINING_DATA,
|
| 54 |
+
MODEL_INITIALIZATION, EVALUATION_PROTOCOL, IMPLEMENTATION_TOOLING
|
| 55 |
+
- "rationale": one sentence explaining why it is necessary
|
| 56 |
+
|
| 57 |
+
Example:
|
| 58 |
+
{
|
| 59 |
+
"ingredients": [
|
| 60 |
+
{
|
| 61 |
+
"description": "Pretrained transformer encoder for sentence embeddings",
|
| 62 |
+
"role": "MODEL_INITIALIZATION",
|
| 63 |
+
"rationale": "Needed to initialize the downstream model used in the discovery."
|
| 64 |
+
}
|
| 65 |
+
]
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
Rules:
|
| 69 |
+
- Only include necessary enabling contributions for realizing the discovery claim.
|
| 70 |
+
- Exclude generic background, motivation, baselines, and comparison-only datasets.
|
| 71 |
+
- Use concrete strings; never copy placeholder values such as "...".
|
| 72 |
+
- Invoke check_answer_format() on your JSON, then call final_answer() with the same object.
|
| 73 |
+
</output>
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
SCIPATH_TASK_DESCRIPTION = """
|
| 77 |
+
You are forecasting enabling contributions required to realize a scientific discovery claim
|
| 78 |
+
(SciPath Task A, Setting 1: claim only).
|
| 79 |
+
|
| 80 |
+
An enabling contribution is a functional component whose absence would prevent the discovery
|
| 81 |
+
from being realized in its claimed form. It may come from prior work or be paper-specific
|
| 82 |
+
(e.g., a data pipeline or annotation procedure).
|
| 83 |
+
|
| 84 |
+
Use available search/retrieval tools when present to identify plausible prior work, then
|
| 85 |
+
synthesize the minimal set of enabling contributions.
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
SCIPATH_MUST_FOLLOW = """
|
| 89 |
+
<must follow conditions>
|
| 90 |
+
- Gather evidence with tools before producing the final ingredient list when tools are available.
|
| 91 |
+
- Separate evidence gathering from final synthesis across steps.
|
| 92 |
+
- NEVER return final_answer without validating via check_answer_format().
|
| 93 |
+
- Write only one code block per step when executing code.
|
| 94 |
+
</must follow conditions>
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
_METHOD_TOOLS = {
|
| 98 |
+
"parametric": TOOLS_GUIDE_PARAMETRIC,
|
| 99 |
+
"websearch_deep": TOOLS_GUIDE_WEBSEARCH_DEEP,
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
_METHOD_PROCEDURE = {
|
| 103 |
+
"parametric": PROCEDURE_PARAMETRIC,
|
| 104 |
+
"websearch_deep": PROCEDURE_WEBSEARCH_DEEP,
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def get_scipath_controller_prompt(query: str, method: str) -> str:
|
| 109 |
+
tools = _METHOD_TOOLS.get(method, TOOLS_GUIDE_WEBSEARCH_DEEP)
|
| 110 |
+
procedure = _METHOD_PROCEDURE.get(method, PROCEDURE_WEBSEARCH_DEEP)
|
| 111 |
+
return f"""
|
| 112 |
+
{SCIPATH_TASK_DESCRIPTION}
|
| 113 |
+
|
| 114 |
+
{tools}
|
| 115 |
+
|
| 116 |
+
{procedure}
|
| 117 |
+
|
| 118 |
+
{SCIPATH_OUTPUT_SPEC}
|
| 119 |
+
|
| 120 |
+
{SCIPATH_MUST_FOLLOW}
|
| 121 |
+
|
| 122 |
+
Discovery claim:
|
| 123 |
+
{query}
|
| 124 |
+
"""
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def validate_scipath_answer(final_answer_dict: dict) -> tuple[bool, str | None, dict | None]:
|
| 128 |
+
if not isinstance(final_answer_dict, dict):
|
| 129 |
+
return False, "Final answer must be a JSON object.", None
|
| 130 |
+
|
| 131 |
+
ingredients = final_answer_dict.get("ingredients")
|
| 132 |
+
if not isinstance(ingredients, list) or not ingredients:
|
| 133 |
+
return False, "Field 'ingredients' must be a non-empty list.", None
|
| 134 |
+
|
| 135 |
+
placeholder_values = {"", "...", "…", "null", "none", "n/a"}
|
| 136 |
+
cleaned: list[dict[str, str]] = []
|
| 137 |
+
for idx, ingredient in enumerate(ingredients):
|
| 138 |
+
if not isinstance(ingredient, dict):
|
| 139 |
+
return False, f"Ingredient {idx} must be an object.", None
|
| 140 |
+
description = str(ingredient.get("description", "")).strip()
|
| 141 |
+
role = str(ingredient.get("role", "")).strip().upper()
|
| 142 |
+
rationale = str(ingredient.get("rationale", "")).strip()
|
| 143 |
+
if (
|
| 144 |
+
description.lower() in placeholder_values
|
| 145 |
+
or role.lower() in placeholder_values
|
| 146 |
+
or rationale.lower() in placeholder_values
|
| 147 |
+
):
|
| 148 |
+
return False, f"Ingredient {idx} contains placeholder or empty fields.", None
|
| 149 |
+
if role not in SCIPATH_ROLES:
|
| 150 |
+
return False, f"Ingredient {idx} has invalid role {role!r}.", None
|
| 151 |
+
cleaned.append(
|
| 152 |
+
{"description": description, "role": role, "rationale": rationale}
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
return True, None, {"ingredients": cleaned}
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def parse_ingredients_from_answer(answer: Any) -> list[dict]:
|
| 159 |
+
if isinstance(answer, str):
|
| 160 |
+
try:
|
| 161 |
+
answer = json.loads(answer)
|
| 162 |
+
except json.JSONDecodeError:
|
| 163 |
+
return []
|
| 164 |
+
if not isinstance(answer, dict):
|
| 165 |
+
return []
|
| 166 |
+
if isinstance(answer.get("ingredients"), list):
|
| 167 |
+
ok, _, validated = validate_scipath_answer(answer)
|
| 168 |
+
if ok and validated:
|
| 169 |
+
return validated["ingredients"]
|
| 170 |
+
return []
|
hf_space/scipath_live/tools.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tools for SciPath Live: format check, web search, deep page fetch."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
import requests
|
| 12 |
+
from pydantic import ValidationError
|
| 13 |
+
from smolagents import DuckDuckGoSearchTool, tool
|
| 14 |
+
|
| 15 |
+
from .prompts import validate_scipath_answer
|
| 16 |
+
|
| 17 |
+
WEBSEARCH_TOOL = DuckDuckGoSearchTool(max_results=5, rate_limit=2)
|
| 18 |
+
|
| 19 |
+
# Prefer demo-local browser cache over Cursor/temp PLAYWRIGHT_BROWSERS_PATH.
|
| 20 |
+
_DEMO_PLAYWRIGHT_DIR = Path(__file__).resolve().parents[2] / ".playwright"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _ensure_playwright_browsers_path() -> Path:
|
| 24 |
+
_DEMO_PLAYWRIGHT_DIR.mkdir(parents=True, exist_ok=True)
|
| 25 |
+
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = str(_DEMO_PLAYWRIGHT_DIR)
|
| 26 |
+
return _DEMO_PLAYWRIGHT_DIR
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _parse_search_results(search_output: str) -> list[dict[str, str]]:
|
| 30 |
+
evidence_items: list[dict[str, str]] = []
|
| 31 |
+
pattern = r"\[([^\]]+)\]\(([^\)]+)\)(.*?)(?=\[|$)"
|
| 32 |
+
for match in re.finditer(pattern, search_output or "", re.DOTALL):
|
| 33 |
+
title = match.group(1).strip()
|
| 34 |
+
url = match.group(2).strip()
|
| 35 |
+
content = match.group(3).strip()
|
| 36 |
+
summary = content.split("·", 1)[1].strip() if "·" in content else content
|
| 37 |
+
summary = " ".join(summary.split()).rstrip("…").strip()
|
| 38 |
+
if summary:
|
| 39 |
+
evidence_items.append(
|
| 40 |
+
{
|
| 41 |
+
"type": "web_search_result",
|
| 42 |
+
"source": url,
|
| 43 |
+
"summary": f"{title}. {summary}",
|
| 44 |
+
}
|
| 45 |
+
)
|
| 46 |
+
return evidence_items
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _get_pdf_content(url: str) -> dict[str, str]:
|
| 50 |
+
try:
|
| 51 |
+
import PyPDF2
|
| 52 |
+
|
| 53 |
+
response = requests.get(url, timeout=30)
|
| 54 |
+
response.raise_for_status()
|
| 55 |
+
pdf_reader = PyPDF2.PdfReader(BytesIO(response.content))
|
| 56 |
+
pages = []
|
| 57 |
+
for i, page in enumerate(pdf_reader.pages):
|
| 58 |
+
pages.append(page.extract_text() or "")
|
| 59 |
+
if i >= 29:
|
| 60 |
+
break
|
| 61 |
+
content = "\n\n".join(pages)
|
| 62 |
+
if "Abstract" in content:
|
| 63 |
+
content = content.split("Abstract", 1)[1]
|
| 64 |
+
title = "PDF Document"
|
| 65 |
+
if pdf_reader.metadata and getattr(pdf_reader.metadata, "title", None):
|
| 66 |
+
title = pdf_reader.metadata.title or title
|
| 67 |
+
return {"title": title, "url": url, "content": content}
|
| 68 |
+
except Exception as exc:
|
| 69 |
+
return {"title": "Error", "url": url, "content": f"Error extracting PDF: {exc}"}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def get_webpage(url: str) -> dict[str, str]:
|
| 73 |
+
"""Fetch webpage or PDF content (Playwright for HTML, PyPDF2 for PDFs)."""
|
| 74 |
+
if "sciencedirect" in url:
|
| 75 |
+
return {
|
| 76 |
+
"title": "ScienceDirect Article",
|
| 77 |
+
"url": url,
|
| 78 |
+
"content": "Automatically fetching content from ScienceDirect is not supported.",
|
| 79 |
+
}
|
| 80 |
+
if url.lower().endswith(".pdf"):
|
| 81 |
+
return _get_pdf_content(url)
|
| 82 |
+
|
| 83 |
+
try:
|
| 84 |
+
from playwright.sync_api import sync_playwright
|
| 85 |
+
except Exception as exc:
|
| 86 |
+
return {
|
| 87 |
+
"title": "Error",
|
| 88 |
+
"url": url,
|
| 89 |
+
"content": f"Playwright unavailable: {exc}",
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
browsers_dir = _ensure_playwright_browsers_path()
|
| 93 |
+
|
| 94 |
+
def _launch_browser(playwright_api: Any) -> Any:
|
| 95 |
+
# Prefer installed Google Chrome (matches host arch) when available.
|
| 96 |
+
try:
|
| 97 |
+
return playwright_api.chromium.launch(channel="chrome", headless=True)
|
| 98 |
+
except Exception:
|
| 99 |
+
return playwright_api.chromium.launch(headless=True)
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
with sync_playwright() as p:
|
| 103 |
+
browser = _launch_browser(p)
|
| 104 |
+
try:
|
| 105 |
+
page = browser.new_page()
|
| 106 |
+
page.goto(url, timeout=15000)
|
| 107 |
+
page.wait_for_load_state("domcontentloaded")
|
| 108 |
+
title = page.title() or ""
|
| 109 |
+
text = (
|
| 110 |
+
page.inner_text("body")
|
| 111 |
+
if page.locator("body").count()
|
| 112 |
+
else page.content()
|
| 113 |
+
)
|
| 114 |
+
# Cap content size for the agent context.
|
| 115 |
+
text = (text or "")[:40000]
|
| 116 |
+
return {"title": title, "url": page.url, "content": text}
|
| 117 |
+
except Exception:
|
| 118 |
+
return _get_pdf_content(url)
|
| 119 |
+
finally:
|
| 120 |
+
browser.close()
|
| 121 |
+
except Exception as exc:
|
| 122 |
+
msg = str(exc)
|
| 123 |
+
if "Executable doesn't exist" in msg or "browserType.launch" in msg.lower():
|
| 124 |
+
return {
|
| 125 |
+
"title": "Error",
|
| 126 |
+
"url": url,
|
| 127 |
+
"content": (
|
| 128 |
+
"Playwright Chromium is not installed for this demo. "
|
| 129 |
+
f"Run: bash scripts/setup_playwright.sh "
|
| 130 |
+
f"(PLAYWRIGHT_BROWSERS_PATH={browsers_dir})"
|
| 131 |
+
),
|
| 132 |
+
}
|
| 133 |
+
return {"title": "Error", "url": url, "content": f"Playwright error: {exc}"}
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
@tool
|
| 137 |
+
def check_answer_format(final_answer_dict: dict) -> Any:
|
| 138 |
+
"""Validate the final SciPath ingredients JSON format.
|
| 139 |
+
|
| 140 |
+
Args:
|
| 141 |
+
final_answer_dict: the final answer/output that is to be returned to the user
|
| 142 |
+
"""
|
| 143 |
+
try:
|
| 144 |
+
ok, err, validated = validate_scipath_answer(final_answer_dict)
|
| 145 |
+
return ok, err, validated
|
| 146 |
+
except ValidationError as e:
|
| 147 |
+
return False, str(e), None
|
| 148 |
+
except Exception as e:
|
| 149 |
+
return False, f"Unexpected error: {e}", None
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@tool
|
| 153 |
+
def web_search_tool(short_search_query: str) -> list[dict]:
|
| 154 |
+
"""Perform a web search and return structured results.
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
short_search_query: A concise search query (typically 1-6 words)
|
| 158 |
+
"""
|
| 159 |
+
search_results = WEBSEARCH_TOOL(short_search_query)
|
| 160 |
+
return _parse_search_results(search_results)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
@tool
|
| 164 |
+
def deep_web_search_tool(url: str) -> dict:
|
| 165 |
+
"""Fetch and return webpage/PDF content for a URL.
|
| 166 |
+
|
| 167 |
+
Args:
|
| 168 |
+
url: the url of a webpage
|
| 169 |
+
"""
|
| 170 |
+
return get_webpage(url)
|
hf_space/streamlit_app.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
hf_space/streamlit_config.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from workflow_graph import STEP_COPY
|
| 2 |
+
|
| 3 |
+
EXAMPLES = {
|
| 4 |
+
"Paper 1": "https://arxiv.org/abs/2208.00329",
|
| 5 |
+
# AveriTeC demo hidden from the picker for now.
|
| 6 |
+
# "Paper 2 (AveriTeC)": "https://arxiv.org/abs/2505.17978",
|
| 7 |
+
"Paper 2": "https://arxiv.org/abs/2211.08788",
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
# System Run setting picker labels (match SciPaths Dev case-study report).
|
| 11 |
+
SYSTEM_RUN_METHODS = {
|
| 12 |
+
"Parametric - LLM Only": "codeagent_parametric",
|
| 13 |
+
"Websearch Deep - LLM + Web Search + Crawl": "codeagent_websearch_deep",
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
TAB_NAMES = [
|
| 17 |
+
"Run",
|
| 18 |
+
"Clusters",
|
| 19 |
+
"Decomposition",
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
APP_VIEWS = [
|
| 23 |
+
"Annotation Process",
|
| 24 |
+
"System Run",
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
# Left-nav items (mina-style icon + label rows). Icons use Streamlit Material symbols.
|
| 28 |
+
APP_NAV = [
|
| 29 |
+
{
|
| 30 |
+
"view": "Annotation Process",
|
| 31 |
+
"icon": ":material/account_tree:",
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"view": "System Run",
|
| 35 |
+
"icon": ":material/play_circle:",
|
| 36 |
+
},
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
METHOD_NOTES = {
|
| 40 |
+
"Pipeline scope": "Runs steps 0, 1, 2, 3, 4, 5, 6, and 8, then launches cluster-first two-pass annotation.",
|
| 41 |
+
"Input": "Accepts a single arXiv URL or arXiv ID.",
|
| 42 |
+
"Cluster-first annotation": "Uses all refined downstream USES/EXTENDS clusters to derive target contributions, then decomposes each target contribution separately.",
|
| 43 |
+
"Stopping rule": "If no valid downstream usage clusters remain after refinement and filtering, annotation is skipped.",
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
DISPLAY_STEPS = [1, 2, 3, 4, 5, 6, 7, 8]
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def pipeline_steps_markdown() -> str:
|
| 50 |
+
return "\n".join(f"{idx}. {STEP_COPY[idx]}" for idx in DISPLAY_STEPS)
|
hf_space/system_live_runner.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Streamlit helpers for System Run → Live Run (self-contained scipath_live)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import subprocess
|
| 8 |
+
import sys
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Any, Callable, Optional
|
| 11 |
+
|
| 12 |
+
from scipath_live.pipeline import (
|
| 13 |
+
DEFAULT_RUNS_ROOT,
|
| 14 |
+
load_example_claim,
|
| 15 |
+
new_job_dir,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
HF_SPACE = Path(__file__).resolve().parent
|
| 19 |
+
DEMO_ROOT = HF_SPACE.parent
|
| 20 |
+
CLI = HF_SPACE / "scipath_live" / "cli.py"
|
| 21 |
+
# Stable browser cache for this demo (avoids Cursor sandbox temp paths).
|
| 22 |
+
PLAYWRIGHT_BROWSERS_DIR = DEMO_ROOT / ".playwright"
|
| 23 |
+
|
| 24 |
+
METHOD_UI = [
|
| 25 |
+
{"id": "codeagent_parametric", "cli": "parametric", "label": "Parametric - LLM Only"},
|
| 26 |
+
{
|
| 27 |
+
"id": "codeagent_websearch_deep",
|
| 28 |
+
"cli": "websearch_deep",
|
| 29 |
+
"label": "Websearch Deep - LLM + Web Search + Crawl",
|
| 30 |
+
},
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def example_claim() -> dict[str, Any]:
|
| 35 |
+
return load_example_claim()
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _env_for_child() -> dict[str, str]:
|
| 39 |
+
env = dict(os.environ)
|
| 40 |
+
# Prefer demo-local .env if present.
|
| 41 |
+
for candidate in (HF_SPACE / ".env", HF_SPACE.parent / ".env"):
|
| 42 |
+
if not candidate.exists():
|
| 43 |
+
continue
|
| 44 |
+
try:
|
| 45 |
+
from dotenv import dotenv_values
|
| 46 |
+
|
| 47 |
+
for key, value in dotenv_values(candidate).items():
|
| 48 |
+
if value and key not in env:
|
| 49 |
+
env[key] = value
|
| 50 |
+
except Exception:
|
| 51 |
+
pass
|
| 52 |
+
google = (
|
| 53 |
+
env.get("GOOGLE_GENAI_API_KEY")
|
| 54 |
+
or env.get("GEMINI_API_KEY")
|
| 55 |
+
or env.get("GOOGLE_API_KEY")
|
| 56 |
+
or ""
|
| 57 |
+
).strip()
|
| 58 |
+
if google:
|
| 59 |
+
env["GEMINI_API_KEY"] = google
|
| 60 |
+
env["GOOGLE_API_KEY"] = google
|
| 61 |
+
env["GOOGLE_GENAI_API_KEY"] = google
|
| 62 |
+
env["PYTHONPATH"] = (
|
| 63 |
+
f"{HF_SPACE}{os.pathsep}{env.get('PYTHONPATH', '')}".rstrip(os.pathsep)
|
| 64 |
+
)
|
| 65 |
+
# Stream agent prints line-by-line into the Live Run terminal.
|
| 66 |
+
env["PYTHONUNBUFFERED"] = "1"
|
| 67 |
+
env["PYTHONIOENCODING"] = "utf-8"
|
| 68 |
+
# Always use demo-local Playwright browsers (override Cursor sandbox cache).
|
| 69 |
+
PLAYWRIGHT_BROWSERS_DIR.mkdir(parents=True, exist_ok=True)
|
| 70 |
+
env["PLAYWRIGHT_BROWSERS_PATH"] = str(PLAYWRIGHT_BROWSERS_DIR)
|
| 71 |
+
return env
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _is_result_payload_line(line: str) -> bool:
|
| 75 |
+
text = (line or "").strip()
|
| 76 |
+
return text.startswith("{") and '"ok"' in text
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _append_trace_from_logs_jsonl(
|
| 80 |
+
job_dir: Path, log_callback: Optional[Callable[[str], None]]
|
| 81 |
+
) -> None:
|
| 82 |
+
"""Fallback: if stdout missed the trace, reconstruct from logs.jsonl."""
|
| 83 |
+
if not log_callback:
|
| 84 |
+
return
|
| 85 |
+
path = job_dir / "logs.jsonl"
|
| 86 |
+
if not path.exists():
|
| 87 |
+
return
|
| 88 |
+
try:
|
| 89 |
+
from scipath_live.agent import format_agent_trace
|
| 90 |
+
|
| 91 |
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
| 92 |
+
steps = payload.get("steps") or []
|
| 93 |
+
for line in format_agent_trace(steps):
|
| 94 |
+
log_callback(line)
|
| 95 |
+
except Exception as exc:
|
| 96 |
+
log_callback(f"[warn] Could not load agent trace: {exc}")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def run_live_method(
|
| 100 |
+
method_id: str,
|
| 101 |
+
*,
|
| 102 |
+
log_callback: Optional[Callable[[str], None]] = None,
|
| 103 |
+
model_name: str = "gemini-3-flash-preview",
|
| 104 |
+
) -> dict[str, Any]:
|
| 105 |
+
"""Subprocess-run a method; stream stdout lines via log_callback."""
|
| 106 |
+
cli_method = next((m["cli"] for m in METHOD_UI if m["id"] == method_id), None)
|
| 107 |
+
if not cli_method:
|
| 108 |
+
raise ValueError(f"Unknown method: {method_id}")
|
| 109 |
+
|
| 110 |
+
job_dir = new_job_dir(DEFAULT_RUNS_ROOT, method=cli_method)
|
| 111 |
+
cmd = [
|
| 112 |
+
sys.executable,
|
| 113 |
+
str(CLI),
|
| 114 |
+
"run",
|
| 115 |
+
"--method",
|
| 116 |
+
cli_method,
|
| 117 |
+
"--output-dir",
|
| 118 |
+
str(job_dir),
|
| 119 |
+
"--model",
|
| 120 |
+
model_name,
|
| 121 |
+
]
|
| 122 |
+
if log_callback:
|
| 123 |
+
log_callback(f"$ {' '.join(cmd)}")
|
| 124 |
+
|
| 125 |
+
proc = subprocess.Popen(
|
| 126 |
+
cmd,
|
| 127 |
+
cwd=str(HF_SPACE),
|
| 128 |
+
env=_env_for_child(),
|
| 129 |
+
stdout=subprocess.PIPE,
|
| 130 |
+
stderr=subprocess.STDOUT,
|
| 131 |
+
text=True,
|
| 132 |
+
bufsize=1,
|
| 133 |
+
)
|
| 134 |
+
assert proc.stdout is not None
|
| 135 |
+
last_json = ""
|
| 136 |
+
saw_trace = False
|
| 137 |
+
for line in proc.stdout:
|
| 138 |
+
line = line.rstrip("\n")
|
| 139 |
+
if not line:
|
| 140 |
+
continue
|
| 141 |
+
if _is_result_payload_line(line):
|
| 142 |
+
# Keep machine payload out of the human terminal.
|
| 143 |
+
last_json = line
|
| 144 |
+
continue
|
| 145 |
+
if "SciFy CodeAgent trace" in line:
|
| 146 |
+
saw_trace = True
|
| 147 |
+
if log_callback:
|
| 148 |
+
log_callback(line)
|
| 149 |
+
code = proc.wait()
|
| 150 |
+
if code != 0:
|
| 151 |
+
raise RuntimeError(f"Live run failed with exit code {code}")
|
| 152 |
+
|
| 153 |
+
if last_json:
|
| 154 |
+
payload = json.loads(last_json)
|
| 155 |
+
if not payload.get("ok"):
|
| 156 |
+
raise RuntimeError(payload.get("error") or "Live run failed")
|
| 157 |
+
result = payload["result"]
|
| 158 |
+
else:
|
| 159 |
+
# Fallback: read result.json written by the agent.
|
| 160 |
+
result_path = job_dir / "result.json"
|
| 161 |
+
if not result_path.exists():
|
| 162 |
+
raise RuntimeError("Live run produced no result.json")
|
| 163 |
+
result = json.loads(result_path.read_text(encoding="utf-8"))
|
| 164 |
+
result["output_dir"] = str(job_dir)
|
| 165 |
+
result["method_id"] = method_id
|
| 166 |
+
|
| 167 |
+
if not saw_trace:
|
| 168 |
+
_append_trace_from_logs_jsonl(job_dir, log_callback)
|
| 169 |
+
|
| 170 |
+
result.setdefault("output_dir", str(job_dir))
|
| 171 |
+
result.setdefault("method_id", method_id)
|
| 172 |
+
return result
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def evaluate_live_run(
|
| 176 |
+
run_result: dict[str, Any],
|
| 177 |
+
*,
|
| 178 |
+
log_callback: Optional[Callable[[str], None]] = None,
|
| 179 |
+
judge_model: str = "gemini/gemini-3.1-pro-preview",
|
| 180 |
+
) -> dict[str, Any]:
|
| 181 |
+
result_json = Path(run_result.get("output_dir") or ".") / "result.json"
|
| 182 |
+
if not result_json.exists():
|
| 183 |
+
# Write a minimal result file for the CLI.
|
| 184 |
+
result_json.parent.mkdir(parents=True, exist_ok=True)
|
| 185 |
+
result_json.write_text(
|
| 186 |
+
json.dumps(run_result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
cmd = [
|
| 190 |
+
sys.executable,
|
| 191 |
+
str(CLI),
|
| 192 |
+
"evaluate",
|
| 193 |
+
"--result-json",
|
| 194 |
+
str(result_json),
|
| 195 |
+
"--judge-model",
|
| 196 |
+
judge_model,
|
| 197 |
+
]
|
| 198 |
+
if log_callback:
|
| 199 |
+
log_callback(f"$ {' '.join(cmd)}")
|
| 200 |
+
|
| 201 |
+
proc = subprocess.run(
|
| 202 |
+
cmd,
|
| 203 |
+
cwd=str(HF_SPACE),
|
| 204 |
+
env=_env_for_child(),
|
| 205 |
+
capture_output=True,
|
| 206 |
+
text=True,
|
| 207 |
+
)
|
| 208 |
+
if log_callback and proc.stdout:
|
| 209 |
+
for line in proc.stdout.splitlines():
|
| 210 |
+
log_callback(line)
|
| 211 |
+
if proc.returncode != 0:
|
| 212 |
+
err = (proc.stderr or proc.stdout or "").strip()
|
| 213 |
+
raise RuntimeError(err or f"Evaluate failed with exit code {proc.returncode}")
|
| 214 |
+
|
| 215 |
+
# Last JSON line is the payload.
|
| 216 |
+
payload = None
|
| 217 |
+
for line in reversed((proc.stdout or "").splitlines()):
|
| 218 |
+
if line.startswith("{"):
|
| 219 |
+
payload = json.loads(line)
|
| 220 |
+
break
|
| 221 |
+
if not payload or not payload.get("ok"):
|
| 222 |
+
raise RuntimeError((payload or {}).get("error") or "Evaluate produced no result")
|
| 223 |
+
return payload["judged"]
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def live_result_to_case_card(run_result: dict[str, Any], judged: Optional[dict] = None) -> dict[str, Any]:
|
| 227 |
+
"""Normalize live artifacts into the System Run case-card shape."""
|
| 228 |
+
method_id = run_result.get("method_id") or "codeagent_parametric"
|
| 229 |
+
method_label = next(
|
| 230 |
+
(m["label"] for m in METHOD_UI if m["id"] == method_id), method_id
|
| 231 |
+
)
|
| 232 |
+
predicted = []
|
| 233 |
+
source_pred = (judged or {}).get("predicted_ingredients") or run_result.get("ingredients") or []
|
| 234 |
+
for i, item in enumerate(source_pred, start=1):
|
| 235 |
+
if not isinstance(item, dict):
|
| 236 |
+
continue
|
| 237 |
+
predicted.append(
|
| 238 |
+
{
|
| 239 |
+
"idx": i,
|
| 240 |
+
"description": str(item.get("description") or ""),
|
| 241 |
+
"role": str(item.get("role") or ""),
|
| 242 |
+
"rationale": str(item.get("rationale") or ""),
|
| 243 |
+
}
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
if judged:
|
| 247 |
+
gold = []
|
| 248 |
+
for j in judged.get("recall_judgments") or []:
|
| 249 |
+
gold.append(
|
| 250 |
+
{
|
| 251 |
+
"description": str(j.get("reference_ingredient") or ""),
|
| 252 |
+
"role": str(j.get("reference_role") or ""),
|
| 253 |
+
"badge": (
|
| 254 |
+
"full"
|
| 255 |
+
if j.get("covered")
|
| 256 |
+
else ("partial" if j.get("partial_matches") else "miss")
|
| 257 |
+
),
|
| 258 |
+
"match_notes": [],
|
| 259 |
+
"judge_note": str(j.get("reasoning") or ""),
|
| 260 |
+
"pair_pred_idx": j.get("best_match_idx"),
|
| 261 |
+
}
|
| 262 |
+
)
|
| 263 |
+
# Reconstruct match notes for partials
|
| 264 |
+
for pm in j.get("partial_matches") or []:
|
| 265 |
+
gold[-1]["match_notes"].append(
|
| 266 |
+
f"Partial ↔ pred #{pm.get('predicted_idx')}: {pm.get('reasoning', '')}"
|
| 267 |
+
)
|
| 268 |
+
if gold[-1]["badge"] == "miss":
|
| 269 |
+
gold[-1]["badge"] = "partial"
|
| 270 |
+
# Live Run is not framed as success/failure — always yellow (part).
|
| 271 |
+
f1 = float(judged.get("f1") or 0.0)
|
| 272 |
+
return {
|
| 273 |
+
"method_id": method_id,
|
| 274 |
+
"method_label": method_label.split(" - ")[0],
|
| 275 |
+
"case_key": "live",
|
| 276 |
+
"case_chip": f"{method_label.split(' - ')[0]} · Live",
|
| 277 |
+
"outcome_tone": "part",
|
| 278 |
+
"tools": (
|
| 279 |
+
["check_answer_format"]
|
| 280 |
+
if "parametric" in method_id
|
| 281 |
+
else ["web_search_tool", "deep_web_search_tool", "check_answer_format"]
|
| 282 |
+
),
|
| 283 |
+
"paper_id": judged.get("paper_id") or run_result.get("paper_id"),
|
| 284 |
+
"claim_idx": judged.get("claim_idx") or run_result.get("claim_idx"),
|
| 285 |
+
"paper_title": judged.get("paper_title") or run_result.get("paper_title"),
|
| 286 |
+
"claim": judged.get("claim") or run_result.get("claim"),
|
| 287 |
+
"recall": float(judged.get("recall") or 0.0),
|
| 288 |
+
"precision": float(judged.get("precision") or 0.0),
|
| 289 |
+
"f1": f1,
|
| 290 |
+
"gold_ingredients": gold,
|
| 291 |
+
"predicted_ingredients": predicted,
|
| 292 |
+
"tool_panel": {"kind": "none"},
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
# Pre-evaluate: predictions only.
|
| 296 |
+
return {
|
| 297 |
+
"method_id": method_id,
|
| 298 |
+
"method_label": method_label.split(" - ")[0],
|
| 299 |
+
"case_key": "featured",
|
| 300 |
+
"case_chip": f"{method_label.split(' - ')[0]} · Live (unevaluated)",
|
| 301 |
+
"outcome_tone": "part",
|
| 302 |
+
"tools": (
|
| 303 |
+
["check_answer_format"]
|
| 304 |
+
if "parametric" in method_id
|
| 305 |
+
else ["web_search_tool", "deep_web_search_tool", "check_answer_format"]
|
| 306 |
+
),
|
| 307 |
+
"paper_id": run_result.get("paper_id"),
|
| 308 |
+
"claim_idx": run_result.get("claim_idx"),
|
| 309 |
+
"paper_title": run_result.get("paper_title"),
|
| 310 |
+
"claim": run_result.get("claim"),
|
| 311 |
+
"recall": 0.0,
|
| 312 |
+
"precision": 0.0,
|
| 313 |
+
"f1": 0.0,
|
| 314 |
+
"gold_ingredients": [],
|
| 315 |
+
"predicted_ingredients": predicted,
|
| 316 |
+
"tool_panel": {"kind": "none"},
|
| 317 |
+
}
|
hf_space/system_run_cases/case_study_analysis.json
ADDED
|
@@ -0,0 +1,829 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"title": "SciPaths Dev Case Studies across CodeAgent settings",
|
| 3 |
+
"analysis_model": "gemini/gemini-3.1-pro-preview",
|
| 4 |
+
"settings": {
|
| 5 |
+
"codeagent_parametric": {
|
| 6 |
+
"tools": [
|
| 7 |
+
"check_answer_format"
|
| 8 |
+
],
|
| 9 |
+
"summary_meta": {
|
| 10 |
+
"source": "gold",
|
| 11 |
+
"gold_file": "/Users/dingzifeng/SciFy-UMBC/dryrun/ForecastingDiscoveryPathways-7FCC/archive/dev.json",
|
| 12 |
+
"gold_claim_field": "rewritten_capability",
|
| 13 |
+
"setting": 1,
|
| 14 |
+
"generator_model": "gemini-3-flash-preview",
|
| 15 |
+
"generator_backend": "codeagent",
|
| 16 |
+
"codeagent_method": "parametric",
|
| 17 |
+
"n_claims": 50,
|
| 18 |
+
"n_total_claims": 50,
|
| 19 |
+
"n_errors": 0,
|
| 20 |
+
"mean_n_predicted": 4.56
|
| 21 |
+
},
|
| 22 |
+
"success": {
|
| 23 |
+
"paper_id": "neurips-2023-poster-2075",
|
| 24 |
+
"claim_idx": 0,
|
| 25 |
+
"paper_title": "Pick-a-Pic: An Open Dataset of User Preferences for Text-to-Image Generation",
|
| 26 |
+
"claim": "Tool: A human-preference prompt-conditioned image scorer, enabling automatic ranking of text-to-image model outputs",
|
| 27 |
+
"recall": 1.0,
|
| 28 |
+
"precision": 0.75,
|
| 29 |
+
"f1": 0.857,
|
| 30 |
+
"predicted_ingredients": [
|
| 31 |
+
{
|
| 32 |
+
"description": "Large-scale dataset of human pairwise preferences for generated images",
|
| 33 |
+
"role": "TRAINING_DATA",
|
| 34 |
+
"rationale": "Necessary to provide the ground-truth human judgments required to train the scorer for alignment with subjective preferences."
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"description": "Pre-trained vision-language model backbone (e.g., CLIP)",
|
| 38 |
+
"role": "MODEL_INITIALIZATION",
|
| 39 |
+
"rationale": "Provides the foundational cross-modal representations needed to assess the semantic relationship between text prompts and visual content."
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"description": "Prompt-conditioned reward modeling architecture and objective",
|
| 43 |
+
"role": "CORE_METHOD",
|
| 44 |
+
"rationale": "Defines the specific neural network structure and loss function used to map image-text pairs to a scalar preference score."
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"description": "Human-preference alignment evaluation benchmark",
|
| 48 |
+
"role": "EVALUATION_PROTOCOL",
|
| 49 |
+
"rationale": "Required to validate that the automated scorer's rankings accurately reflect human preferences on unseen samples."
|
| 50 |
+
}
|
| 51 |
+
],
|
| 52 |
+
"recall_judgments": [
|
| 53 |
+
{
|
| 54 |
+
"reference_idx": 1,
|
| 55 |
+
"reference_ingredient": "Pre-trained vision-language encoder for prompt-conditioned image scoring",
|
| 56 |
+
"reference_role": "MODEL_INITIALIZATION",
|
| 57 |
+
"covered": true,
|
| 58 |
+
"best_match_idx": 2,
|
| 59 |
+
"reasoning": "Both describe the use of a pre-trained vision-language model (like CLIP) to initialize the cross-modal representations.",
|
| 60 |
+
"best_match": {
|
| 61 |
+
"description": "Pre-trained vision-language model backbone (e.g., CLIP)",
|
| 62 |
+
"role": "MODEL_INITIALIZATION",
|
| 63 |
+
"rationale": "Provides the foundational cross-modal representations needed to assess the semantic relationship between text prompts and visual content."
|
| 64 |
+
},
|
| 65 |
+
"partial_matches": []
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"reference_idx": 2,
|
| 69 |
+
"reference_ingredient": "Large-scale human-preference dataset over text-to-image generations",
|
| 70 |
+
"reference_role": "TRAINING_DATA",
|
| 71 |
+
"covered": true,
|
| 72 |
+
"best_match_idx": 1,
|
| 73 |
+
"reasoning": "Both explicitly identify the large-scale dataset of pairwise human preferences over generated images as the training data.",
|
| 74 |
+
"best_match": {
|
| 75 |
+
"description": "Large-scale dataset of human pairwise preferences for generated images",
|
| 76 |
+
"role": "TRAINING_DATA",
|
| 77 |
+
"rationale": "Necessary to provide the ground-truth human judgments required to train the scorer for alignment with subjective preferences."
|
| 78 |
+
},
|
| 79 |
+
"partial_matches": []
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"reference_idx": 3,
|
| 83 |
+
"reference_ingredient": "Pairwise preference-learning objective for reward-style image ranking",
|
| 84 |
+
"reference_role": "CORE_METHOD",
|
| 85 |
+
"covered": true,
|
| 86 |
+
"best_match_idx": 3,
|
| 87 |
+
"reasoning": "Both specify the reward-modeling/preference-learning objective required to train the scoring function to reflect human preferences.",
|
| 88 |
+
"best_match": {
|
| 89 |
+
"description": "Prompt-conditioned reward modeling architecture and objective",
|
| 90 |
+
"role": "CORE_METHOD",
|
| 91 |
+
"rationale": "Defines the specific neural network structure and loss function used to map image-text pairs to a scalar preference score."
|
| 92 |
+
},
|
| 93 |
+
"partial_matches": []
|
| 94 |
+
}
|
| 95 |
+
],
|
| 96 |
+
"matched_pairs": [
|
| 97 |
+
{
|
| 98 |
+
"reference_idx": 1,
|
| 99 |
+
"predicted_idx": 2,
|
| 100 |
+
"match": "full",
|
| 101 |
+
"reasoning": "Both describe the use of a pre-trained vision-language model (like CLIP) to initialize the cross-modal representations."
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"reference_idx": 2,
|
| 105 |
+
"predicted_idx": 1,
|
| 106 |
+
"match": "full",
|
| 107 |
+
"reasoning": "Both explicitly identify the large-scale dataset of pairwise human preferences over generated images as the training data."
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"reference_idx": 3,
|
| 111 |
+
"predicted_idx": 3,
|
| 112 |
+
"match": "full",
|
| 113 |
+
"reasoning": "Both specify the reward-modeling/preference-learning objective required to train the scoring function to reflect human preferences."
|
| 114 |
+
}
|
| 115 |
+
],
|
| 116 |
+
"partial_matches": [],
|
| 117 |
+
"log_line": 19,
|
| 118 |
+
"n_steps": 2,
|
| 119 |
+
"tool_summary": {
|
| 120 |
+
"searches": [],
|
| 121 |
+
"retrieves": [],
|
| 122 |
+
"deeps": []
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"failure": {
|
| 126 |
+
"paper_id": "neurips-2024-poster-219",
|
| 127 |
+
"claim_idx": 0,
|
| 128 |
+
"paper_title": "RGFN: Synthesizable Molecular Generation Using GFlowNets",
|
| 129 |
+
"claim": "Method: A generative framework for synthesizable molecular design, enabling generation of molecular candidates together with feasible synthesis pathways.",
|
| 130 |
+
"recall": 0.0,
|
| 131 |
+
"precision": 0.0,
|
| 132 |
+
"f1": 0.0,
|
| 133 |
+
"predicted_ingredients": [
|
| 134 |
+
{
|
| 135 |
+
"description": "Markov Decision Process (MDP) formulation of molecular synthesis",
|
| 136 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 137 |
+
"rationale": "Frames the generation of molecules as a sequence of discrete synthetic steps, ensuring the output is synthesizable by construction."
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"description": "Library of reaction templates extracted from reaction databases (e.g., USPTO)",
|
| 141 |
+
"role": "DATA_SOURCE",
|
| 142 |
+
"rationale": "Provides the set of valid chemical transformations that the generative model can use to build molecules."
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"description": "Database of commercially available building blocks",
|
| 146 |
+
"role": "DATA_SOURCE",
|
| 147 |
+
"rationale": "Serves as the required starting materials for any proposed synthesis pathway."
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"description": "Neural policy network for reaction and building block selection",
|
| 151 |
+
"role": "CORE_METHOD",
|
| 152 |
+
"rationale": "The primary engine that learns to navigate the chemical space by selecting optimal synthetic actions."
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"description": "Synthetic feasibility and property evaluation protocol",
|
| 156 |
+
"role": "EVALUATION_PROTOCOL",
|
| 157 |
+
"rationale": "Used to validate that the generated molecules meet the desired chemical properties and that the pathways are realistic."
|
| 158 |
+
}
|
| 159 |
+
],
|
| 160 |
+
"recall_judgments": [
|
| 161 |
+
{
|
| 162 |
+
"reference_idx": 1,
|
| 163 |
+
"reference_ingredient": "Generative Flow Network (GFlowNet) framework trained with trajectory balance",
|
| 164 |
+
"reference_role": "CORE_METHOD",
|
| 165 |
+
"covered": false,
|
| 166 |
+
"best_match_idx": null,
|
| 167 |
+
"reasoning": "",
|
| 168 |
+
"best_match": null,
|
| 169 |
+
"partial_matches": [
|
| 170 |
+
{
|
| 171 |
+
"reference_idx": 1,
|
| 172 |
+
"predicted_idx": 1,
|
| 173 |
+
"match": "partial",
|
| 174 |
+
"reasoning": "The predicted item captures the sequential MDP formulation underlying the generation process but is too broad and misses the specific GFlowNet framework and trajectory balance."
|
| 175 |
+
}
|
| 176 |
+
]
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"reference_idx": 2,
|
| 180 |
+
"reference_ingredient": "Graph transformer architecture for state and action representation",
|
| 181 |
+
"reference_role": "CORE_METHOD",
|
| 182 |
+
"covered": false,
|
| 183 |
+
"best_match_idx": null,
|
| 184 |
+
"reasoning": "",
|
| 185 |
+
"best_match": null,
|
| 186 |
+
"partial_matches": [
|
| 187 |
+
{
|
| 188 |
+
"reference_idx": 2,
|
| 189 |
+
"predicted_idx": 4,
|
| 190 |
+
"match": "partial",
|
| 191 |
+
"reasoning": "The predicted item identifies the neural policy network used to navigate the space but lacks the specific graph transformer architecture required by the reference."
|
| 192 |
+
}
|
| 193 |
+
]
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"reference_idx": 3,
|
| 197 |
+
"reference_ingredient": "Predefined action space of robust chemical reaction templates and molecular building blocks",
|
| 198 |
+
"reference_role": "CONCEPTUAL_FRAMEWORK",
|
| 199 |
+
"covered": false,
|
| 200 |
+
"best_match_idx": null,
|
| 201 |
+
"reasoning": "",
|
| 202 |
+
"best_match": null,
|
| 203 |
+
"partial_matches": [
|
| 204 |
+
{
|
| 205 |
+
"reference_idx": 3,
|
| 206 |
+
"predicted_idx": 2,
|
| 207 |
+
"match": "partial",
|
| 208 |
+
"reasoning": "The predicted item captures the reaction templates portion of the reference's predefined action space but omits the molecular building blocks."
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"reference_idx": 3,
|
| 212 |
+
"predicted_idx": 3,
|
| 213 |
+
"match": "partial",
|
| 214 |
+
"reasoning": "The predicted item captures the molecular building blocks portion of the reference's action space but omits the reaction templates."
|
| 215 |
+
}
|
| 216 |
+
]
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"reference_idx": 4,
|
| 220 |
+
"reference_ingredient": "Fingerprint-based action embedding scheme for scalable fragment selection",
|
| 221 |
+
"reference_role": "CORE_METHOD",
|
| 222 |
+
"covered": false,
|
| 223 |
+
"best_match_idx": null,
|
| 224 |
+
"reasoning": "",
|
| 225 |
+
"best_match": null,
|
| 226 |
+
"partial_matches": []
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
"reference_idx": 5,
|
| 230 |
+
"reference_ingredient": "Training loss of the model",
|
| 231 |
+
"reference_role": "CORE_METHOD",
|
| 232 |
+
"covered": false,
|
| 233 |
+
"best_match_idx": null,
|
| 234 |
+
"reasoning": "",
|
| 235 |
+
"best_match": null,
|
| 236 |
+
"partial_matches": []
|
| 237 |
+
}
|
| 238 |
+
],
|
| 239 |
+
"matched_pairs": [],
|
| 240 |
+
"partial_matches": [
|
| 241 |
+
{
|
| 242 |
+
"reference_idx": 1,
|
| 243 |
+
"predicted_idx": 1,
|
| 244 |
+
"match": "partial",
|
| 245 |
+
"reasoning": "The predicted item captures the sequential MDP formulation underlying the generation process but is too broad and misses the specific GFlowNet framework and trajectory balance."
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"reference_idx": 2,
|
| 249 |
+
"predicted_idx": 4,
|
| 250 |
+
"match": "partial",
|
| 251 |
+
"reasoning": "The predicted item identifies the neural policy network used to navigate the space but lacks the specific graph transformer architecture required by the reference."
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"reference_idx": 3,
|
| 255 |
+
"predicted_idx": 2,
|
| 256 |
+
"match": "partial",
|
| 257 |
+
"reasoning": "The predicted item captures the reaction templates portion of the reference's predefined action space but omits the molecular building blocks."
|
| 258 |
+
},
|
| 259 |
+
{
|
| 260 |
+
"reference_idx": 3,
|
| 261 |
+
"predicted_idx": 3,
|
| 262 |
+
"match": "partial",
|
| 263 |
+
"reasoning": "The predicted item captures the molecular building blocks portion of the reference's action space but omits the reaction templates."
|
| 264 |
+
}
|
| 265 |
+
],
|
| 266 |
+
"log_line": 38,
|
| 267 |
+
"n_steps": 2,
|
| 268 |
+
"tool_summary": {
|
| 269 |
+
"searches": [],
|
| 270 |
+
"retrieves": [],
|
| 271 |
+
"deeps": []
|
| 272 |
+
}
|
| 273 |
+
},
|
| 274 |
+
"gemini_analysis": {
|
| 275 |
+
"tooling_clarification": "No external search or retrieval tools were provided for evidence gathering. The agent relied entirely on its internal parametric memory, using tools only to verify the output format.",
|
| 276 |
+
"success_why": [
|
| 277 |
+
"The model successfully recalled the exact architecture and training data for 'Pick-a-Pic', a prominent 2023 text-to-image alignment dataset.",
|
| 278 |
+
"It accurately identified the CLIP backbone, the large-scale human preference dataset, and the reward modeling objective without needing external grounding."
|
| 279 |
+
],
|
| 280 |
+
"success_tool_role": [
|
| 281 |
+
"No evidence tools were used.",
|
| 282 |
+
"Success was driven entirely by the LLM's strong parametric knowledge of highly cited, recent (2023) multimodal AI research."
|
| 283 |
+
],
|
| 284 |
+
"failure_diagnosis": "Faced with a highly specific 2024 molecular design paper (RGFN), the model hallucinated a generic, plausible pipeline for synthesizable molecular generation (MDP, USPTO templates, neural policy network) but completely missed the actual GFlowNet methodology.",
|
| 285 |
+
"failure_error_bullets": [
|
| 286 |
+
"[Missed core method] Failed to identify the GFlowNet framework and graph transformer architectures.",
|
| 287 |
+
"[Wrong specificity] Predicted a generic Markov Decision Process (MDP) instead of the specific GFlowNet trained with trajectory balance.",
|
| 288 |
+
"[Missing data/source] Missed the fingerprint-based action embedding scheme."
|
| 289 |
+
],
|
| 290 |
+
"failure_what_needed": [
|
| 291 |
+
"External retrieval of the paper's abstract to ground the generic claim ('generative framework for synthesizable molecular design') to the specific RGFN/GFlowNet methodology."
|
| 292 |
+
]
|
| 293 |
+
}
|
| 294 |
+
},
|
| 295 |
+
"codeagent_retrieval_plus_websearch": {
|
| 296 |
+
"tools": [
|
| 297 |
+
"retrieve_documents",
|
| 298 |
+
"web_search_tool",
|
| 299 |
+
"check_answer_format"
|
| 300 |
+
],
|
| 301 |
+
"summary_meta": {
|
| 302 |
+
"source": "gold",
|
| 303 |
+
"gold_file": "/Users/dingzifeng/SciFy-UMBC/dryrun/ForecastingDiscoveryPathways-7FCC/archive/dev.json",
|
| 304 |
+
"gold_claim_field": "rewritten_capability",
|
| 305 |
+
"setting": 1,
|
| 306 |
+
"generator_model": "gemini-3-flash-preview",
|
| 307 |
+
"generator_backend": "codeagent",
|
| 308 |
+
"codeagent_method": "retrieval_plus_websearch",
|
| 309 |
+
"n_claims": 50,
|
| 310 |
+
"n_total_claims": 50,
|
| 311 |
+
"n_errors": 0,
|
| 312 |
+
"mean_n_predicted": 4.7
|
| 313 |
+
},
|
| 314 |
+
"case": {
|
| 315 |
+
"paper_id": "2023.acl-short.123",
|
| 316 |
+
"claim_idx": 0,
|
| 317 |
+
"paper_title": "An Embarrassingly Easy but Strong Baseline for Nested Named Entity Recognition",
|
| 318 |
+
"claim": "Tool: A standardized preprocessing and split-construction workflow for nested NER benchmarks, enabling fair comparison across models.",
|
| 319 |
+
"recall": 0.5,
|
| 320 |
+
"precision": 0.2,
|
| 321 |
+
"f1": 0.286,
|
| 322 |
+
"predicted_ingredients": [
|
| 323 |
+
{
|
| 324 |
+
"description": "Original nested NER datasets (e.g., GENIA, ACE 2004, ACE 2005)",
|
| 325 |
+
"role": "DATA_SOURCE",
|
| 326 |
+
"rationale": "These provide the raw annotated text data that the workflow standardizes for benchmarking purposes."
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
"description": "Unified data representation format for nested entities",
|
| 330 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 331 |
+
"rationale": "A consistent schema is necessary to represent overlapping spans across diverse datasets in a single format."
|
| 332 |
+
},
|
| 333 |
+
{
|
| 334 |
+
"description": "Automated preprocessing pipeline for tokenization and span alignment",
|
| 335 |
+
"role": "CORE_METHOD",
|
| 336 |
+
"rationale": "Ensures that all datasets are processed using identical linguistic rules to prevent bias from different tokenizers."
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"description": "Standardized split-construction protocol (e.g., Lu and Roth splits)",
|
| 340 |
+
"role": "EVALUATION_PROTOCOL",
|
| 341 |
+
"rationale": "Guarantees that models are compared on identical subsets of the data to maintain experimental fairness."
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"description": "Nested span evaluation metric implementation",
|
| 345 |
+
"role": "EVALUATION_PROTOCOL",
|
| 346 |
+
"rationale": "Required to accurately and consistently measure performance on overlapping entity structures across different models."
|
| 347 |
+
}
|
| 348 |
+
],
|
| 349 |
+
"recall_judgments": [
|
| 350 |
+
{
|
| 351 |
+
"reference_idx": 1,
|
| 352 |
+
"reference_ingredient": "Raw nested NER corpora",
|
| 353 |
+
"reference_role": "DATA_SOURCE",
|
| 354 |
+
"covered": true,
|
| 355 |
+
"best_match_idx": 1,
|
| 356 |
+
"reasoning": "Both identify the original nested NER corpora (such as ACE2004, ACE2005, and GENIA) that provide the raw annotated text data for standardization.",
|
| 357 |
+
"best_match": {
|
| 358 |
+
"description": "Original nested NER datasets (e.g., GENIA, ACE 2004, ACE 2005)",
|
| 359 |
+
"role": "DATA_SOURCE",
|
| 360 |
+
"rationale": "These provide the raw annotated text data that the workflow standardizes for benchmarking purposes."
|
| 361 |
+
},
|
| 362 |
+
"partial_matches": []
|
| 363 |
+
},
|
| 364 |
+
{
|
| 365 |
+
"reference_idx": 2,
|
| 366 |
+
"reference_ingredient": "Tokenization and annotation correction heuristics",
|
| 367 |
+
"reference_role": "IMPLEMENTATION_TOOLING",
|
| 368 |
+
"covered": false,
|
| 369 |
+
"best_match_idx": null,
|
| 370 |
+
"reasoning": "",
|
| 371 |
+
"best_match": null,
|
| 372 |
+
"partial_matches": [
|
| 373 |
+
{
|
| 374 |
+
"reference_idx": 2,
|
| 375 |
+
"predicted_idx": 3,
|
| 376 |
+
"match": "partial",
|
| 377 |
+
"reasoning": "Both refer to the tokenization and span alignment process, but the reference specifies the underlying heuristics to fix tokenization and annotation conflicts, whereas the prediction describes the broader automated pipeline."
|
| 378 |
+
}
|
| 379 |
+
]
|
| 380 |
+
}
|
| 381 |
+
],
|
| 382 |
+
"retrieved_doc_titles": [
|
| 383 |
+
"Statistical Learning for Accurate and Interpretable Battery Lifetime Prediction",
|
| 384 |
+
"Accurate Performance Characterization, Reporting, and Benchmarking for Indoor Photovoltaics",
|
| 385 |
+
"Efficient implicit integration for finite-strain viscoplasticity with a nested multiplicative split",
|
| 386 |
+
"Prediction of charge separation in GaAs/AlAs cylindrical nanostructures",
|
| 387 |
+
"Effect of the structure of lead iodine perovskites on the photovoltaic efficiencies",
|
| 388 |
+
"The high strain-rate behaviour of three molecular weights of polyethylene examined with a magnesium alloy split-Hopkinson pressure bar",
|
| 389 |
+
"Landau Theory of Charge and Spin Ordering in the Nickelates",
|
| 390 |
+
"Excitonic effects in two-dimensional semiconductors: Path integral Monte Carlo approach"
|
| 391 |
+
],
|
| 392 |
+
"gemini_analysis": {
|
| 393 |
+
"model": "gemini/gemini-3.1-pro-preview",
|
| 394 |
+
"focus": "performance when retrieval returned docs",
|
| 395 |
+
"headline": "Even with web search plus returned retrieval docs, the pathway stayed weakly grounded: retrieval was off-domain, so the model leaned on parametric knowledge and over-generated (F1 0.29).",
|
| 396 |
+
"what_was_retrieved": [
|
| 397 |
+
"Web search was used in most steps (alongside retrieval early, then alone later), with queries about nested NER preprocessing / benchmark standardization.",
|
| 398 |
+
"Retrieval returned documents from unrelated domains (materials science, physics, battery lifetime), likely via polysemous terms such as \"nested\", \"split\", and \"benchmarking\".",
|
| 399 |
+
"No retrieved documents were about NLP / Named Entity Recognition."
|
| 400 |
+
],
|
| 401 |
+
"effect_on_prediction": [
|
| 402 |
+
"With off-domain retrieval and no strong grounding from tools, the model fell back on parametric knowledge of nested NER workflows.",
|
| 403 |
+
"That fallback recovered some recall (e.g., raw nested NER corpora / GENIA & ACE-style datasets) and a partial match on tokenization preprocessing.",
|
| 404 |
+
"Without relevant tool text to constrain the pathway, it also over-generated extra workflow pieces (unified format, evaluation metric), hurting precision (P=0.2)."
|
| 405 |
+
],
|
| 406 |
+
"error_taxonomy_bullets": [
|
| 407 |
+
"[Domain Mismatch in Retrieval] Returned abstracts matched generic keywords to the wrong scientific fields, so retrieval did not surface the gold ingredients.",
|
| 408 |
+
"[Web search under-used for grounding] Despite repeated nested-NER web queries, the final ingredients still look like a generic preprocessing pipeline rather than the paper-specific tokenization/annotation heuristics.",
|
| 409 |
+
"[Parametric Reliance + Over-generation] The model filled gaps from memory and added plausible but non-gold steps, lowering precision."
|
| 410 |
+
],
|
| 411 |
+
"takeaway": "Web search ran throughout the trajectory, including after documents were returned. Retrieval returned materials/physics papers rather than nested-NER sources, so neither tool provided the specific gold ingredients.",
|
| 412 |
+
"web_search_also_used": true,
|
| 413 |
+
"web_search_queries_example": [
|
| 414 |
+
"standardized preprocessing nested NER benchmarks workflow",
|
| 415 |
+
"nested NER benchmarks fair comparison tool",
|
| 416 |
+
"nested NER dataset preprocessing standardization",
|
| 417 |
+
"standardizing nested NER benchmarks"
|
| 418 |
+
]
|
| 419 |
+
}
|
| 420 |
+
}
|
| 421 |
+
},
|
| 422 |
+
"codeagent_websearch_deep": {
|
| 423 |
+
"tools": [
|
| 424 |
+
"web_search_tool",
|
| 425 |
+
"deep_web_search_tool",
|
| 426 |
+
"check_answer_format"
|
| 427 |
+
],
|
| 428 |
+
"summary_meta": {
|
| 429 |
+
"generator_model": "gemini-3-flash-preview",
|
| 430 |
+
"generator_backend": "codeagent",
|
| 431 |
+
"codeagent_method": "websearch_deep",
|
| 432 |
+
"mean_f1": 0.318,
|
| 433 |
+
"n_claims": 50
|
| 434 |
+
},
|
| 435 |
+
"success": {
|
| 436 |
+
"paper_id": "neurips-2025-spotlight-472",
|
| 437 |
+
"claim_idx": 1,
|
| 438 |
+
"paper_title": "ShapeLLM-Omni: A Native Multimodal LLM for 3D Generation and Understanding",
|
| 439 |
+
"claim": "Resource: A native 3D multimodal language model, enabling unified text, image, and 3D generation, understanding, and language-guided editing.",
|
| 440 |
+
"recall": 1.0,
|
| 441 |
+
"precision": 1.0,
|
| 442 |
+
"f1": 1.0,
|
| 443 |
+
"predicted_ingredients": [
|
| 444 |
+
{
|
| 445 |
+
"description": "3D Vector-Quantized Variational Autoencoder (VQVAE)",
|
| 446 |
+
"role": "CORE_METHOD",
|
| 447 |
+
"rationale": "Necessary to map 3D objects into a discrete latent space of 3D-aware tokens that the LLM can natively process and generate."
|
| 448 |
+
},
|
| 449 |
+
{
|
| 450 |
+
"description": "3D-Alpaca Dataset",
|
| 451 |
+
"role": "TRAINING_DATA",
|
| 452 |
+
"rationale": "A large-scale instruction-tuning dataset required to train the model on unified 3D generation, understanding, and editing tasks."
|
| 453 |
+
},
|
| 454 |
+
{
|
| 455 |
+
"description": "Qwen-2.5-vl-7B-Instruct",
|
| 456 |
+
"role": "MODEL_INITIALIZATION",
|
| 457 |
+
"rationale": "Provides the foundational multimodal (text and image) capabilities that are extended to include 3D functionality."
|
| 458 |
+
},
|
| 459 |
+
{
|
| 460 |
+
"description": "Unified Token-based Multimodal Architecture",
|
| 461 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 462 |
+
"rationale": "Enables the model to treat 3D tokens as a native modality alongside text and images for interleaved generation and understanding."
|
| 463 |
+
}
|
| 464 |
+
],
|
| 465 |
+
"recall_judgments": [
|
| 466 |
+
{
|
| 467 |
+
"reference_idx": 1,
|
| 468 |
+
"reference_ingredient": "Pretrained multimodal vision-language backbone",
|
| 469 |
+
"reference_role": "MODEL_INITIALIZATION",
|
| 470 |
+
"covered": true,
|
| 471 |
+
"best_match_idx": 3,
|
| 472 |
+
"reasoning": "Both identify the pretrained Qwen-2.5-VL vision-language model used as the foundational backbone for initialization.",
|
| 473 |
+
"best_match": {
|
| 474 |
+
"description": "Qwen-2.5-vl-7B-Instruct",
|
| 475 |
+
"role": "MODEL_INITIALIZATION",
|
| 476 |
+
"rationale": "Provides the foundational multimodal (text and image) capabilities that are extended to include 3D functionality."
|
| 477 |
+
},
|
| 478 |
+
"partial_matches": []
|
| 479 |
+
},
|
| 480 |
+
{
|
| 481 |
+
"reference_idx": 2,
|
| 482 |
+
"reference_ingredient": "Discrete 3D tokenization through a 3D VQVAE",
|
| 483 |
+
"reference_role": "CORE_METHOD",
|
| 484 |
+
"covered": true,
|
| 485 |
+
"best_match_idx": 1,
|
| 486 |
+
"reasoning": "Both describe the use of a 3D VQVAE to convert 3D objects into discrete tokens that the model can process natively.",
|
| 487 |
+
"best_match": {
|
| 488 |
+
"description": "3D Vector-Quantized Variational Autoencoder (VQVAE)",
|
| 489 |
+
"role": "CORE_METHOD",
|
| 490 |
+
"rationale": "Necessary to map 3D objects into a discrete latent space of 3D-aware tokens that the LLM can natively process and generate."
|
| 491 |
+
},
|
| 492 |
+
"partial_matches": []
|
| 493 |
+
},
|
| 494 |
+
{
|
| 495 |
+
"reference_idx": 3,
|
| 496 |
+
"reference_ingredient": "Fully autoregressive early-fusion next-token modeling over text, image-conditioned inputs, and 3D tokens",
|
| 497 |
+
"reference_role": "CONCEPTUAL_FRAMEWORK, CORE_METHOD",
|
| 498 |
+
"covered": true,
|
| 499 |
+
"best_match_idx": 4,
|
| 500 |
+
"reasoning": "Both refer to the unified, token-based conceptual framework that treats 3D, text, and image tokens in a single interleaved architecture.",
|
| 501 |
+
"best_match": {
|
| 502 |
+
"description": "Unified Token-based Multimodal Architecture",
|
| 503 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 504 |
+
"rationale": "Enables the model to treat 3D tokens as a native modality alongside text and images for interleaved generation and understanding."
|
| 505 |
+
},
|
| 506 |
+
"partial_matches": []
|
| 507 |
+
},
|
| 508 |
+
{
|
| 509 |
+
"reference_idx": 4,
|
| 510 |
+
"reference_ingredient": "3D instruction-tuning corpus",
|
| 511 |
+
"reference_role": "TRAINING_DATA",
|
| 512 |
+
"covered": true,
|
| 513 |
+
"best_match_idx": 2,
|
| 514 |
+
"reasoning": "Both specify the 3D-Alpaca instruction-tuning dataset used to train and adapt the model for 3D tasks.",
|
| 515 |
+
"best_match": {
|
| 516 |
+
"description": "3D-Alpaca Dataset",
|
| 517 |
+
"role": "TRAINING_DATA",
|
| 518 |
+
"rationale": "A large-scale instruction-tuning dataset required to train the model on unified 3D generation, understanding, and editing tasks."
|
| 519 |
+
},
|
| 520 |
+
"partial_matches": []
|
| 521 |
+
}
|
| 522 |
+
],
|
| 523 |
+
"matched_pairs": [
|
| 524 |
+
{
|
| 525 |
+
"reference_idx": 1,
|
| 526 |
+
"predicted_idx": 3,
|
| 527 |
+
"match": "full",
|
| 528 |
+
"reasoning": "Both identify the pretrained Qwen-2.5-VL vision-language model used as the foundational backbone for initialization."
|
| 529 |
+
},
|
| 530 |
+
{
|
| 531 |
+
"reference_idx": 2,
|
| 532 |
+
"predicted_idx": 1,
|
| 533 |
+
"match": "full",
|
| 534 |
+
"reasoning": "Both describe the use of a 3D VQVAE to convert 3D objects into discrete tokens that the model can process natively."
|
| 535 |
+
},
|
| 536 |
+
{
|
| 537 |
+
"reference_idx": 3,
|
| 538 |
+
"predicted_idx": 4,
|
| 539 |
+
"match": "full",
|
| 540 |
+
"reasoning": "Both refer to the unified, token-based conceptual framework that treats 3D, text, and image tokens in a single interleaved architecture."
|
| 541 |
+
},
|
| 542 |
+
{
|
| 543 |
+
"reference_idx": 4,
|
| 544 |
+
"predicted_idx": 2,
|
| 545 |
+
"match": "full",
|
| 546 |
+
"reasoning": "Both specify the 3D-Alpaca instruction-tuning dataset used to train and adapt the model for 3D tasks."
|
| 547 |
+
}
|
| 548 |
+
]
|
| 549 |
+
},
|
| 550 |
+
"failure": {
|
| 551 |
+
"paper_id": "neurips-2023-poster-1534",
|
| 552 |
+
"claim_idx": 0,
|
| 553 |
+
"paper_title": "Continuous-Time Functional Diffusion Processes",
|
| 554 |
+
"claim": "Method: A diffusion-based generative framework for continuous function spaces, enabling modeling of continuous and irregularly sampled data.",
|
| 555 |
+
"recall": 0.0,
|
| 556 |
+
"precision": 0.0,
|
| 557 |
+
"f1": 0.0,
|
| 558 |
+
"predicted_ingredients": [
|
| 559 |
+
{
|
| 560 |
+
"description": "Denoising diffusion probabilistic modeling adapted for finite-dimensional marginals of functions",
|
| 561 |
+
"role": "CORE_METHOD",
|
| 562 |
+
"rationale": "Provides the generative mechanism to sample from functional distributions by reversing a noise process applied to function values at arbitrary locations."
|
| 563 |
+
},
|
| 564 |
+
{
|
| 565 |
+
"description": "Permutation-invariant attention-based architecture",
|
| 566 |
+
"role": "IMPLEMENTATION_TOOLING",
|
| 567 |
+
"rationale": "Ensures the model can process sets of input-output pairs of varying sizes while satisfying the exchangeability property required for stochastic processes."
|
| 568 |
+
},
|
| 569 |
+
{
|
| 570 |
+
"description": "Conceptual framework of exchangeability and consistency for stochastic processes",
|
| 571 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 572 |
+
"rationale": "Provides the theoretical foundation for defining a valid distribution over an infinite-dimensional function space via its finite marginals."
|
| 573 |
+
},
|
| 574 |
+
{
|
| 575 |
+
"description": "Coordinate-based representation of data observations",
|
| 576 |
+
"role": "CONCEPTUAL_FRAMEWORK",
|
| 577 |
+
"rationale": "Enables the model to handle irregularly sampled data by treating observations as evaluations of a function at specific continuous coordinates."
|
| 578 |
+
},
|
| 579 |
+
{
|
| 580 |
+
"description": "Score-based or denoising objective defined over function values at given coordinates",
|
| 581 |
+
"role": "CORE_METHOD",
|
| 582 |
+
"rationale": "Allows the model to learn the underlying structure of the function space by predicting noise or scores at arbitrary input points."
|
| 583 |
+
}
|
| 584 |
+
],
|
| 585 |
+
"recall_judgments": [
|
| 586 |
+
{
|
| 587 |
+
"reference_idx": 1,
|
| 588 |
+
"reference_ingredient": "Score-based generative modeling with continuous-time stochastic differential equations",
|
| 589 |
+
"reference_role": "CONCEPTUAL_FRAMEWORK",
|
| 590 |
+
"covered": false,
|
| 591 |
+
"best_match_idx": null,
|
| 592 |
+
"reasoning": "",
|
| 593 |
+
"best_match": null,
|
| 594 |
+
"partial_matches": [
|
| 595 |
+
{
|
| 596 |
+
"reference_idx": 1,
|
| 597 |
+
"predicted_idx": 1,
|
| 598 |
+
"match": "partial",
|
| 599 |
+
"reasoning": "Both identify the core diffusion generative mechanism, but the reference specifies a continuous-time SDE formulation while the prediction focuses on adapting diffusion for finite marginals."
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
"reference_idx": 1,
|
| 603 |
+
"predicted_idx": 5,
|
| 604 |
+
"match": "partial",
|
| 605 |
+
"reasoning": "Both highlight the score-based or denoising formulation, though the prediction restricts it to function values at given coordinates."
|
| 606 |
+
}
|
| 607 |
+
]
|
| 608 |
+
},
|
| 609 |
+
{
|
| 610 |
+
"reference_idx": 2,
|
| 611 |
+
"reference_ingredient": "Infinite-dimensional stochastic differential equation framework for forward and reverse diffusion in Hilbert spaces",
|
| 612 |
+
"reference_role": "CONCEPTUAL_FRAMEWORK",
|
| 613 |
+
"covered": false,
|
| 614 |
+
"best_match_idx": null,
|
| 615 |
+
"reasoning": "",
|
| 616 |
+
"best_match": null,
|
| 617 |
+
"partial_matches": [
|
| 618 |
+
{
|
| 619 |
+
"reference_idx": 2,
|
| 620 |
+
"predicted_idx": 3,
|
| 621 |
+
"match": "partial",
|
| 622 |
+
"reasoning": "Both provide the theoretical foundation for defining distributions over infinite-dimensional function spaces, but use different mathematical frameworks (Hilbert space SDEs versus exchangeability/consistency)."
|
| 623 |
+
}
|
| 624 |
+
]
|
| 625 |
+
},
|
| 626 |
+
{
|
| 627 |
+
"reference_idx": 3,
|
| 628 |
+
"reference_ingredient": "Countable-evaluation formulation that connects continuous functions to discrete observations for practical learning and inference",
|
| 629 |
+
"reference_role": "CORE_METHOD",
|
| 630 |
+
"covered": false,
|
| 631 |
+
"best_match_idx": null,
|
| 632 |
+
"reasoning": "",
|
| 633 |
+
"best_match": null,
|
| 634 |
+
"partial_matches": [
|
| 635 |
+
{
|
| 636 |
+
"reference_idx": 3,
|
| 637 |
+
"predicted_idx": 1,
|
| 638 |
+
"match": "partial",
|
| 639 |
+
"reasoning": "Both address the need to connect continuous function spaces to discrete observations (countable evaluations versus finite-dimensional marginals) for practical inference."
|
| 640 |
+
},
|
| 641 |
+
{
|
| 642 |
+
"reference_idx": 3,
|
| 643 |
+
"predicted_idx": 4,
|
| 644 |
+
"match": "partial",
|
| 645 |
+
"reasoning": "Both focus on bridging continuous functions with discrete data points, though they describe it differently (countable-evaluation formulation versus coordinate-based representations)."
|
| 646 |
+
}
|
| 647 |
+
]
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"reference_idx": 4,
|
| 651 |
+
"reference_ingredient": "Implicit neural representation-based practical implementation of the functional score network",
|
| 652 |
+
"reference_role": "CORE_METHOD",
|
| 653 |
+
"covered": false,
|
| 654 |
+
"best_match_idx": null,
|
| 655 |
+
"reasoning": "",
|
| 656 |
+
"best_match": null,
|
| 657 |
+
"partial_matches": [
|
| 658 |
+
{
|
| 659 |
+
"reference_idx": 4,
|
| 660 |
+
"predicted_idx": 4,
|
| 661 |
+
"match": "partial",
|
| 662 |
+
"reasoning": "Implicit neural representations are a form of coordinate-based representation, but the prediction describes applying them to data observations rather than the functional score network."
|
| 663 |
+
}
|
| 664 |
+
]
|
| 665 |
+
}
|
| 666 |
+
],
|
| 667 |
+
"partial_matches": [
|
| 668 |
+
{
|
| 669 |
+
"reference_idx": 1,
|
| 670 |
+
"predicted_idx": 1,
|
| 671 |
+
"match": "partial",
|
| 672 |
+
"reasoning": "Both identify the core diffusion generative mechanism, but the reference specifies a continuous-time SDE formulation while the prediction focuses on adapting diffusion for finite marginals."
|
| 673 |
+
},
|
| 674 |
+
{
|
| 675 |
+
"reference_idx": 1,
|
| 676 |
+
"predicted_idx": 5,
|
| 677 |
+
"match": "partial",
|
| 678 |
+
"reasoning": "Both highlight the score-based or denoising formulation, though the prediction restricts it to function values at given coordinates."
|
| 679 |
+
},
|
| 680 |
+
{
|
| 681 |
+
"reference_idx": 2,
|
| 682 |
+
"predicted_idx": 3,
|
| 683 |
+
"match": "partial",
|
| 684 |
+
"reasoning": "Both provide the theoretical foundation for defining distributions over infinite-dimensional function spaces, but use different mathematical frameworks (Hilbert space SDEs versus exchangeability/consistency)."
|
| 685 |
+
},
|
| 686 |
+
{
|
| 687 |
+
"reference_idx": 3,
|
| 688 |
+
"predicted_idx": 1,
|
| 689 |
+
"match": "partial",
|
| 690 |
+
"reasoning": "Both address the need to connect continuous function spaces to discrete observations (countable evaluations versus finite-dimensional marginals) for practical inference."
|
| 691 |
+
},
|
| 692 |
+
{
|
| 693 |
+
"reference_idx": 3,
|
| 694 |
+
"predicted_idx": 4,
|
| 695 |
+
"match": "partial",
|
| 696 |
+
"reasoning": "Both focus on bridging continuous functions with discrete data points, though they describe it differently (countable-evaluation formulation versus coordinate-based representations)."
|
| 697 |
+
},
|
| 698 |
+
{
|
| 699 |
+
"reference_idx": 4,
|
| 700 |
+
"predicted_idx": 4,
|
| 701 |
+
"match": "partial",
|
| 702 |
+
"reasoning": "Implicit neural representations are a form of coordinate-based representation, but the prediction describes applying them to data observations rather than the functional score network."
|
| 703 |
+
}
|
| 704 |
+
]
|
| 705 |
+
},
|
| 706 |
+
"gemini_analysis": {
|
| 707 |
+
"model": "gemini/gemini-3.1-pro-preview",
|
| 708 |
+
"focus": "tool-aware (web_search_tool + deep_web_search_tool)",
|
| 709 |
+
"tooling_clarification": "The agent uses `web_search_tool` to retrieve search engine snippets and `deep_web_search_tool` to fetch and read full webpage content. There is no 'crawl' tool; `deep_web_search_tool` is the sole mechanism for extracting full-text page content.",
|
| 710 |
+
"success_search_contribution": [
|
| 711 |
+
"Formulated targeted queries (e.g., 'native 3D multimodal language model unified text image 3D generation') to locate primary and secondary sources for ShapeLLM-Omni.",
|
| 712 |
+
"Successfully identified the project page ('jamesyjl.github.io/ShapeLLM/') as a viable alternative source.",
|
| 713 |
+
"Used follow-up searches ('ShapeLLM-Omni 3D-Alpaca dataset source Objaverse') to verify specific entities like '3D-Alpaca' and '3D-aware discrete tokens' found in earlier steps."
|
| 714 |
+
],
|
| 715 |
+
"success_deep_contribution": [
|
| 716 |
+
"Attempted to fetch the arXiv HTML ('https://arxiv.org/html/2506.01853v1') but encountered a PDF parse error ('No /Root object!'), prompting a necessary pivot.",
|
| 717 |
+
"Successfully fetched the project page ('https://jamesyjl.github.io/ShapeLLM/'), extracting exact architectural details ('3D VQVAE', 'Qwen-2.5-vl-7B-Instruct') and dataset names ('3D-Alpaca') that perfectly aligned with the gold ingredients."
|
| 718 |
+
],
|
| 719 |
+
"success_why_overall": [
|
| 720 |
+
"The agent demonstrated tool resilience by pivoting to a project page when the primary arXiv HTML deep-fetch failed.",
|
| 721 |
+
"The deep-fetched project page contained highly specific, structured information about the method's architecture and training data, which the agent accurately synthesized into the final ingredients."
|
| 722 |
+
],
|
| 723 |
+
"failure_diagnosis": "The agent successfully surfaced the correct paper via search but failed to deep-fetch any of its URLs, instead deep-fetching and extracting ingredients from a neighboring method (Neural Diffusion Processes).",
|
| 724 |
+
"failure_search_what_went_wrong": [
|
| 725 |
+
"Search successfully found the correct target paper ('Continuous-Time Functional Diffusion Processes') and its URLs (NeurIPS abstract, arXiv HTML, GitHub project page) in steps 5 and 7.",
|
| 726 |
+
"Search snippets contained highly relevant gold terms (e.g., 'hilbert-spaces', 'stochastic-partial-differential-equations'), but the agent ignored them.",
|
| 727 |
+
"The agent incorrectly pivoted its search strategy to query a neighboring method ('Neural Diffusion Processes Dutordoir ICML 2023 architecture')."
|
| 728 |
+
],
|
| 729 |
+
"failure_deep_what_went_wrong": [
|
| 730 |
+
"Never called `deep_web_search_tool` on the correct FDP URLs found in search (e.g., 'arxiv.org/html/2303.00800v3').",
|
| 731 |
+
"Wasted deep-fetch calls on guessed/wrong arXiv IDs (e.g., 2303.01631, 2205.15306) which returned completely unrelated papers.",
|
| 732 |
+
"Deep-fetched OpenReview and PMLR pages for the wrong paper ('Neural Diffusion Processes'), leading to the extraction of incorrect methodological details."
|
| 733 |
+
],
|
| 734 |
+
"failure_error_bullets": [
|
| 735 |
+
"[Substitutes neighboring method] The agent extracted ingredients for 'Neural Diffusion Processes' (finite marginals, exchangeability, attention) instead of the gold 'Functional Diffusion Processes' (Hilbert-space SDEs, countable evaluation, INR) because it deep-fetched the wrong paper's PMLR page.",
|
| 736 |
+
"[Candidate retrieval failure] Although `web_search_tool` surfaced the correct URLs, the agent failed to retrieve their full content via `deep_web_search_tool`, relying instead on deep-fetches of incorrect papers.",
|
| 737 |
+
"[Missed core method] By focusing on the wrong paper's deep-fetched content, the agent completely missed the core continuous-time SDE and Hilbert space framework of the target paper."
|
| 738 |
+
],
|
| 739 |
+
"failure_what_needed": [
|
| 740 |
+
"The agent needed to pass the correct URLs found by `web_search_tool` (e.g., 'https://arxiv.org/html/2303.00800v3') directly into `deep_web_search_tool` instead of guessing arXiv IDs or pivoting to fetch pages for Neural Diffusion Processes."
|
| 741 |
+
]
|
| 742 |
+
},
|
| 743 |
+
"tool_traces": {
|
| 744 |
+
"tools_available": {
|
| 745 |
+
"web_search_tool": "Broad web search; returns list of {source URL, summary}. NOT a crawler.",
|
| 746 |
+
"deep_web_search_tool": "Given a URL, fetches webpage/PDF content as {title,url,content}. This is the page-fetch / crawl-like tool. There is NO separate tool named crawl.",
|
| 747 |
+
"check_answer_format": "Validates final JSON schema."
|
| 748 |
+
},
|
| 749 |
+
"success": {
|
| 750 |
+
"paper_id": "neurips-2025-spotlight-472",
|
| 751 |
+
"claim_idx": 1,
|
| 752 |
+
"n_steps": 4,
|
| 753 |
+
"web_search_queries": [
|
| 754 |
+
"native 3D multimodal language model unified text image 3D generation",
|
| 755 |
+
"3D multimodal language model understanding and language-guided editing",
|
| 756 |
+
"native 3D-LLM text image 3D generation understanding editing",
|
| 757 |
+
"ShapeLLM-Omni 3D-Alpaca dataset source Objaverse"
|
| 758 |
+
],
|
| 759 |
+
"web_search_hits_notable": [
|
| 760 |
+
"arxiv.org/html/2506.01853v1 ShapeLLM-Omni",
|
| 761 |
+
"neurips.cc/virtual/2025/poster/116179 mentioning 3D-Alpaca",
|
| 762 |
+
"project page jamesyjl.github.io/ShapeLLM/"
|
| 763 |
+
],
|
| 764 |
+
"deep_fetches": [
|
| 765 |
+
{
|
| 766 |
+
"url": "https://arxiv.org/html/2506.01853v1",
|
| 767 |
+
"outcome": "FAILED: Error fetching webpage: No /Root object! - Is this really a PDF?"
|
| 768 |
+
},
|
| 769 |
+
{
|
| 770 |
+
"url": "https://jamesyjl.github.io/ShapeLLM/",
|
| 771 |
+
"outcome": "SUCCESS: project page content mentioning ShapeLLM-Omni, 3D tokens, training dataset details, backbone mentions"
|
| 772 |
+
}
|
| 773 |
+
],
|
| 774 |
+
"final_ingredients": [
|
| 775 |
+
"3D VQVAE",
|
| 776 |
+
"3D-Alpaca Dataset",
|
| 777 |
+
"Qwen-2.5-vl-7B-Instruct",
|
| 778 |
+
"Unified Token-based Multimodal Architecture"
|
| 779 |
+
]
|
| 780 |
+
},
|
| 781 |
+
"failure": {
|
| 782 |
+
"paper_id": "neurips-2023-poster-1534",
|
| 783 |
+
"claim_idx": 0,
|
| 784 |
+
"n_steps": 17,
|
| 785 |
+
"web_search_queries_sample": [
|
| 786 |
+
"diffusion-based generative framework for continuous function spaces",
|
| 787 |
+
"score-based generative modeling in function space",
|
| 788 |
+
"Functional Diffusion Processes NeurIPS 2023",
|
| 789 |
+
"Continuous-Time Functional Diffusion Processes irregularly sampled data",
|
| 790 |
+
"Neural Diffusion Processes Dutordoir ICML 2023 architecture"
|
| 791 |
+
],
|
| 792 |
+
"web_search_correct_hits_but_underused": [
|
| 793 |
+
"NeurIPS 2023 abstract page for Continuous-Time Functional Diffusion Processes (found in step 5)",
|
| 794 |
+
"arxiv.org/html/2303.00800v3 Continuous-Time Functional Diffusion Processes (found in step 7)",
|
| 795 |
+
"1zb.github.io/functional-diffusion/ project page (found in step 7)"
|
| 796 |
+
],
|
| 797 |
+
"deep_fetches": [
|
| 798 |
+
{
|
| 799 |
+
"url": "https://arxiv.org/abs/2302.03046",
|
| 800 |
+
"intended": "guessed target paper",
|
| 801 |
+
"outcome": "attempted in malformed multi-code step; no usable obs"
|
| 802 |
+
},
|
| 803 |
+
{
|
| 804 |
+
"url": "https://arxiv.org/abs/2303.01631",
|
| 805 |
+
"intended": "FDP paper",
|
| 806 |
+
"outcome": "WRONG PAPER: Real-Time Tube-Based Non-Gaussian Risk Bounded Motion Planning..."
|
| 807 |
+
},
|
| 808 |
+
{
|
| 809 |
+
"url": "https://arxiv.org/abs/2205.15306",
|
| 810 |
+
"intended": "Neural Diffusion Processes",
|
| 811 |
+
"outcome": "WRONG PAPER: How to Compose Shortest Paths"
|
| 812 |
+
},
|
| 813 |
+
{
|
| 814 |
+
"url": "https://openreview.net/forum?id=tV7GSY5GYG",
|
| 815 |
+
"intended": "NDP OpenReview",
|
| 816 |
+
"outcome": "fetched NDP OpenReview page (neighboring method, not gold FDP)"
|
| 817 |
+
},
|
| 818 |
+
{
|
| 819 |
+
"url": "https://proceedings.mlr.press/v202/dutordoir23a.html",
|
| 820 |
+
"intended": "NDP PMLR",
|
| 821 |
+
"outcome": "fetched NDP paper page; agent synthesized NDP ingredients"
|
| 822 |
+
}
|
| 823 |
+
],
|
| 824 |
+
"failure_mode_tooling": "Search surfaced the correct FDP paper, but deep_web_search_tool was applied to wrong arXiv IDs and then to a neighboring Neural Diffusion Processes paper. Final ingredients mirror NDP (finite marginals, exchangeability, attention) rather than FDP (Hilbert-space continuous-time SDEs, countable evaluation, INR score network)."
|
| 825 |
+
}
|
| 826 |
+
}
|
| 827 |
+
}
|
| 828 |
+
}
|
| 829 |
+
}
|
hf_space/system_run_data.py
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Load curated System Run case studies for the Streamlit demo.
|
| 2 |
+
|
| 3 |
+
Labels and section structure follow the SciPaths Dev · CodeAgent Case Studies HTML report.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import json
|
| 9 |
+
from functools import lru_cache
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Any, Optional
|
| 12 |
+
|
| 13 |
+
CASES_DIR = Path(__file__).resolve().parent / "system_run_cases"
|
| 14 |
+
ANALYSIS_PATH = CASES_DIR / "case_study_analysis.json"
|
| 15 |
+
|
| 16 |
+
SYSTEM_RUN_METHODS: list[dict[str, str]] = [
|
| 17 |
+
{
|
| 18 |
+
"id": "codeagent_parametric",
|
| 19 |
+
"label": "Parametric - LLM Only",
|
| 20 |
+
"banner_title": "1 · codeagent_parametric",
|
| 21 |
+
"banner_desc": "Parametric-only generation (no search/retrieval). Mean F1 ≈ 0.20.",
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"id": "codeagent_websearch_deep",
|
| 25 |
+
# Audience label: "Crawl" = deep_web_search_tool (page/PDF fetch; no separate crawl tool).
|
| 26 |
+
"label": "Websearch Deep - LLM + Web Search + Crawl",
|
| 27 |
+
"banner_title": "2 · codeagent_websearch_deep",
|
| 28 |
+
"banner_desc": (
|
| 29 |
+
"Tools: web_search_tool + deep_web_search_tool (page fetch / crawl). "
|
| 30 |
+
"Mean F1 ≈ 0.32."
|
| 31 |
+
),
|
| 32 |
+
},
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
DEFAULT_SYSTEM_METHOD = SYSTEM_RUN_METHODS[0]["id"]
|
| 36 |
+
DEFAULT_SYSTEM_CASE = "success"
|
| 37 |
+
|
| 38 |
+
# Case keys exposed in the UI — labels match the HTML report.
|
| 39 |
+
_METHOD_CASES: dict[str, list[tuple[str, str]]] = {
|
| 40 |
+
"codeagent_parametric": [("success", "Success"), ("failure", "Failure")],
|
| 41 |
+
"codeagent_websearch_deep": [("success", "Success"), ("failure", "Failure")],
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
CASE_OUTCOME_LABEL: dict[str, str] = {
|
| 45 |
+
"success": "Success",
|
| 46 |
+
"failure": "Failure",
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
TOOLING_NOTE_HTML = (
|
| 50 |
+
"<strong>Tooling by setting.</strong>"
|
| 51 |
+
"<br/>· <code>parametric</code>: parametric knowledge only (no search/retrieval)."
|
| 52 |
+
"<br/>· <code>websearch_deep</code>: <code>web_search_tool</code> + "
|
| 53 |
+
"<code>deep_web_search_tool</code> (URL page fetch)."
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
@lru_cache(maxsize=1)
|
| 58 |
+
def load_analysis() -> dict[str, Any]:
|
| 59 |
+
with ANALYSIS_PATH.open("r", encoding="utf-8") as f:
|
| 60 |
+
data = json.load(f)
|
| 61 |
+
if not isinstance(data, dict):
|
| 62 |
+
raise ValueError(f"Invalid case study analysis at {ANALYSIS_PATH}")
|
| 63 |
+
return data
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def method_ids() -> list[str]:
|
| 67 |
+
return [m["id"] for m in SYSTEM_RUN_METHODS]
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def method_meta(method_id: str) -> dict[str, str]:
|
| 71 |
+
for m in SYSTEM_RUN_METHODS:
|
| 72 |
+
if m["id"] == method_id:
|
| 73 |
+
return m
|
| 74 |
+
return {"id": method_id, "label": method_id, "banner_title": method_id, "banner_desc": ""}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def method_label(method_id: str) -> str:
|
| 78 |
+
return method_meta(method_id).get("label") or method_id
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def case_chip_label(method_id: str, case_key: str) -> str:
|
| 82 |
+
"""Top-of-card label, e.g. 'Parametric · Success'."""
|
| 83 |
+
pretty = {
|
| 84 |
+
"codeagent_parametric": "Parametric",
|
| 85 |
+
"codeagent_websearch_deep": "WebSearch Deep",
|
| 86 |
+
}.get(method_id, method_label(method_id))
|
| 87 |
+
outcome = CASE_OUTCOME_LABEL.get(case_key, case_key)
|
| 88 |
+
return f"{pretty} · {outcome}"
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def list_cases(method_id: str) -> list[tuple[str, str]]:
|
| 92 |
+
return list(_METHOD_CASES.get(method_id, []))
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def default_case_for_method(method_id: str) -> str:
|
| 96 |
+
cases = list_cases(method_id)
|
| 97 |
+
return cases[0][0] if cases else DEFAULT_SYSTEM_CASE
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def outcome_tone(case_key: str, f1: float) -> str:
|
| 101 |
+
if case_key == "success":
|
| 102 |
+
return "ok"
|
| 103 |
+
if case_key == "failure":
|
| 104 |
+
return "bad"
|
| 105 |
+
if f1 >= 0.75:
|
| 106 |
+
return "ok"
|
| 107 |
+
if f1 <= 0.05:
|
| 108 |
+
return "bad"
|
| 109 |
+
return "part"
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _setting(method_id: str) -> dict[str, Any]:
|
| 113 |
+
settings = load_analysis().get("settings") or {}
|
| 114 |
+
block = settings.get(method_id)
|
| 115 |
+
if not isinstance(block, dict):
|
| 116 |
+
raise KeyError(f"Unknown system-run method: {method_id}")
|
| 117 |
+
return block
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _raw_case_block(method_id: str, case_key: str) -> dict[str, Any]:
|
| 121 |
+
setting = _setting(method_id)
|
| 122 |
+
if case_key == "featured":
|
| 123 |
+
block = setting.get("case")
|
| 124 |
+
else:
|
| 125 |
+
block = setting.get(case_key)
|
| 126 |
+
if not isinstance(block, dict):
|
| 127 |
+
raise KeyError(f"Unknown case {case_key!r} for method {method_id}")
|
| 128 |
+
return block
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def _gold_ingredients(case: dict[str, Any]) -> list[dict[str, Any]]:
|
| 132 |
+
"""Gold column with full/partial/miss badges and match notes (HTML report style)."""
|
| 133 |
+
predicted = case.get("predicted_ingredients") or []
|
| 134 |
+
gold: list[dict[str, Any]] = []
|
| 135 |
+
for judgment in case.get("recall_judgments") or []:
|
| 136 |
+
if not isinstance(judgment, dict):
|
| 137 |
+
continue
|
| 138 |
+
desc = str(judgment.get("reference_ingredient") or "").strip()
|
| 139 |
+
role = str(judgment.get("reference_role") or "").strip()
|
| 140 |
+
if not desc:
|
| 141 |
+
continue
|
| 142 |
+
covered = bool(judgment.get("covered"))
|
| 143 |
+
partials = judgment.get("partial_matches") or []
|
| 144 |
+
if not isinstance(partials, list):
|
| 145 |
+
partials = []
|
| 146 |
+
match_notes: list[str] = []
|
| 147 |
+
judge_note = ""
|
| 148 |
+
pair_pred_idx: Optional[int] = None
|
| 149 |
+
if covered:
|
| 150 |
+
badge = "full"
|
| 151 |
+
best = judgment.get("best_match") if isinstance(judgment.get("best_match"), dict) else {}
|
| 152 |
+
best_desc = str(best.get("description") or "").strip()
|
| 153 |
+
if best_desc:
|
| 154 |
+
match_notes.append(f"Matched to pred: {best_desc}")
|
| 155 |
+
judge_note = str(judgment.get("reasoning") or "").strip()
|
| 156 |
+
raw_idx = judgment.get("best_match_idx")
|
| 157 |
+
if isinstance(raw_idx, int) and raw_idx > 0:
|
| 158 |
+
pair_pred_idx = raw_idx
|
| 159 |
+
elif best_desc:
|
| 160 |
+
# Fall back to description match against predicted list (1-based).
|
| 161 |
+
for i, pred in enumerate(predicted, start=1):
|
| 162 |
+
if isinstance(pred, dict) and str(pred.get("description") or "").strip() == best_desc:
|
| 163 |
+
pair_pred_idx = i
|
| 164 |
+
break
|
| 165 |
+
elif partials:
|
| 166 |
+
badge = "partial"
|
| 167 |
+
for pm in partials:
|
| 168 |
+
if not isinstance(pm, dict):
|
| 169 |
+
continue
|
| 170 |
+
pred_idx = pm.get("predicted_idx")
|
| 171 |
+
reason = str(pm.get("reasoning") or "").strip()
|
| 172 |
+
if pred_idx is not None and reason:
|
| 173 |
+
match_notes.append(f"Partial ↔ pred #{pred_idx}: {reason}")
|
| 174 |
+
elif reason:
|
| 175 |
+
match_notes.append(reason)
|
| 176 |
+
else:
|
| 177 |
+
badge = "miss"
|
| 178 |
+
gold.append(
|
| 179 |
+
{
|
| 180 |
+
"description": desc,
|
| 181 |
+
"role": role,
|
| 182 |
+
"badge": badge,
|
| 183 |
+
"match_notes": match_notes,
|
| 184 |
+
"judge_note": judge_note,
|
| 185 |
+
"pair_pred_idx": pair_pred_idx,
|
| 186 |
+
}
|
| 187 |
+
)
|
| 188 |
+
return gold
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def build_pair_rows(
|
| 192 |
+
gold: list[dict[str, Any]], predicted: list[dict[str, Any]]
|
| 193 |
+
) -> list[dict[str, Any]]:
|
| 194 |
+
"""Align gold↔prediction rows for success-case connector lines."""
|
| 195 |
+
by_idx = {
|
| 196 |
+
int(p["idx"]): p
|
| 197 |
+
for p in predicted
|
| 198 |
+
if isinstance(p.get("idx"), int)
|
| 199 |
+
}
|
| 200 |
+
used: set[int] = set()
|
| 201 |
+
rows: list[dict[str, Any]] = []
|
| 202 |
+
for g in gold:
|
| 203 |
+
pred_idx = g.get("pair_pred_idx")
|
| 204 |
+
pred = by_idx.get(int(pred_idx)) if isinstance(pred_idx, int) else None
|
| 205 |
+
linked = bool(pred is not None and g.get("badge") == "full")
|
| 206 |
+
if linked and isinstance(pred_idx, int):
|
| 207 |
+
used.add(pred_idx)
|
| 208 |
+
rows.append({"gold": g, "pred": pred, "linked": linked})
|
| 209 |
+
for p in predicted:
|
| 210 |
+
idx = p.get("idx")
|
| 211 |
+
if isinstance(idx, int) and idx not in used:
|
| 212 |
+
rows.append({"gold": None, "pred": p, "linked": False})
|
| 213 |
+
return rows
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def _tool_panel(method_id: str, case_key: str, case: dict[str, Any], setting: dict[str, Any]) -> dict[str, Any]:
|
| 217 |
+
"""Structured tool evidence / traces for the case card."""
|
| 218 |
+
if method_id == "codeagent_retrieval_plus_websearch":
|
| 219 |
+
analysis = case.get("gemini_analysis") if isinstance(case.get("gemini_analysis"), dict) else {}
|
| 220 |
+
return {
|
| 221 |
+
"kind": "retrieval",
|
| 222 |
+
"intro": (
|
| 223 |
+
"This run used both retrieve_documents and web_search_tool. "
|
| 224 |
+
"Web search continued in later steps after documents were returned."
|
| 225 |
+
),
|
| 226 |
+
"queries": list(analysis.get("web_search_queries_example") or []),
|
| 227 |
+
"retrieved_docs": list(case.get("retrieved_doc_titles") or []),
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
if method_id == "codeagent_websearch_deep":
|
| 231 |
+
traces = setting.get("tool_traces") if isinstance(setting.get("tool_traces"), dict) else {}
|
| 232 |
+
trace = traces.get("success" if case_key == "success" else "failure")
|
| 233 |
+
if not isinstance(trace, dict):
|
| 234 |
+
return {"kind": "none"}
|
| 235 |
+
deep_fetches = []
|
| 236 |
+
for item in trace.get("deep_fetches") or []:
|
| 237 |
+
if not isinstance(item, dict):
|
| 238 |
+
continue
|
| 239 |
+
outcome = str(item.get("outcome") or "")
|
| 240 |
+
ok = outcome.upper().startswith("SUCCESS")
|
| 241 |
+
deep_fetches.append(
|
| 242 |
+
{
|
| 243 |
+
"url": str(item.get("url") or ""),
|
| 244 |
+
"outcome": outcome,
|
| 245 |
+
"ok": ok,
|
| 246 |
+
}
|
| 247 |
+
)
|
| 248 |
+
return {
|
| 249 |
+
"kind": "deep",
|
| 250 |
+
"queries": list(
|
| 251 |
+
trace.get("web_search_queries")
|
| 252 |
+
or trace.get("web_search_queries_sample")
|
| 253 |
+
or []
|
| 254 |
+
),
|
| 255 |
+
"hits": list(
|
| 256 |
+
trace.get("web_search_hits_notable")
|
| 257 |
+
or trace.get("web_search_correct_hits_but_underused")
|
| 258 |
+
or []
|
| 259 |
+
),
|
| 260 |
+
"deep_fetches": deep_fetches,
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
return {"kind": "none"}
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def get_case(method_id: str, case_key: str) -> dict[str, Any]:
|
| 267 |
+
"""Normalized case card payload for the UI."""
|
| 268 |
+
setting = _setting(method_id)
|
| 269 |
+
case = _raw_case_block(method_id, case_key)
|
| 270 |
+
tools = [str(t) for t in (setting.get("tools") or [])]
|
| 271 |
+
predicted = []
|
| 272 |
+
for idx, item in enumerate(case.get("predicted_ingredients") or [], start=1):
|
| 273 |
+
if not isinstance(item, dict):
|
| 274 |
+
continue
|
| 275 |
+
predicted.append(
|
| 276 |
+
{
|
| 277 |
+
"idx": idx,
|
| 278 |
+
"description": str(item.get("description") or "").strip(),
|
| 279 |
+
"role": str(item.get("role") or "").strip(),
|
| 280 |
+
"rationale": str(item.get("rationale") or "").strip(),
|
| 281 |
+
}
|
| 282 |
+
)
|
| 283 |
+
f1 = float(case.get("f1") or 0.0)
|
| 284 |
+
return {
|
| 285 |
+
"method_id": method_id,
|
| 286 |
+
"method_label": method_label(method_id),
|
| 287 |
+
"case_key": case_key,
|
| 288 |
+
"case_chip": case_chip_label(method_id, case_key),
|
| 289 |
+
"outcome_tone": outcome_tone(case_key, f1),
|
| 290 |
+
"tools": tools,
|
| 291 |
+
"paper_id": str(case.get("paper_id") or ""),
|
| 292 |
+
"claim_idx": case.get("claim_idx"),
|
| 293 |
+
"paper_title": str(case.get("paper_title") or ""),
|
| 294 |
+
"claim": str(case.get("claim") or ""),
|
| 295 |
+
"recall": float(case.get("recall") or 0.0),
|
| 296 |
+
"precision": float(case.get("precision") or 0.0),
|
| 297 |
+
"f1": f1,
|
| 298 |
+
"gold_ingredients": _gold_ingredients(case),
|
| 299 |
+
"predicted_ingredients": predicted,
|
| 300 |
+
"tool_panel": _tool_panel(method_id, case_key, case, setting),
|
| 301 |
+
"banner": method_meta(method_id),
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def get_analysis(method_id: str, case_key: Optional[str] = None) -> dict[str, Any]:
|
| 306 |
+
setting = _setting(method_id)
|
| 307 |
+
if method_id == "codeagent_retrieval_plus_websearch":
|
| 308 |
+
case = setting.get("case") or {}
|
| 309 |
+
analysis = case.get("gemini_analysis") if isinstance(case, dict) else None
|
| 310 |
+
return dict(analysis) if isinstance(analysis, dict) else {}
|
| 311 |
+
|
| 312 |
+
analysis = setting.get("gemini_analysis")
|
| 313 |
+
if not isinstance(analysis, dict):
|
| 314 |
+
return {}
|
| 315 |
+
out = dict(analysis)
|
| 316 |
+
out["_case_key"] = case_key or ""
|
| 317 |
+
return out
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def analysis_view_for_case(method_id: str, case_key: str) -> dict[str, Any]:
|
| 321 |
+
"""Structured analysis view matching HTML report headings."""
|
| 322 |
+
analysis = get_analysis(method_id, case_key)
|
| 323 |
+
if not analysis:
|
| 324 |
+
return {"model_tag": "Gemini 3.1 Pro analysis", "blocks": []}
|
| 325 |
+
|
| 326 |
+
blocks: list[dict[str, Any]] = []
|
| 327 |
+
|
| 328 |
+
if method_id == "codeagent_retrieval_plus_websearch":
|
| 329 |
+
headline = str(analysis.get("headline") or "").strip()
|
| 330 |
+
takeaway = str(analysis.get("takeaway") or "").strip()
|
| 331 |
+
diag = headline
|
| 332 |
+
if takeaway:
|
| 333 |
+
diag = f"{headline}\n{takeaway}" if headline else takeaway
|
| 334 |
+
if diag:
|
| 335 |
+
blocks.append({"type": "diagnosis", "text": diag, "strong_first": True})
|
| 336 |
+
two_col = []
|
| 337 |
+
for key, title in (
|
| 338 |
+
("what_was_retrieved", "What the tools returned"),
|
| 339 |
+
("effect_on_prediction", "Effect on prediction"),
|
| 340 |
+
):
|
| 341 |
+
items = analysis.get(key)
|
| 342 |
+
if isinstance(items, list) and items:
|
| 343 |
+
two_col.append({"title": title, "items": [str(x) for x in items]})
|
| 344 |
+
if two_col:
|
| 345 |
+
blocks.append({"type": "two_col", "cols": two_col})
|
| 346 |
+
errs = analysis.get("error_taxonomy_bullets")
|
| 347 |
+
if isinstance(errs, list) and errs:
|
| 348 |
+
blocks.append(
|
| 349 |
+
{"type": "list", "title": "Error analysis", "items": [str(x) for x in errs]}
|
| 350 |
+
)
|
| 351 |
+
return {"model_tag": "Gemini 3.1 Pro analysis", "blocks": blocks}
|
| 352 |
+
|
| 353 |
+
if method_id == "codeagent_parametric":
|
| 354 |
+
if case_key == "success":
|
| 355 |
+
why = analysis.get("success_why")
|
| 356 |
+
if isinstance(why, list) and why:
|
| 357 |
+
blocks.append(
|
| 358 |
+
{
|
| 359 |
+
"type": "list",
|
| 360 |
+
"title": "Why this succeeded",
|
| 361 |
+
"items": [str(x) for x in why],
|
| 362 |
+
}
|
| 363 |
+
)
|
| 364 |
+
else:
|
| 365 |
+
if analysis.get("failure_diagnosis"):
|
| 366 |
+
blocks.append(
|
| 367 |
+
{
|
| 368 |
+
"type": "diagnosis",
|
| 369 |
+
"text": f"Diagnosis. {analysis['failure_diagnosis']}",
|
| 370 |
+
"strong_first": True,
|
| 371 |
+
}
|
| 372 |
+
)
|
| 373 |
+
errs = analysis.get("failure_error_bullets")
|
| 374 |
+
if isinstance(errs, list) and errs:
|
| 375 |
+
blocks.append(
|
| 376 |
+
{
|
| 377 |
+
"type": "list",
|
| 378 |
+
"title": "Error taxonomy",
|
| 379 |
+
"items": [str(x) for x in errs],
|
| 380 |
+
}
|
| 381 |
+
)
|
| 382 |
+
needed = analysis.get("failure_what_needed")
|
| 383 |
+
if isinstance(needed, list) and needed:
|
| 384 |
+
blocks.append(
|
| 385 |
+
{
|
| 386 |
+
"type": "list",
|
| 387 |
+
"title": "What recovery would have required",
|
| 388 |
+
"items": [str(x) for x in needed],
|
| 389 |
+
}
|
| 390 |
+
)
|
| 391 |
+
return {"model_tag": "Gemini 3.1 Pro analysis", "blocks": blocks}
|
| 392 |
+
|
| 393 |
+
# websearch_deep
|
| 394 |
+
if case_key == "success":
|
| 395 |
+
two_col = []
|
| 396 |
+
for key, title in (
|
| 397 |
+
("success_search_contribution", "web_search_tool"),
|
| 398 |
+
("success_deep_contribution", "deep_web_search_tool"),
|
| 399 |
+
):
|
| 400 |
+
items = analysis.get(key)
|
| 401 |
+
if isinstance(items, list) and items:
|
| 402 |
+
two_col.append({"title": title, "items": [str(x) for x in items]})
|
| 403 |
+
if two_col:
|
| 404 |
+
blocks.append({"type": "two_col", "cols": two_col})
|
| 405 |
+
why = analysis.get("success_why_overall")
|
| 406 |
+
if isinstance(why, list) and why:
|
| 407 |
+
blocks.append(
|
| 408 |
+
{"type": "list", "title": "Why this succeeded", "items": [str(x) for x in why]}
|
| 409 |
+
)
|
| 410 |
+
else:
|
| 411 |
+
if analysis.get("failure_diagnosis"):
|
| 412 |
+
blocks.append(
|
| 413 |
+
{
|
| 414 |
+
"type": "diagnosis",
|
| 415 |
+
"text": f"Diagnosis. {analysis['failure_diagnosis']}",
|
| 416 |
+
"strong_first": True,
|
| 417 |
+
}
|
| 418 |
+
)
|
| 419 |
+
two_col = []
|
| 420 |
+
for key, title in (
|
| 421 |
+
("failure_search_what_went_wrong", "web_search_tool"),
|
| 422 |
+
("failure_deep_what_went_wrong", "deep_web_search_tool"),
|
| 423 |
+
):
|
| 424 |
+
items = analysis.get(key)
|
| 425 |
+
if isinstance(items, list) and items:
|
| 426 |
+
two_col.append({"title": title, "items": [str(x) for x in items]})
|
| 427 |
+
if two_col:
|
| 428 |
+
blocks.append({"type": "two_col", "cols": two_col})
|
| 429 |
+
errs = analysis.get("failure_error_bullets")
|
| 430 |
+
if isinstance(errs, list) and errs:
|
| 431 |
+
blocks.append(
|
| 432 |
+
{"type": "list", "title": "Error taxonomy", "items": [str(x) for x in errs]}
|
| 433 |
+
)
|
| 434 |
+
needed = analysis.get("failure_what_needed")
|
| 435 |
+
if isinstance(needed, list) and needed:
|
| 436 |
+
blocks.append(
|
| 437 |
+
{
|
| 438 |
+
"type": "list",
|
| 439 |
+
"title": "What recovery would have required",
|
| 440 |
+
"items": [str(x) for x in needed],
|
| 441 |
+
}
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
return {"model_tag": "Gemini 3.1 Pro analysis", "blocks": blocks}
|
hf_space/workflow_graph.py
ADDED
|
@@ -0,0 +1,1197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evolving SciPaths workflow graph (Neo4j-backed).
|
| 2 |
+
|
| 3 |
+
Visual formatting follows Mina Brain's knowledge-graph canvas strictly:
|
| 4 |
+
filled circle + white inner stroke + colored ring, 25-char labels with white
|
| 5 |
+
halo, expanding write-pulse rings, #fafbfd stage, click detail card.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import re
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import Any, Optional
|
| 14 |
+
|
| 15 |
+
import streamlit as st
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
import neo4j_workflow as neo4j_store
|
| 19 |
+
except Exception: # pragma: no cover
|
| 20 |
+
neo4j_store = None # type: ignore
|
| 21 |
+
|
| 22 |
+
# Short labels shared by Steps panel + workflow graph captions.
|
| 23 |
+
STEP_COPY: dict[int, str] = {
|
| 24 |
+
1: "Load the paper",
|
| 25 |
+
2: "Find where it’s cited",
|
| 26 |
+
3: "Collect citation contexts",
|
| 27 |
+
4: "Classify how it’s used",
|
| 28 |
+
5: "Keep real reuse (uses / extends)",
|
| 29 |
+
6: "Pull the citing passages",
|
| 30 |
+
7: "Group similar reuse themes and summarize target contributions",
|
| 31 |
+
8: "Find enabling contributions and corresponding prior studies",
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
WORK_PANEL_STEPS: list[tuple[int, str]] = [(n, STEP_COPY[n]) for n in range(1, 9)]
|
| 35 |
+
|
| 36 |
+
CANVAS_LABEL_MAX = 25
|
| 37 |
+
|
| 38 |
+
# Mina Brain NODE_COLORS mapped onto SciPaths kinds (fill / ring / text).
|
| 39 |
+
KIND_META = {
|
| 40 |
+
"target": {
|
| 41 |
+
"fill": "#4F6EF7",
|
| 42 |
+
"ring": "#A5B4FC",
|
| 43 |
+
"text": "#1E3A8A",
|
| 44 |
+
"r": 22,
|
| 45 |
+
"type_label": "Target paper",
|
| 46 |
+
"color": "#4F6EF7",
|
| 47 |
+
"size": 22,
|
| 48 |
+
},
|
| 49 |
+
"citing": {
|
| 50 |
+
"fill": "#60A5FA",
|
| 51 |
+
"ring": "#BFDBFE",
|
| 52 |
+
"text": "#1D4ED8",
|
| 53 |
+
"r": 14,
|
| 54 |
+
"type_label": "Citing paper",
|
| 55 |
+
"color": "#60A5FA",
|
| 56 |
+
"size": 14,
|
| 57 |
+
},
|
| 58 |
+
"cluster": {
|
| 59 |
+
"fill": "#8B5CF6",
|
| 60 |
+
"ring": "#DDD6FE",
|
| 61 |
+
"text": "#5B21B6",
|
| 62 |
+
"r": 16,
|
| 63 |
+
"type_label": "Reuse theme",
|
| 64 |
+
"color": "#8B5CF6",
|
| 65 |
+
"size": 16,
|
| 66 |
+
},
|
| 67 |
+
"claim": {
|
| 68 |
+
"fill": "#FBBF24",
|
| 69 |
+
"ring": "#FDE68A",
|
| 70 |
+
"text": "#B45309",
|
| 71 |
+
"r": 15,
|
| 72 |
+
"type_label": "Target contribution",
|
| 73 |
+
"color": "#FBBF24",
|
| 74 |
+
"size": 15,
|
| 75 |
+
},
|
| 76 |
+
"ingredient": {
|
| 77 |
+
"fill": "#94A3B8",
|
| 78 |
+
"ring": "#E2E8F0",
|
| 79 |
+
"text": "#475569",
|
| 80 |
+
"r": 12,
|
| 81 |
+
"type_label": "Enabling contribution",
|
| 82 |
+
"color": "#94A3B8",
|
| 83 |
+
"size": 12,
|
| 84 |
+
},
|
| 85 |
+
"study": {
|
| 86 |
+
"fill": "#34D399",
|
| 87 |
+
"ring": "#A7F3D0",
|
| 88 |
+
"text": "#047857",
|
| 89 |
+
"r": 13,
|
| 90 |
+
"type_label": "Prior study",
|
| 91 |
+
"color": "#34D399",
|
| 92 |
+
"size": 13,
|
| 93 |
+
},
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
# Category order + compact per-kind palette the canvas shell + rail consume.
|
| 97 |
+
_KIND_ORDER = ["target", "claim", "ingredient", "cluster", "study", "citing"]
|
| 98 |
+
_KIND_META_JS = {
|
| 99 |
+
k: {"fill": v["fill"], "ring": v["ring"], "text": v["text"], "label": v["type_label"]}
|
| 100 |
+
for k, v in KIND_META.items()
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _load_json(path: Path) -> Any | None:
|
| 105 |
+
if not path.exists():
|
| 106 |
+
return None
|
| 107 |
+
try:
|
| 108 |
+
return json.loads(path.read_text(encoding="utf-8"))
|
| 109 |
+
except Exception:
|
| 110 |
+
return None
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _clean(text: str) -> str:
|
| 114 |
+
return re.sub(r"\s+", " ", (text or "").strip())
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def canvas_label(text: str, *, limit: int = CANVAS_LABEL_MAX) -> str:
|
| 118 |
+
cleaned = _clean(text)
|
| 119 |
+
if len(cleaned) <= limit:
|
| 120 |
+
return cleaned
|
| 121 |
+
return cleaned[:limit].rstrip() + "…"
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def visible_step_from_events(events: list[str]) -> int:
|
| 125 |
+
started = 0
|
| 126 |
+
for raw in events or []:
|
| 127 |
+
text = str(raw)
|
| 128 |
+
m = re.search(r"Step\s+(\d+)\s*(?:/|\s+complete)", text, re.IGNORECASE)
|
| 129 |
+
if m:
|
| 130 |
+
started = max(started, int(m.group(1)))
|
| 131 |
+
continue
|
| 132 |
+
lower = text.lower()
|
| 133 |
+
if "[annotation]" in lower or "annotate" in lower:
|
| 134 |
+
started = max(started, 8)
|
| 135 |
+
return min(8, started)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def pulse_step_from_events(events: list[str]) -> int:
|
| 139 |
+
started: set[int] = set()
|
| 140 |
+
completed: set[int] = set()
|
| 141 |
+
for raw in events or []:
|
| 142 |
+
text = str(raw)
|
| 143 |
+
m_done = re.search(r"Step\s+(\d+)\s+complete", text, re.IGNORECASE)
|
| 144 |
+
if m_done:
|
| 145 |
+
completed.add(int(m_done.group(1)))
|
| 146 |
+
continue
|
| 147 |
+
m_start = re.search(r"Step\s+(\d+)\s*/", text)
|
| 148 |
+
if m_start:
|
| 149 |
+
started.add(int(m_start.group(1)))
|
| 150 |
+
continue
|
| 151 |
+
lower = text.lower()
|
| 152 |
+
if "[annotation]" in lower or "annotate" in lower:
|
| 153 |
+
if "complete" in lower or "skipped" in lower:
|
| 154 |
+
completed.add(8)
|
| 155 |
+
else:
|
| 156 |
+
started.add(8)
|
| 157 |
+
active = [n for n in sorted(started) if n not in completed]
|
| 158 |
+
return active[-1] if active else 0
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def _paper_title_from_dir(paper_dir: Optional[Path]) -> str:
|
| 162 |
+
if not paper_dir:
|
| 163 |
+
return "Target paper"
|
| 164 |
+
data = _load_json(paper_dir / "paper_metadata.json")
|
| 165 |
+
record = None
|
| 166 |
+
if isinstance(data, list) and data and isinstance(data[0], dict):
|
| 167 |
+
record = data[0]
|
| 168 |
+
elif isinstance(data, dict):
|
| 169 |
+
record = data
|
| 170 |
+
return _clean(str((record or {}).get("title") or "")) or "Target paper"
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _paper_abstract_from_dir(paper_dir: Optional[Path]) -> str:
|
| 174 |
+
if not paper_dir:
|
| 175 |
+
return ""
|
| 176 |
+
data = _load_json(paper_dir / "paper_metadata.json")
|
| 177 |
+
record = None
|
| 178 |
+
if isinstance(data, list) and data and isinstance(data[0], dict):
|
| 179 |
+
record = data[0]
|
| 180 |
+
elif isinstance(data, dict):
|
| 181 |
+
record = data
|
| 182 |
+
abstract = _clean(str((record or {}).get("abstract") or ""))
|
| 183 |
+
abstract = re.sub(r"^(abstract)\s*[:.]?\s*", "", abstract, flags=re.IGNORECASE)
|
| 184 |
+
abstract = re.sub(r"(?<=[.!?\)\]\"'”’])\d+$", "", abstract)
|
| 185 |
+
return abstract
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _node(
|
| 189 |
+
*,
|
| 190 |
+
nid: str,
|
| 191 |
+
kind: str,
|
| 192 |
+
short: str,
|
| 193 |
+
title: str,
|
| 194 |
+
detail: str,
|
| 195 |
+
step_added: int,
|
| 196 |
+
pulse: bool,
|
| 197 |
+
) -> dict[str, Any]:
|
| 198 |
+
meta = KIND_META.get(kind, KIND_META["citing"])
|
| 199 |
+
return {
|
| 200 |
+
"id": nid,
|
| 201 |
+
"kind": kind,
|
| 202 |
+
"label": canvas_label(short),
|
| 203 |
+
"title": _clean(title) or short,
|
| 204 |
+
"detail": _clean(detail),
|
| 205 |
+
"step_added": step_added,
|
| 206 |
+
"pulse": pulse,
|
| 207 |
+
"color": meta["fill"],
|
| 208 |
+
"fill": meta["fill"],
|
| 209 |
+
"ring": meta["ring"],
|
| 210 |
+
"text": meta["text"],
|
| 211 |
+
"r": meta["r"],
|
| 212 |
+
"size": meta["r"],
|
| 213 |
+
"type_label": meta["type_label"],
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def _collect_citing(paper_dir: Optional[Path], limit: int = 6) -> list[dict[str, str]]:
|
| 218 |
+
if not paper_dir:
|
| 219 |
+
return []
|
| 220 |
+
out: list[dict[str, str]] = []
|
| 221 |
+
seen: set[str] = set()
|
| 222 |
+
contrib = _load_json(paper_dir / "usage_contributions.json") or {}
|
| 223 |
+
items = contrib.get("contributions") if isinstance(contrib, dict) else None
|
| 224 |
+
if isinstance(items, list):
|
| 225 |
+
for item in items:
|
| 226 |
+
if not isinstance(item, dict):
|
| 227 |
+
continue
|
| 228 |
+
cid = str(item.get("citing_paper_id") or "").strip()
|
| 229 |
+
title = _clean(str(item.get("citing_title") or "Citing paper"))
|
| 230 |
+
if not cid or cid in seen:
|
| 231 |
+
continue
|
| 232 |
+
seen.add(cid)
|
| 233 |
+
out.append(
|
| 234 |
+
{
|
| 235 |
+
"id": f"citing:{cid}",
|
| 236 |
+
"title": title,
|
| 237 |
+
"detail": _clean(
|
| 238 |
+
str(item.get("paper_claim") or item.get("claim") or item.get("evidence_span") or "")
|
| 239 |
+
),
|
| 240 |
+
"label_tag": item.get("label") or "",
|
| 241 |
+
}
|
| 242 |
+
)
|
| 243 |
+
if len(out) >= limit:
|
| 244 |
+
return out
|
| 245 |
+
return out
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def _collect_clusters(paper_dir: Optional[Path], limit: int = 4) -> list[dict[str, str]]:
|
| 249 |
+
if not paper_dir:
|
| 250 |
+
return []
|
| 251 |
+
discovery = _load_json(paper_dir / "usage_discovery_from_contributions.json") or {}
|
| 252 |
+
clusters = discovery.get("clusters") if isinstance(discovery, dict) else None
|
| 253 |
+
out: list[dict[str, str]] = []
|
| 254 |
+
if not isinstance(clusters, list):
|
| 255 |
+
return out
|
| 256 |
+
for item in clusters[:limit]:
|
| 257 |
+
if not isinstance(item, dict):
|
| 258 |
+
continue
|
| 259 |
+
cid = str(item.get("cluster_id") or f"C{len(out) + 1}")
|
| 260 |
+
title = _clean(
|
| 261 |
+
str(item.get("representative_claim") or item.get("cluster_title") or f"Theme {cid}")
|
| 262 |
+
)
|
| 263 |
+
out.append(
|
| 264 |
+
{
|
| 265 |
+
"id": f"cluster:{cid}",
|
| 266 |
+
"cluster_id": cid,
|
| 267 |
+
"title": title,
|
| 268 |
+
"detail": _clean(str(item.get("merge_rationale") or f"{item.get('count', '')} contribution instances")),
|
| 269 |
+
}
|
| 270 |
+
)
|
| 271 |
+
return out
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def _collect_annotation(payload: Optional[dict]) -> tuple[list[dict], list[dict], list[dict]]:
|
| 275 |
+
claims: list[dict] = []
|
| 276 |
+
ingredients: list[dict] = []
|
| 277 |
+
studies: list[dict] = []
|
| 278 |
+
if not isinstance(payload, dict):
|
| 279 |
+
return claims, ingredients, studies
|
| 280 |
+
for claim in (payload.get("claims") or [])[:4]:
|
| 281 |
+
if not isinstance(claim, dict):
|
| 282 |
+
continue
|
| 283 |
+
claim_id = str(claim.get("claim_id") or f"C{len(claims) + 1}")
|
| 284 |
+
title = _clean(str(claim.get("rewritten_claim") or claim.get("text") or claim_id))
|
| 285 |
+
claims.append(
|
| 286 |
+
{
|
| 287 |
+
"id": f"claim:{claim_id}",
|
| 288 |
+
"claim_id": claim_id,
|
| 289 |
+
"cluster_id": str(claim.get("cluster_id") or ""),
|
| 290 |
+
"title": title,
|
| 291 |
+
"detail": _clean(str(claim.get("decision") or "")),
|
| 292 |
+
}
|
| 293 |
+
)
|
| 294 |
+
for ing in (claim.get("ingredients") or [])[:3]:
|
| 295 |
+
if not isinstance(ing, dict):
|
| 296 |
+
continue
|
| 297 |
+
iid = str(ing.get("ingredient_id") or f"{claim_id}.I{len(ingredients) + 1}")
|
| 298 |
+
ann = ing.get("canonical_annotation") if isinstance(ing.get("canonical_annotation"), dict) else {}
|
| 299 |
+
ingredients.append(
|
| 300 |
+
{
|
| 301 |
+
"id": f"ing:{iid}",
|
| 302 |
+
"ingredient_id": iid,
|
| 303 |
+
"claim_id": claim_id,
|
| 304 |
+
"title": _clean(str(ing.get("ingredient") or iid)),
|
| 305 |
+
"detail": _clean(
|
| 306 |
+
str((ann or {}).get("contribution") or (ann or {}).get("rationale") or "")
|
| 307 |
+
),
|
| 308 |
+
"role": _clean(str((ann or {}).get("role") or "")),
|
| 309 |
+
}
|
| 310 |
+
)
|
| 311 |
+
g = ing.get("canonical_grounding") if isinstance(ing.get("canonical_grounding"), dict) else None
|
| 312 |
+
if g:
|
| 313 |
+
sid = str(g.get("paper_id") or g.get("ref_id") or g.get("ref_title") or iid)
|
| 314 |
+
studies.append(
|
| 315 |
+
{
|
| 316 |
+
"id": f"study:{sid}",
|
| 317 |
+
"ingredient_id": iid,
|
| 318 |
+
"title": _clean(str(g.get("ref_title") or g.get("ref_id") or "Prior study")),
|
| 319 |
+
"detail": _clean(str(g.get("ref_authors") or g.get("ref_year") or "")),
|
| 320 |
+
}
|
| 321 |
+
)
|
| 322 |
+
seen: set[str] = set()
|
| 323 |
+
uniq: list[dict] = []
|
| 324 |
+
for s in studies:
|
| 325 |
+
if s["id"] in seen:
|
| 326 |
+
continue
|
| 327 |
+
seen.add(s["id"])
|
| 328 |
+
uniq.append(s)
|
| 329 |
+
return claims, ingredients, uniq[:6]
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def build_graph_model(
|
| 333 |
+
*,
|
| 334 |
+
paper_dir: Optional[Path],
|
| 335 |
+
payload: Optional[dict],
|
| 336 |
+
visible_step: int,
|
| 337 |
+
pulse_step: int = 0,
|
| 338 |
+
caption: str = "",
|
| 339 |
+
) -> dict[str, Any]:
|
| 340 |
+
step = max(0, min(8, int(visible_step or 0)))
|
| 341 |
+
nodes: list[dict[str, Any]] = []
|
| 342 |
+
edges: list[dict[str, Any]] = []
|
| 343 |
+
|
| 344 |
+
if step >= 1:
|
| 345 |
+
title = _paper_title_from_dir(paper_dir)
|
| 346 |
+
abstract = _paper_abstract_from_dir(paper_dir)
|
| 347 |
+
nodes.append(
|
| 348 |
+
_node(
|
| 349 |
+
nid="target",
|
| 350 |
+
kind="target",
|
| 351 |
+
short="Target",
|
| 352 |
+
title=title,
|
| 353 |
+
detail=abstract[:500],
|
| 354 |
+
step_added=1,
|
| 355 |
+
pulse=pulse_step == 1,
|
| 356 |
+
)
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
citing = _collect_citing(paper_dir) if step >= 2 else []
|
| 360 |
+
if step >= 2:
|
| 361 |
+
rows = citing or [
|
| 362 |
+
{"id": f"citing:placeholder:{i}", "title": f"Citing paper {i+1}", "detail": "Waiting for citation artifacts…", "label_tag": ""}
|
| 363 |
+
for i in range(3)
|
| 364 |
+
]
|
| 365 |
+
for i, item in enumerate(rows):
|
| 366 |
+
nodes.append(
|
| 367 |
+
_node(
|
| 368 |
+
nid=item["id"],
|
| 369 |
+
kind="citing",
|
| 370 |
+
short=f"Citing {i + 1}",
|
| 371 |
+
title=item["title"],
|
| 372 |
+
detail=item.get("detail") or item.get("label_tag") or "",
|
| 373 |
+
step_added=2,
|
| 374 |
+
pulse=pulse_step in {2, 3, 4, 5, 6},
|
| 375 |
+
)
|
| 376 |
+
)
|
| 377 |
+
edges.append(
|
| 378 |
+
{
|
| 379 |
+
"id": f"e-target-{item['id']}",
|
| 380 |
+
"source": "target",
|
| 381 |
+
"target": item["id"],
|
| 382 |
+
"kind": "cite",
|
| 383 |
+
"muted": step < 5,
|
| 384 |
+
"pulse": pulse_step in {2, 3, 4, 5},
|
| 385 |
+
"step_added": 2,
|
| 386 |
+
}
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
# Step 7: reuse themes + target contributions arrive together.
|
| 390 |
+
clusters = _collect_clusters(paper_dir) if step >= 7 else []
|
| 391 |
+
claims, ingredients, studies = (
|
| 392 |
+
_collect_annotation(payload) if step >= 7 else ([], [], [])
|
| 393 |
+
)
|
| 394 |
+
if step >= 7 and clusters:
|
| 395 |
+
for item in clusters:
|
| 396 |
+
cid = item.get("cluster_id") or "?"
|
| 397 |
+
nodes.append(
|
| 398 |
+
_node(
|
| 399 |
+
nid=item["id"],
|
| 400 |
+
kind="cluster",
|
| 401 |
+
short=f"Theme {cid}",
|
| 402 |
+
title=item["title"],
|
| 403 |
+
detail=item.get("detail") or "",
|
| 404 |
+
step_added=7,
|
| 405 |
+
pulse=pulse_step == 7,
|
| 406 |
+
)
|
| 407 |
+
)
|
| 408 |
+
edges.append(
|
| 409 |
+
{
|
| 410 |
+
"id": f"e-cluster-{item['id']}",
|
| 411 |
+
"source": "target",
|
| 412 |
+
"target": item["id"],
|
| 413 |
+
"kind": "theme",
|
| 414 |
+
"pulse": pulse_step == 7,
|
| 415 |
+
"muted": False,
|
| 416 |
+
"step_added": 7,
|
| 417 |
+
}
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
if step >= 7:
|
| 421 |
+
for item in claims:
|
| 422 |
+
cid = item.get("claim_id") or "?"
|
| 423 |
+
nodes.append(
|
| 424 |
+
_node(
|
| 425 |
+
nid=item["id"],
|
| 426 |
+
kind="claim",
|
| 427 |
+
short=f"Claim {cid}",
|
| 428 |
+
title=item["title"],
|
| 429 |
+
detail=item.get("detail") or "",
|
| 430 |
+
step_added=7,
|
| 431 |
+
pulse=pulse_step == 7,
|
| 432 |
+
)
|
| 433 |
+
)
|
| 434 |
+
src = f"cluster:{item.get('cluster_id')}" if item.get("cluster_id") else "target"
|
| 435 |
+
if not any(n["id"] == src for n in nodes):
|
| 436 |
+
src = "target"
|
| 437 |
+
edges.append(
|
| 438 |
+
{
|
| 439 |
+
"id": f"e-claim-{item['id']}",
|
| 440 |
+
"source": src,
|
| 441 |
+
"target": item["id"],
|
| 442 |
+
"kind": "derive",
|
| 443 |
+
"pulse": pulse_step == 7,
|
| 444 |
+
"muted": False,
|
| 445 |
+
"step_added": 7,
|
| 446 |
+
}
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
+
# Step 8: enabling contributions + prior studies.
|
| 450 |
+
if step >= 8:
|
| 451 |
+
for i, item in enumerate(ingredients):
|
| 452 |
+
nodes.append(
|
| 453 |
+
_node(
|
| 454 |
+
nid=item["id"],
|
| 455 |
+
kind="ingredient",
|
| 456 |
+
short=f"Enable {i + 1}",
|
| 457 |
+
title=item["title"],
|
| 458 |
+
detail=" · ".join(x for x in [item.get("role") or "", item.get("detail") or ""] if x),
|
| 459 |
+
step_added=8,
|
| 460 |
+
pulse=pulse_step == 8,
|
| 461 |
+
)
|
| 462 |
+
)
|
| 463 |
+
src = f"claim:{item.get('claim_id')}"
|
| 464 |
+
if any(n["id"] == src for n in nodes):
|
| 465 |
+
edges.append(
|
| 466 |
+
{
|
| 467 |
+
"id": f"e-ing-{item['id']}",
|
| 468 |
+
"source": src,
|
| 469 |
+
"target": item["id"],
|
| 470 |
+
"kind": "enable",
|
| 471 |
+
"pulse": pulse_step == 8,
|
| 472 |
+
"muted": False,
|
| 473 |
+
"step_added": 8,
|
| 474 |
+
}
|
| 475 |
+
)
|
| 476 |
+
for i, item in enumerate(studies):
|
| 477 |
+
nodes.append(
|
| 478 |
+
_node(
|
| 479 |
+
nid=item["id"],
|
| 480 |
+
kind="study",
|
| 481 |
+
short=f"Prior {i + 1}",
|
| 482 |
+
title=item["title"],
|
| 483 |
+
detail=item.get("detail") or "",
|
| 484 |
+
step_added=8,
|
| 485 |
+
pulse=pulse_step == 8,
|
| 486 |
+
)
|
| 487 |
+
)
|
| 488 |
+
src = f"ing:{item.get('ingredient_id')}"
|
| 489 |
+
if any(n["id"] == src for n in nodes):
|
| 490 |
+
edges.append(
|
| 491 |
+
{
|
| 492 |
+
"id": f"e-study-{item['id']}",
|
| 493 |
+
"source": src,
|
| 494 |
+
"target": item["id"],
|
| 495 |
+
"kind": "ground",
|
| 496 |
+
"pulse": pulse_step == 8,
|
| 497 |
+
"muted": False,
|
| 498 |
+
"step_added": 8,
|
| 499 |
+
}
|
| 500 |
+
)
|
| 501 |
+
|
| 502 |
+
if step <= 0:
|
| 503 |
+
phase_caption = "Waiting to run"
|
| 504 |
+
elif pulse_step:
|
| 505 |
+
phase_caption = STEP_COPY.get(pulse_step, "")
|
| 506 |
+
elif step >= 8:
|
| 507 |
+
phase_caption = "Workflow complete"
|
| 508 |
+
else:
|
| 509 |
+
phase_caption = caption or STEP_COPY.get(step, "")
|
| 510 |
+
|
| 511 |
+
return {
|
| 512 |
+
"step": step,
|
| 513 |
+
"pulse_step": pulse_step,
|
| 514 |
+
"caption": phase_caption,
|
| 515 |
+
"nodes": nodes,
|
| 516 |
+
"edges": edges,
|
| 517 |
+
"backend": "memory",
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def _graph_payload(model: dict[str, Any]) -> dict[str, Any]:
|
| 522 |
+
"""Serialize a graph model into the payload the canvas shell consumes."""
|
| 523 |
+
nodes_in = model.get("nodes") or []
|
| 524 |
+
edges_in = model.get("edges") or []
|
| 525 |
+
|
| 526 |
+
nodes_js = []
|
| 527 |
+
for n in nodes_in:
|
| 528 |
+
kind = str(n.get("kind") or "citing")
|
| 529 |
+
meta = KIND_META.get(kind, KIND_META["citing"])
|
| 530 |
+
nodes_js.append(
|
| 531 |
+
{
|
| 532 |
+
"id": n["id"],
|
| 533 |
+
"label": n.get("label") or "",
|
| 534 |
+
"kind": kind,
|
| 535 |
+
"fill": n.get("fill") or meta["fill"],
|
| 536 |
+
"ring": n.get("ring") or meta["ring"],
|
| 537 |
+
"text": n.get("text") or meta["text"],
|
| 538 |
+
"r": float(n.get("r") or meta["r"]),
|
| 539 |
+
"pulse": bool(n.get("pulse")),
|
| 540 |
+
"fullTitle": n.get("title") or "",
|
| 541 |
+
"detail": n.get("detail") or "",
|
| 542 |
+
"typeLabel": n.get("type_label") or meta["type_label"],
|
| 543 |
+
}
|
| 544 |
+
)
|
| 545 |
+
edges_js = []
|
| 546 |
+
for e in edges_in:
|
| 547 |
+
edges_js.append(
|
| 548 |
+
{
|
| 549 |
+
"id": e.get("id"),
|
| 550 |
+
"source": e.get("source"),
|
| 551 |
+
"target": e.get("target"),
|
| 552 |
+
"pulse": bool(e.get("pulse")),
|
| 553 |
+
"muted": bool(e.get("muted")),
|
| 554 |
+
}
|
| 555 |
+
)
|
| 556 |
+
return {
|
| 557 |
+
"nodes": nodes_js,
|
| 558 |
+
"edges": edges_js,
|
| 559 |
+
"kindMeta": _KIND_META_JS,
|
| 560 |
+
"kindOrder": _KIND_ORDER,
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
def courier_html(payload: dict[str, Any]) -> str:
|
| 565 |
+
"""Tiny invisible frame that posts a graph model into the persistent shell.
|
| 566 |
+
|
| 567 |
+
Runs in its own Streamlit component iframe, reaches the parent document, and
|
| 568 |
+
postMessages the model to the graph iframe — so the graph updates in place
|
| 569 |
+
instead of the whole component being re-emitted (which reloads it).
|
| 570 |
+
"""
|
| 571 |
+
model_str = json.dumps(payload)
|
| 572 |
+
embed = json.dumps(model_str).replace("</", "<\\/")
|
| 573 |
+
return (
|
| 574 |
+
"<!DOCTYPE html><html><head><meta charset=\"utf-8\" /></head><body>"
|
| 575 |
+
"<script>"
|
| 576 |
+
"(function(){"
|
| 577 |
+
"var model=JSON.parse(" + embed + ");"
|
| 578 |
+
"function post(){try{var f=window.parent.document.querySelectorAll('iframe');"
|
| 579 |
+
"for(var i=0;i<f.length;i++){try{f[i].contentWindow.postMessage({type:'scipaths-graph',model:model},'*');}catch(e){}}}catch(e){}}"
|
| 580 |
+
"post();var n=0;var t=setInterval(function(){n++;post();if(n>10)clearInterval(t);},160);"
|
| 581 |
+
"})();"
|
| 582 |
+
"</script></body></html>"
|
| 583 |
+
)
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
def graph_shell_html(height: int = 520) -> str:
|
| 587 |
+
"""Stable, model-free canvas shell. Data arrives via postMessage (incremental).
|
| 588 |
+
|
| 589 |
+
The HTML is constant for a given height, so Streamlit reuses the same iframe
|
| 590 |
+
across reruns instead of remounting it. The graph is populated / grown by
|
| 591 |
+
``courier_html`` messages.
|
| 592 |
+
"""
|
| 593 |
+
payload = json.dumps(
|
| 594 |
+
{"nodes": [], "edges": [], "kindMeta": _KIND_META_JS, "kindOrder": _KIND_ORDER}
|
| 595 |
+
)
|
| 596 |
+
row_h = max(300, height - 30)
|
| 597 |
+
return f"""<!DOCTYPE html>
|
| 598 |
+
<html>
|
| 599 |
+
<head>
|
| 600 |
+
<meta charset="utf-8" />
|
| 601 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 602 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
| 603 |
+
<style>
|
| 604 |
+
html, body {{ margin:0; padding:0; background:transparent; font-family: Inter, system-ui, sans-serif; color:#13201d; }}
|
| 605 |
+
* {{ box-sizing:border-box; }}
|
| 606 |
+
.wrap {{ border-top:1px solid rgba(19,32,29,0.12); padding-top:0.5rem; }}
|
| 607 |
+
.kicker {{ font-size:0.68rem; font-weight:700; letter-spacing:0.12em; text-transform:uppercase; color:#6a7a74; margin-bottom:0.4rem; }}
|
| 608 |
+
.row {{ display:flex; gap:0.6rem; align-items:stretch; height:{row_h}px; }}
|
| 609 |
+
/* ── Control rail ── */
|
| 610 |
+
.rail {{
|
| 611 |
+
width:210px; flex:0 0 210px; display:flex; flex-direction:column; overflow:hidden;
|
| 612 |
+
border:1px solid rgba(15,23,42,0.10); border-radius:11px; background:#ffffff;
|
| 613 |
+
}}
|
| 614 |
+
.rail-head {{ padding:0.55rem 0.75rem; border-bottom:1px solid rgba(15,23,42,0.08);
|
| 615 |
+
font-size:0.66rem; font-weight:700; letter-spacing:0.09em; text-transform:uppercase; color:#6a7a74; }}
|
| 616 |
+
.rail-body {{ flex:1; overflow-y:auto; padding:0.6rem 0.65rem; }}
|
| 617 |
+
.rail-search {{ position:relative; margin-bottom:0.7rem; }}
|
| 618 |
+
.rail-search input {{
|
| 619 |
+
width:100%; height:30px; padding:0 1.6rem 0 0.6rem; font-size:0.76rem; font-family:inherit;
|
| 620 |
+
border:1px solid rgba(15,23,42,0.16); border-radius:7px; outline:none; color:#13201d;
|
| 621 |
+
}}
|
| 622 |
+
.rail-search input:focus {{ border-color:#4F6EF7; box-shadow:0 0 0 3px rgba(79,110,247,0.16); }}
|
| 623 |
+
.rail-search .clr {{ position:absolute; right:6px; top:50%; transform:translateY(-50%);
|
| 624 |
+
border:0; background:transparent; color:#94a3b8; cursor:pointer; font-size:0.9rem; line-height:1; }}
|
| 625 |
+
.rail-actions {{ display:grid; grid-template-columns:1fr 1fr; gap:0.35rem; margin-bottom:0.75rem; }}
|
| 626 |
+
.rail-actions button {{
|
| 627 |
+
font-size:0.66rem; font-weight:600; font-family:inherit; padding:0.34rem 0.3rem; cursor:pointer;
|
| 628 |
+
border:1px solid rgba(15,23,42,0.16); border-radius:7px; background:#fff; color:#13201d;
|
| 629 |
+
}}
|
| 630 |
+
.rail-actions button:hover {{ background:#f4f6fb; }}
|
| 631 |
+
.rail-actions button.on {{ border-color:#4F6EF7; background:#eef1fe; color:#1E3A8A; }}
|
| 632 |
+
.sec-head {{ display:flex; align-items:center; justify-content:space-between; margin:0 0 0.35rem 0.15rem; }}
|
| 633 |
+
.sec-head .lbl {{ font-size:0.63rem; font-weight:700; letter-spacing:0.07em; text-transform:uppercase; color:#94a3b8; }}
|
| 634 |
+
.sec-head .acts {{ display:flex; gap:0.45rem; }}
|
| 635 |
+
.sec-head .acts button {{ border:0; background:transparent; font-size:0.62rem; font-weight:600; color:#4F6EF7; cursor:pointer; padding:0; }}
|
| 636 |
+
.cat {{
|
| 637 |
+
width:100%; display:flex; align-items:center; gap:0.5rem; padding:0.28rem 0.35rem; cursor:pointer;
|
| 638 |
+
border:0; background:transparent; border-radius:6px; text-align:left; font-family:inherit;
|
| 639 |
+
}}
|
| 640 |
+
.cat:hover {{ background:#f4f6fb; }}
|
| 641 |
+
.cat .box {{ width:13px; height:13px; flex:0 0 13px; border-radius:3px; border:1.5px solid rgba(15,23,42,0.28);
|
| 642 |
+
display:flex; align-items:center; justify-content:center; color:#fff; font-size:9px; }}
|
| 643 |
+
.cat .box.on {{ background:#4F6EF7; border-color:#4F6EF7; }}
|
| 644 |
+
.cat .dot {{ width:12px; height:12px; flex:0 0 12px; border-radius:50%; }}
|
| 645 |
+
.cat .name {{ flex:1; font-size:0.74rem; color:#334155; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }}
|
| 646 |
+
.cat .cnt {{ font-size:0.68rem; color:#94a3b8; font-variant-numeric:tabular-nums; }}
|
| 647 |
+
.cat.off .name {{ color:#b6c0cc; text-decoration:line-through; }}
|
| 648 |
+
.cat.off .dot {{ opacity:0.35; }}
|
| 649 |
+
/* ── Stage ── */
|
| 650 |
+
.stage {{
|
| 651 |
+
position:relative; flex:1; min-width:0; border-radius:11px; overflow:hidden;
|
| 652 |
+
background:#fafbfd; border:1px solid rgba(15,23,42,0.08);
|
| 653 |
+
}}
|
| 654 |
+
canvas {{ display:block; width:100%; height:100%; cursor:grab; }}
|
| 655 |
+
canvas.dragging {{ cursor:grabbing; }}
|
| 656 |
+
.card {{
|
| 657 |
+
position:absolute; left:12px; top:12px; width:250px; max-height:calc(100% - 24px);
|
| 658 |
+
overflow:auto; background:rgba(255,255,255,0.97); border:1px solid rgba(15,23,42,0.10);
|
| 659 |
+
border-radius:10px; padding:0.7rem 0.75rem; box-shadow:0 10px 30px rgba(15,23,42,0.10);
|
| 660 |
+
display:none; z-index:2;
|
| 661 |
+
}}
|
| 662 |
+
.card.open {{ display:block; }}
|
| 663 |
+
.card-type {{ font-size:0.66rem; font-weight:700; letter-spacing:0.08em; text-transform:uppercase; color:#4F6EF7; margin-bottom:0.25rem; }}
|
| 664 |
+
.card-title {{ font-size:0.9rem; font-weight:700; line-height:1.3; margin:0 0 0.4rem 0; color:#13201d; }}
|
| 665 |
+
.card-detail {{ font-size:0.78rem; line-height:1.45; color:#3d4f4a; margin:0; white-space:pre-wrap; }}
|
| 666 |
+
.card-close {{ position:absolute; right:8px; top:6px; border:0; background:transparent; color:#6a7a74; font-size:1rem; cursor:pointer; }}
|
| 667 |
+
.hint {{ position:absolute; left:10px; bottom:8px; font-size:0.66rem; color:#94a3b8; pointer-events:none;
|
| 668 |
+
background:rgba(255,255,255,0.9); padding:0.2rem 0.45rem; border-radius:6px; }}
|
| 669 |
+
</style>
|
| 670 |
+
</head>
|
| 671 |
+
<body>
|
| 672 |
+
<div class="wrap">
|
| 673 |
+
<div class="kicker">Workflow</div>
|
| 674 |
+
<div class="row">
|
| 675 |
+
<aside class="rail">
|
| 676 |
+
<div class="rail-head">Graph controls</div>
|
| 677 |
+
<div class="rail-body">
|
| 678 |
+
<div class="rail-search">
|
| 679 |
+
<input id="q" type="text" placeholder="Highlight nodes" autocomplete="off" />
|
| 680 |
+
<button class="clr" id="qClr" title="Clear" style="display:none">×</button>
|
| 681 |
+
</div>
|
| 682 |
+
<div class="rail-actions">
|
| 683 |
+
<button id="fitBtn">Fit view</button>
|
| 684 |
+
<button id="resetBtn">Reset</button>
|
| 685 |
+
</div>
|
| 686 |
+
<div class="sec-head">
|
| 687 |
+
<span class="lbl">Node categories</span>
|
| 688 |
+
<span class="acts">
|
| 689 |
+
<button id="allBtn">All</button>
|
| 690 |
+
<button id="noneBtn">None</button>
|
| 691 |
+
</span>
|
| 692 |
+
</div>
|
| 693 |
+
<div id="cats"></div>
|
| 694 |
+
</div>
|
| 695 |
+
</aside>
|
| 696 |
+
<div class="stage" id="stage">
|
| 697 |
+
<canvas id="cv"></canvas>
|
| 698 |
+
<div id="card" class="card">
|
| 699 |
+
<button class="card-close" id="closeBtn" aria-label="Close">×</button>
|
| 700 |
+
<div class="card-type" id="cardType"></div>
|
| 701 |
+
<div class="card-title" id="cardTitle"></div>
|
| 702 |
+
<p class="card-detail" id="cardDetail"></p>
|
| 703 |
+
</div>
|
| 704 |
+
<div class="hint">Drag to move · scroll to zoom · click a node for details</div>
|
| 705 |
+
</div>
|
| 706 |
+
</div>
|
| 707 |
+
</div>
|
| 708 |
+
<script>
|
| 709 |
+
const payload = {payload};
|
| 710 |
+
const KM = payload.kindMeta || {{}};
|
| 711 |
+
const stage = document.getElementById('stage');
|
| 712 |
+
const canvas = document.getElementById('cv');
|
| 713 |
+
const ctx = canvas.getContext('2d');
|
| 714 |
+
const card = document.getElementById('card');
|
| 715 |
+
const cardType = document.getElementById('cardType');
|
| 716 |
+
const cardTitle = document.getElementById('cardTitle');
|
| 717 |
+
const cardDetail = document.getElementById('cardDetail');
|
| 718 |
+
document.getElementById('closeBtn').onclick = () => {{ selected = null; card.classList.remove('open'); }};
|
| 719 |
+
|
| 720 |
+
let W = 0, H = 0, dpr = 1;
|
| 721 |
+
function resize() {{
|
| 722 |
+
dpr = window.devicePixelRatio || 1;
|
| 723 |
+
W = stage.clientWidth; H = stage.clientHeight;
|
| 724 |
+
canvas.width = Math.floor(W * dpr);
|
| 725 |
+
canvas.height = Math.floor(H * dpr);
|
| 726 |
+
canvas.style.width = W + 'px';
|
| 727 |
+
canvas.style.height = H + 'px';
|
| 728 |
+
}}
|
| 729 |
+
resize();
|
| 730 |
+
window.addEventListener('resize', () => {{ resize(); }});
|
| 731 |
+
|
| 732 |
+
// ── Position memory across reruns (Mina keeps xy so the graph grows, not jumps).
|
| 733 |
+
const memKey = 'scipaths-mina-pos';
|
| 734 |
+
let saved = {{}};
|
| 735 |
+
try {{ saved = JSON.parse(sessionStorage.getItem(memKey) || '{{}}'); }} catch (e) {{ saved = {{}}; }}
|
| 736 |
+
|
| 737 |
+
// Mutable graph state — grown in place by incremental postMessage updates.
|
| 738 |
+
let nodes = [];
|
| 739 |
+
const byId = new Map();
|
| 740 |
+
let edges = [];
|
| 741 |
+
let target = null;
|
| 742 |
+
|
| 743 |
+
// ── Filter / search state ──
|
| 744 |
+
const hiddenKinds = new Set();
|
| 745 |
+
let query = '';
|
| 746 |
+
const isVisible = (n) => !hiddenKinds.has(n.kind);
|
| 747 |
+
|
| 748 |
+
// ── Write pulse — Mina: staggered one-shot expanding rings + travelling dashes.
|
| 749 |
+
const PULSE_RING_MS = 1200;
|
| 750 |
+
const PULSE_RING_OFFSETS = [0, 0.35];
|
| 751 |
+
const PULSE_LIFETIME_MS = PULSE_RING_MS * (1 + Math.max(...PULSE_RING_OFFSETS));
|
| 752 |
+
const PULSE_DASH = [7, 5];
|
| 753 |
+
let pulseNodeIds = new Set();
|
| 754 |
+
let pulseEdgeIds = new Set();
|
| 755 |
+
let pulseStart = 0;
|
| 756 |
+
|
| 757 |
+
// ── View transform (zoom + pan), Mina-style ──
|
| 758 |
+
let zoom = 0.95;
|
| 759 |
+
let panX = (W / 2) * (1 - zoom);
|
| 760 |
+
let panY = (H / 2) * (1 - zoom);
|
| 761 |
+
let alpha = 1;
|
| 762 |
+
const toWorld = (px, py) => ({{ x: (px - panX) / zoom, y: (py - panY) / zoom }});
|
| 763 |
+
|
| 764 |
+
let dragNode = null, panning = false, moved = false;
|
| 765 |
+
let last = {{ x: 0, y: 0 }};
|
| 766 |
+
let selected = null;
|
| 767 |
+
|
| 768 |
+
function persist() {{
|
| 769 |
+
const out = {{}};
|
| 770 |
+
nodes.forEach(n => {{ out[n.id] = {{ x: n.x, y: n.y }}; }});
|
| 771 |
+
try {{ sessionStorage.setItem(memKey, JSON.stringify(out)); }} catch (e) {{}}
|
| 772 |
+
}}
|
| 773 |
+
|
| 774 |
+
function nodeAt(px, py) {{
|
| 775 |
+
const w = toWorld(px, py);
|
| 776 |
+
for (let i = nodes.length - 1; i >= 0; i--) {{
|
| 777 |
+
const n = nodes[i];
|
| 778 |
+
if (!isVisible(n)) continue;
|
| 779 |
+
const dx = n.x - w.x, dy = n.y - w.y;
|
| 780 |
+
if (dx * dx + dy * dy <= (n.r + 5) * (n.r + 5)) return n;
|
| 781 |
+
}}
|
| 782 |
+
return null;
|
| 783 |
+
}}
|
| 784 |
+
const getPos = (ev) => {{ const r = canvas.getBoundingClientRect(); return {{ x: ev.clientX - r.left, y: ev.clientY - r.top }}; }};
|
| 785 |
+
|
| 786 |
+
canvas.addEventListener('mousedown', (ev) => {{
|
| 787 |
+
const p = getPos(ev); const n = nodeAt(p.x, p.y); moved = false;
|
| 788 |
+
if (n) {{ dragNode = n; alpha = Math.max(alpha, 0.4); }} else {{ panning = true; }}
|
| 789 |
+
last = p; canvas.classList.add('dragging');
|
| 790 |
+
}});
|
| 791 |
+
canvas.addEventListener('mousemove', (ev) => {{
|
| 792 |
+
const p = getPos(ev);
|
| 793 |
+
if (dragNode) {{ const w = toWorld(p.x, p.y); dragNode.x = w.x; dragNode.y = w.y; dragNode.vx = 0; dragNode.vy = 0; moved = true; }}
|
| 794 |
+
else if (panning) {{ panX += p.x - last.x; panY += p.y - last.y; moved = true; }}
|
| 795 |
+
else {{ canvas.style.cursor = nodeAt(p.x, p.y) ? 'pointer' : 'grab'; }}
|
| 796 |
+
last = p;
|
| 797 |
+
}});
|
| 798 |
+
window.addEventListener('mouseup', (ev) => {{
|
| 799 |
+
if (ev.target === canvas && !moved) {{
|
| 800 |
+
const p = getPos(ev); const n = nodeAt(p.x, p.y);
|
| 801 |
+
if (n) {{
|
| 802 |
+
selected = n;
|
| 803 |
+
cardType.textContent = n.typeLabel || n.kind || 'Node';
|
| 804 |
+
cardTitle.textContent = n.fullTitle || n.label || '';
|
| 805 |
+
cardDetail.textContent = n.detail || 'No additional detail for this node.';
|
| 806 |
+
card.classList.add('open');
|
| 807 |
+
}} else {{ selected = null; card.classList.remove('open'); }}
|
| 808 |
+
}}
|
| 809 |
+
if (dragNode) persist();
|
| 810 |
+
dragNode = null; panning = false; canvas.classList.remove('dragging');
|
| 811 |
+
}});
|
| 812 |
+
canvas.addEventListener('wheel', (ev) => {{
|
| 813 |
+
ev.preventDefault();
|
| 814 |
+
const p = getPos(ev); const before = toWorld(p.x, p.y);
|
| 815 |
+
const factor = ev.deltaY < 0 ? 1.1 : 0.9;
|
| 816 |
+
zoom = Math.max(0.3, Math.min(2.5, zoom * factor));
|
| 817 |
+
panX = p.x - before.x * zoom; panY = p.y - before.y * zoom;
|
| 818 |
+
}}, {{ passive: false }});
|
| 819 |
+
|
| 820 |
+
// ── Force layout — Mina model: inverse-square repulsion + springs +
|
| 821 |
+
// center gravity + collision relaxation + annealing alpha (looser). ──
|
| 822 |
+
function tick() {{
|
| 823 |
+
const N = nodes.length;
|
| 824 |
+
for (let i = 0; i < N; i++) {{
|
| 825 |
+
for (let j = i + 1; j < N; j++) {{
|
| 826 |
+
const a = nodes[i], b = nodes[j];
|
| 827 |
+
let dx = b.x - a.x, dy = b.y - a.y;
|
| 828 |
+
let d2 = dx * dx + dy * dy; if (d2 < 1) d2 = 1;
|
| 829 |
+
const d = Math.sqrt(d2);
|
| 830 |
+
const force = (5600 * alpha) / d2;
|
| 831 |
+
dx /= d; dy /= d;
|
| 832 |
+
if (a !== dragNode && !a.fixed) {{ a.vx -= dx * force; a.vy -= dy * force; }}
|
| 833 |
+
if (b !== dragNode && !b.fixed) {{ b.vx += dx * force; b.vy += dy * force; }}
|
| 834 |
+
}}
|
| 835 |
+
}}
|
| 836 |
+
edges.forEach(e => {{
|
| 837 |
+
const a = byId.get(e.source), b = byId.get(e.target);
|
| 838 |
+
if (!a || !b) return;
|
| 839 |
+
const ideal = 155;
|
| 840 |
+
let dx = b.x - a.x, dy = b.y - a.y;
|
| 841 |
+
const d = Math.max(1, Math.sqrt(dx * dx + dy * dy));
|
| 842 |
+
const force = ((d - ideal) / d) * 0.045 * alpha;
|
| 843 |
+
dx *= force; dy *= force;
|
| 844 |
+
if (!a.fixed && a !== dragNode) {{ a.vx += dx; a.vy += dy; }}
|
| 845 |
+
if (!b.fixed && b !== dragNode) {{ b.vx -= dx; b.vy -= dy; }}
|
| 846 |
+
}});
|
| 847 |
+
const cx = W / 2, cy = H / 2;
|
| 848 |
+
nodes.forEach(n => {{
|
| 849 |
+
if (n.fixed || n === dragNode) return;
|
| 850 |
+
n.vx += (cx - n.x) * 0.0042 * alpha;
|
| 851 |
+
n.vy += (cy - n.y) * 0.0042 * alpha;
|
| 852 |
+
n.vx *= 0.86; n.vy *= 0.86;
|
| 853 |
+
n.x += n.vx; n.y += n.vy;
|
| 854 |
+
}});
|
| 855 |
+
// Hard collision relaxation so nothing overlaps (with label breathing room).
|
| 856 |
+
for (let i = 0; i < N; i++) {{
|
| 857 |
+
for (let j = i + 1; j < N; j++) {{
|
| 858 |
+
const a = nodes[i], b = nodes[j];
|
| 859 |
+
const dx = b.x - a.x, dy = b.y - a.y;
|
| 860 |
+
const min = a.r + b.r + 18;
|
| 861 |
+
const d = Math.sqrt(dx * dx + dy * dy) || 0.01;
|
| 862 |
+
if (d < min) {{
|
| 863 |
+
const push = (min - d) / d;
|
| 864 |
+
const ox = dx * push * 0.5, oy = dy * push * 0.5;
|
| 865 |
+
if (a !== dragNode && !a.fixed) {{ a.x -= ox; a.y -= oy; }}
|
| 866 |
+
if (b !== dragNode && !b.fixed) {{ b.x += ox; b.y += oy; }}
|
| 867 |
+
}}
|
| 868 |
+
}}
|
| 869 |
+
}}
|
| 870 |
+
if (target) {{ target.x = cx; target.y = cy; target.vx = 0; target.vy = 0; }}
|
| 871 |
+
alpha = Math.max(0.02, alpha * 0.9955);
|
| 872 |
+
for (const n of nodes) saved[n.id] = {{ x: n.x, y: n.y }};
|
| 873 |
+
}}
|
| 874 |
+
|
| 875 |
+
function draw() {{
|
| 876 |
+
tick();
|
| 877 |
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
| 878 |
+
ctx.clearRect(0, 0, W, H);
|
| 879 |
+
ctx.fillStyle = '#fafbfd';
|
| 880 |
+
ctx.fillRect(0, 0, W, H);
|
| 881 |
+
ctx.translate(panX, panY);
|
| 882 |
+
ctx.scale(zoom, zoom);
|
| 883 |
+
|
| 884 |
+
const q = query.trim().toLowerCase();
|
| 885 |
+
const pulseAge = performance.now() - pulseStart;
|
| 886 |
+
const pulseAlive = pulseAge < PULSE_LIFETIME_MS;
|
| 887 |
+
const pulseFade = pulseAlive ? 1 - pulseAge / PULSE_LIFETIME_MS : 0;
|
| 888 |
+
|
| 889 |
+
// edges
|
| 890 |
+
edges.forEach(e => {{
|
| 891 |
+
const a = byId.get(e.source), b = byId.get(e.target);
|
| 892 |
+
if (!a || !b || !isVisible(a) || !isVisible(b)) return;
|
| 893 |
+
const pulsing = pulseAlive && pulseEdgeIds.has(e.id);
|
| 894 |
+
ctx.beginPath();
|
| 895 |
+
ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y);
|
| 896 |
+
ctx.strokeStyle = e.muted ? 'rgba(148,163,184,0.30)' : 'rgba(100,116,139,0.42)';
|
| 897 |
+
ctx.lineWidth = (e.muted ? 1 : 1.4) + (pulsing ? 1.2 * pulseFade : 0);
|
| 898 |
+
if (pulsing) {{
|
| 899 |
+
ctx.setLineDash(PULSE_DASH);
|
| 900 |
+
ctx.lineDashOffset = -((pulseAge / 26) % (PULSE_DASH[0] + PULSE_DASH[1]));
|
| 901 |
+
}} else {{
|
| 902 |
+
ctx.setLineDash(e.muted ? [3, 3] : []);
|
| 903 |
+
ctx.lineDashOffset = 0;
|
| 904 |
+
}}
|
| 905 |
+
ctx.stroke();
|
| 906 |
+
ctx.setLineDash([]); ctx.lineDashOffset = 0;
|
| 907 |
+
}});
|
| 908 |
+
|
| 909 |
+
// nodes — exact Mina draw order
|
| 910 |
+
nodes.forEach(n => {{
|
| 911 |
+
if (!isVisible(n)) return;
|
| 912 |
+
const x = n.x, y = n.y, r = n.r;
|
| 913 |
+
const isSelected = selected && selected.id === n.id;
|
| 914 |
+
const dimmed = q.length > 0 && !String(n.label || '').toLowerCase().includes(q);
|
| 915 |
+
ctx.globalAlpha = dimmed ? 0.15 : 1;
|
| 916 |
+
|
| 917 |
+
if (pulseAlive && pulseNodeIds.has(n.id)) {{
|
| 918 |
+
for (const offset of PULSE_RING_OFFSETS) {{
|
| 919 |
+
const p = pulseAge / PULSE_RING_MS - offset;
|
| 920 |
+
if (p <= 0 || p >= 1) continue;
|
| 921 |
+
ctx.beginPath();
|
| 922 |
+
ctx.arc(x, y, r + 3 + p * 26, 0, Math.PI * 2);
|
| 923 |
+
ctx.strokeStyle = n.fill;
|
| 924 |
+
ctx.globalAlpha = (1 - p) * 0.55;
|
| 925 |
+
ctx.lineWidth = 2.5 * (1 - p) + 0.5;
|
| 926 |
+
ctx.stroke();
|
| 927 |
+
}}
|
| 928 |
+
ctx.globalAlpha = dimmed ? 0.15 : 1;
|
| 929 |
+
}}
|
| 930 |
+
|
| 931 |
+
if (isSelected) {{
|
| 932 |
+
ctx.beginPath();
|
| 933 |
+
ctx.arc(x, y, r + 7, 0, Math.PI * 2);
|
| 934 |
+
ctx.fillStyle = n.fill + '22'; ctx.fill();
|
| 935 |
+
ctx.beginPath();
|
| 936 |
+
ctx.arc(x, y, r + 5, 0, Math.PI * 2);
|
| 937 |
+
ctx.strokeStyle = n.ring; ctx.lineWidth = 1.5; ctx.stroke();
|
| 938 |
+
}}
|
| 939 |
+
|
| 940 |
+
ctx.beginPath();
|
| 941 |
+
ctx.arc(x, y, r, 0, Math.PI * 2);
|
| 942 |
+
ctx.fillStyle = n.fill; ctx.fill();
|
| 943 |
+
ctx.lineWidth = 2; ctx.strokeStyle = '#fff'; ctx.stroke();
|
| 944 |
+
ctx.beginPath();
|
| 945 |
+
ctx.arc(x, y, r + 1.5, 0, Math.PI * 2);
|
| 946 |
+
ctx.lineWidth = 1.25; ctx.strokeStyle = n.ring; ctx.stroke();
|
| 947 |
+
|
| 948 |
+
const raw = String(n.label || '');
|
| 949 |
+
const label = raw.length > 26 ? raw.slice(0, 25) + '…' : raw;
|
| 950 |
+
const weight = n.kind === 'target' ? '700 12px' : (n.kind === 'cluster' ? '600 11px' : '500 10px');
|
| 951 |
+
ctx.font = weight + ' Inter, sans-serif';
|
| 952 |
+
ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic';
|
| 953 |
+
ctx.lineWidth = 3; ctx.strokeStyle = 'rgba(250,251,253,0.9)';
|
| 954 |
+
ctx.strokeText(label, x, y + r + 13);
|
| 955 |
+
ctx.fillStyle = n.text;
|
| 956 |
+
ctx.fillText(label, x, y + r + 13);
|
| 957 |
+
ctx.globalAlpha = 1;
|
| 958 |
+
}});
|
| 959 |
+
|
| 960 |
+
requestAnimationFrame(draw);
|
| 961 |
+
}}
|
| 962 |
+
requestAnimationFrame(draw);
|
| 963 |
+
setTimeout(persist, 900);
|
| 964 |
+
|
| 965 |
+
// ── Fit / reset view ──
|
| 966 |
+
function fitView() {{
|
| 967 |
+
const vis = nodes.filter(isVisible);
|
| 968 |
+
if (!vis.length) return;
|
| 969 |
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
| 970 |
+
vis.forEach(n => {{ minX = Math.min(minX, n.x - n.r); minY = Math.min(minY, n.y - n.r);
|
| 971 |
+
maxX = Math.max(maxX, n.x + n.r + 20); maxY = Math.max(maxY, n.y + n.r + 20); }});
|
| 972 |
+
const gw = Math.max(1, maxX - minX), gh = Math.max(1, maxY - minY);
|
| 973 |
+
zoom = Math.max(0.3, Math.min(1.6, Math.min((W - 40) / gw, (H - 40) / gh)));
|
| 974 |
+
panX = W / 2 - ((minX + maxX) / 2) * zoom;
|
| 975 |
+
panY = H / 2 - ((minY + maxY) / 2) * zoom;
|
| 976 |
+
}}
|
| 977 |
+
document.getElementById('fitBtn').onclick = fitView;
|
| 978 |
+
document.getElementById('resetBtn').onclick = () => {{
|
| 979 |
+
hiddenKinds.clear(); query = ''; document.getElementById('q').value = '';
|
| 980 |
+
document.getElementById('qClr').style.display = 'none';
|
| 981 |
+
selected = null; card.classList.remove('open');
|
| 982 |
+
try {{ sessionStorage.removeItem(memKey); }} catch (e) {{}}
|
| 983 |
+
alpha = 1; renderCats();
|
| 984 |
+
const cx = W / 2, cy = H / 2;
|
| 985 |
+
nodes.forEach((n, i) => {{
|
| 986 |
+
const a = (i / Math.max(nodes.length, 1)) * Math.PI * 2;
|
| 987 |
+
n.x = n.id === 'target' ? cx : cx + Math.cos(a) * (90 + Math.random() * 70);
|
| 988 |
+
n.y = n.id === 'target' ? cy : cy + Math.sin(a) * (90 + Math.random() * 70);
|
| 989 |
+
n.vx = 0; n.vy = 0;
|
| 990 |
+
}});
|
| 991 |
+
zoom = 0.95; panX = (W / 2) * (1 - zoom); panY = (H / 2) * (1 - zoom);
|
| 992 |
+
}};
|
| 993 |
+
|
| 994 |
+
// ── Search ──
|
| 995 |
+
const qInput = document.getElementById('q');
|
| 996 |
+
const qClr = document.getElementById('qClr');
|
| 997 |
+
qInput.addEventListener('input', () => {{ query = qInput.value; qClr.style.display = query ? 'block' : 'none'; }});
|
| 998 |
+
qClr.onclick = () => {{ query = ''; qInput.value = ''; qClr.style.display = 'none'; }};
|
| 999 |
+
|
| 1000 |
+
// ── Node category filter rail ──
|
| 1001 |
+
function counts() {{
|
| 1002 |
+
const m = {{}};
|
| 1003 |
+
nodes.forEach(n => {{ m[n.kind] = (m[n.kind] || 0) + 1; }});
|
| 1004 |
+
return m;
|
| 1005 |
+
}}
|
| 1006 |
+
function renderCats() {{
|
| 1007 |
+
const c = counts();
|
| 1008 |
+
const order = (payload.kindOrder || Object.keys(KM)).filter(k => (c[k] || 0) > 0);
|
| 1009 |
+
const host = document.getElementById('cats');
|
| 1010 |
+
host.innerHTML = '';
|
| 1011 |
+
order.forEach(k => {{
|
| 1012 |
+
const meta = KM[k] || {{ fill: '#94a3b8', ring: '#e2e8f0', label: k }};
|
| 1013 |
+
const on = !hiddenKinds.has(k);
|
| 1014 |
+
const btn = document.createElement('button');
|
| 1015 |
+
btn.className = 'cat' + (on ? '' : ' off');
|
| 1016 |
+
btn.innerHTML =
|
| 1017 |
+
'<span class="box' + (on ? ' on' : '') + '">' + (on ? '✓' : '') + '</span>' +
|
| 1018 |
+
'<span class="dot" style="background:' + meta.fill + ';box-shadow:0 0 0 2px ' + meta.ring + '"></span>' +
|
| 1019 |
+
'<span class="name">' + meta.label + '</span>' +
|
| 1020 |
+
'<span class="cnt">' + (c[k] || 0) + '</span>';
|
| 1021 |
+
btn.onclick = () => {{
|
| 1022 |
+
if (hiddenKinds.has(k)) hiddenKinds.delete(k); else hiddenKinds.add(k);
|
| 1023 |
+
alpha = Math.max(alpha, 0.3); renderCats();
|
| 1024 |
+
}};
|
| 1025 |
+
host.appendChild(btn);
|
| 1026 |
+
}});
|
| 1027 |
+
}}
|
| 1028 |
+
document.getElementById('allBtn').onclick = () => {{ hiddenKinds.clear(); alpha = Math.max(alpha, 0.3); renderCats(); }};
|
| 1029 |
+
document.getElementById('noneBtn').onclick = () => {{
|
| 1030 |
+
Object.keys(counts()).forEach(k => hiddenKinds.add(k)); renderCats();
|
| 1031 |
+
}};
|
| 1032 |
+
renderCats();
|
| 1033 |
+
|
| 1034 |
+
// ── Incremental model merge (Mina reveal): add new nodes near a neighbor,
|
| 1035 |
+
// keep existing positions, pulse writes, drop nodes no longer present. ──
|
| 1036 |
+
let lastSig = '';
|
| 1037 |
+
function applyModel(model) {{
|
| 1038 |
+
if (!model) return;
|
| 1039 |
+
const inNodes = model.nodes || [];
|
| 1040 |
+
const inEdges = model.edges || [];
|
| 1041 |
+
const sig = inNodes.map(n => n.id + (n.pulse ? '*' : '')).join(',') + '|' + inEdges.map(e => e.id).join(',');
|
| 1042 |
+
if (sig === lastSig) return;
|
| 1043 |
+
lastSig = sig;
|
| 1044 |
+
if (model.kindMeta) Object.assign(KM, model.kindMeta);
|
| 1045 |
+
if (model.kindOrder) payload.kindOrder = model.kindOrder;
|
| 1046 |
+
const incIds = new Set(inNodes.map(n => n.id));
|
| 1047 |
+
const adj = {{}};
|
| 1048 |
+
inEdges.forEach(e => {{ (adj[e.source] = adj[e.source] || []).push(e.target); (adj[e.target] = adj[e.target] || []).push(e.source); }});
|
| 1049 |
+
const pN = new Set(), pE = new Set();
|
| 1050 |
+
let added = false;
|
| 1051 |
+
inNodes.forEach(n => {{
|
| 1052 |
+
let nd = byId.get(n.id);
|
| 1053 |
+
if (nd) {{
|
| 1054 |
+
nd.label = n.label; nd.kind = n.kind; nd.fill = n.fill; nd.ring = n.ring;
|
| 1055 |
+
nd.text = n.text; nd.r = n.r; nd.typeLabel = n.typeLabel; nd.fullTitle = n.fullTitle; nd.detail = n.detail;
|
| 1056 |
+
if (n.pulse) pN.add(n.id);
|
| 1057 |
+
}} else {{
|
| 1058 |
+
const prev = saved[n.id];
|
| 1059 |
+
let x, y;
|
| 1060 |
+
if (prev) {{ x = prev.x; y = prev.y; }}
|
| 1061 |
+
else {{
|
| 1062 |
+
const nb = (adj[n.id] || []).map(id => byId.get(id)).find(Boolean);
|
| 1063 |
+
const base = nb ? {{ x: nb.x, y: nb.y }} : {{ x: W / 2, y: H / 2 }};
|
| 1064 |
+
const a = Math.random() * Math.PI * 2, rr = 70 + Math.random() * 60;
|
| 1065 |
+
x = base.x + Math.cos(a) * rr; y = base.y + Math.sin(a) * rr;
|
| 1066 |
+
}}
|
| 1067 |
+
nd = Object.assign({{}}, n, {{ x, y, vx: 0, vy: 0, fixed: n.id === 'target' }});
|
| 1068 |
+
nodes.push(nd); byId.set(n.id, nd); added = true; pN.add(n.id);
|
| 1069 |
+
}}
|
| 1070 |
+
}});
|
| 1071 |
+
for (let i = nodes.length - 1; i >= 0; i--) {{
|
| 1072 |
+
if (!incIds.has(nodes[i].id)) {{ byId.delete(nodes[i].id); nodes.splice(i, 1); }}
|
| 1073 |
+
}}
|
| 1074 |
+
edges = inEdges.filter(e => byId.get(e.source) && byId.get(e.target)).map(e => Object.assign({{}}, e));
|
| 1075 |
+
edges.forEach(e => {{ if (e.pulse) pE.add(e.id); }});
|
| 1076 |
+
pulseNodeIds = pN; pulseEdgeIds = pE;
|
| 1077 |
+
if (pN.size || pE.size || added) {{ pulseStart = performance.now(); alpha = Math.max(alpha, 0.55); }}
|
| 1078 |
+
target = byId.get('target'); if (target) target.fixed = true;
|
| 1079 |
+
renderCats();
|
| 1080 |
+
}}
|
| 1081 |
+
window.addEventListener('message', (e) => {{
|
| 1082 |
+
const d = e.data;
|
| 1083 |
+
if (d && d.type === 'scipaths-graph') applyModel(d.model);
|
| 1084 |
+
}});
|
| 1085 |
+
if (payload.nodes && payload.nodes.length) applyModel(payload);
|
| 1086 |
+
</script>
|
| 1087 |
+
</body>
|
| 1088 |
+
</html>"""
|
| 1089 |
+
|
| 1090 |
+
|
| 1091 |
+
def sync_and_load_graph(run_id: str, model: dict[str, Any]) -> dict[str, Any]:
|
| 1092 |
+
"""Push cumulative graph to Neo4j and read it back for rendering."""
|
| 1093 |
+
if not run_id or neo4j_store is None:
|
| 1094 |
+
model = dict(model)
|
| 1095 |
+
model["backend"] = "memory"
|
| 1096 |
+
return model
|
| 1097 |
+
nodes = model.get("nodes") or []
|
| 1098 |
+
edges = model.get("edges") or []
|
| 1099 |
+
ok = neo4j_store.upsert_graph(run_id, nodes, edges)
|
| 1100 |
+
if not ok:
|
| 1101 |
+
model = dict(model)
|
| 1102 |
+
model["backend"] = "memory"
|
| 1103 |
+
return model
|
| 1104 |
+
fetched = neo4j_store.fetch_graph(run_id)
|
| 1105 |
+
if not fetched:
|
| 1106 |
+
model = dict(model)
|
| 1107 |
+
model["backend"] = "neo4j-write"
|
| 1108 |
+
return model
|
| 1109 |
+
# Preserve caption/step and enrich fetched nodes with Mina colors.
|
| 1110 |
+
out_nodes = []
|
| 1111 |
+
for n in fetched.get("nodes") or []:
|
| 1112 |
+
kind = str(n.get("kind") or "citing")
|
| 1113 |
+
meta = KIND_META.get(kind, KIND_META["citing"])
|
| 1114 |
+
out_nodes.append(
|
| 1115 |
+
{
|
| 1116 |
+
**n,
|
| 1117 |
+
"type_label": meta["type_label"],
|
| 1118 |
+
"color": n.get("color") or meta["fill"],
|
| 1119 |
+
"fill": n.get("fill") or meta["fill"],
|
| 1120 |
+
"ring": n.get("ring") or meta["ring"],
|
| 1121 |
+
"text": n.get("text") or meta["text"],
|
| 1122 |
+
"r": n.get("r") or meta["r"],
|
| 1123 |
+
"size": n.get("size") or meta["r"],
|
| 1124 |
+
}
|
| 1125 |
+
)
|
| 1126 |
+
return {
|
| 1127 |
+
"step": model.get("step"),
|
| 1128 |
+
"pulse_step": model.get("pulse_step"),
|
| 1129 |
+
"caption": model.get("caption"),
|
| 1130 |
+
"nodes": out_nodes,
|
| 1131 |
+
"edges": fetched.get("edges") or [],
|
| 1132 |
+
"backend": "neo4j",
|
| 1133 |
+
}
|
| 1134 |
+
|
| 1135 |
+
|
| 1136 |
+
def build_synced_model(
|
| 1137 |
+
*,
|
| 1138 |
+
paper_dir: Optional[Path],
|
| 1139 |
+
payload: Optional[dict],
|
| 1140 |
+
events: list[str],
|
| 1141 |
+
run_id: str = "",
|
| 1142 |
+
) -> dict[str, Any]:
|
| 1143 |
+
"""Build the cumulative graph model for the given events and sync to Neo4j."""
|
| 1144 |
+
visible = visible_step_from_events(events)
|
| 1145 |
+
pulse = pulse_step_from_events(events)
|
| 1146 |
+
joined = " ".join(str(e) for e in (events or []))
|
| 1147 |
+
if "Pipeline completed successfully." in joined or (
|
| 1148 |
+
"Step 8 complete" in joined or ("annotation" in joined.lower() and "complete" in joined.lower())
|
| 1149 |
+
):
|
| 1150 |
+
if visible >= 8:
|
| 1151 |
+
pulse = 0
|
| 1152 |
+
visible = 8
|
| 1153 |
+
|
| 1154 |
+
model = build_graph_model(
|
| 1155 |
+
paper_dir=paper_dir,
|
| 1156 |
+
payload=payload,
|
| 1157 |
+
visible_step=visible,
|
| 1158 |
+
pulse_step=pulse,
|
| 1159 |
+
)
|
| 1160 |
+
return sync_and_load_graph(run_id or "default", model)
|
| 1161 |
+
|
| 1162 |
+
|
| 1163 |
+
def render_graph_shell(placeholder, *, height: int = 520) -> None:
|
| 1164 |
+
"""Mount the persistent, model-free canvas shell (constant HTML → no remount)."""
|
| 1165 |
+
html = graph_shell_html(height)
|
| 1166 |
+
if placeholder is None:
|
| 1167 |
+
st.iframe(html, height=height)
|
| 1168 |
+
return
|
| 1169 |
+
with placeholder.container():
|
| 1170 |
+
st.iframe(html, height=height)
|
| 1171 |
+
|
| 1172 |
+
|
| 1173 |
+
def push_graph_update(courier_slot, model: dict[str, Any], *, height: int = 0) -> None:
|
| 1174 |
+
"""Push a model into the mounted shell via an invisible courier frame."""
|
| 1175 |
+
html = courier_html(_graph_payload(model))
|
| 1176 |
+
if courier_slot is None:
|
| 1177 |
+
st.components.v1.html(html, height=height)
|
| 1178 |
+
return
|
| 1179 |
+
with courier_slot.container():
|
| 1180 |
+
st.components.v1.html(html, height=height)
|
| 1181 |
+
|
| 1182 |
+
|
| 1183 |
+
def render_workflow_into(
|
| 1184 |
+
placeholder,
|
| 1185 |
+
*,
|
| 1186 |
+
paper_dir: Optional[Path],
|
| 1187 |
+
payload: Optional[dict],
|
| 1188 |
+
events: list[str],
|
| 1189 |
+
run_id: str = "",
|
| 1190 |
+
height: int = 520,
|
| 1191 |
+
) -> None:
|
| 1192 |
+
"""Back-compat single-slot render: mount shell then push once into it."""
|
| 1193 |
+
render_graph_shell(placeholder, height=height)
|
| 1194 |
+
model = build_synced_model(
|
| 1195 |
+
paper_dir=paper_dir, payload=payload, events=events, run_id=run_id
|
| 1196 |
+
)
|
| 1197 |
+
push_graph_update(None, model)
|
replay_traces/2208.00329/input_ids.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "2208.00329",
|
| 4 |
+
"title": "",
|
| 5 |
+
"id_type": "ArXiv"
|
| 6 |
+
}
|
| 7 |
+
]
|
replay_traces/2208.00329/logs/step_01.log
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_01_fetch/fetch_metadata.py --ids /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/input_ids.json --outdir /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 2 |
+
|
| 3 |
+
/Users/dingzifeng/SciFy-UMBC/demo_2026_07/src/step_01_fetch/fetch_metadata.py:95: DeprecationWarning: The 'Search.results' method is deprecated, use 'Client.results' instead
|
| 4 |
+
paper = next(search.results())
|
| 5 |
+
/Users/dingzifeng/SciFy-UMBC/demo_2026_07/src/step_01_fetch/fetch_metadata.py:124: DeprecationWarning: Python 3.14 will, by default, filter extracted tar archives and reject files or modify their metadata. Use the filter argument to control this behavior.
|
| 6 |
+
tar.extractall(path=source_dir)
|
| 7 |
+
[INFO] Using ID list from /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/input_ids.json
|
| 8 |
+
[INFO] Output will be saved to /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 9 |
+
[DEBUG] fetch_one_acl_id: id=2208.00329 id_type=ArXiv title_len=0 s2_key_present=yes s2_key_len=44
|
| 10 |
+
[DEBUG] wrote metadata to /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers/2208.00329/paper_metadata.json
|
| 11 |
+
[INFO] Wrote combined BibTeX to /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers/2208.00329/references.bib
|
| 12 |
+
[INFO] Processed LaTeX for 2208.00329 at /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers/2208.00329/processed_main.tex
|
| 13 |
+
[WARN] 429 Too Many Requests → retrying in 1.50s
|
| 14 |
+
[WARN] 429 Too Many Requests → retrying in 2.15s
|
| 15 |
+
[WARN] 429 Too Many Requests → retrying in 4.24s
|
| 16 |
+
[WARN] 429 Too Many Requests → retrying in 8.26s
|
| 17 |
+
[SUCCESS]
|
| 18 |
+
done in 28.508277893066406 s
|
replay_traces/2208.00329/logs/step_02.log
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_02_mark_citations/replace_citation_markers.py --root /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 2 |
+
|
| 3 |
+
[INFO] Found 1 paper dirs under /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 4 |
+
[OK] 2208.00329: updated 19 contexts over 20
|
| 5 |
+
[SUMMARY] total_updated=19 over 20; skipped_incomplete=0
|
replay_traces/2208.00329/logs/step_03.log
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_03_usage_contexts/build_usage_contexts.py --root /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers --out-name usage_contexts.json
|
| 2 |
+
|
| 3 |
+
[INFO] Found 1 paper dirs under /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 4 |
+
[OK] 2208.00329: wrote usage_contexts.json (20 contexts from 7 citations)
|
replay_traces/2208.00329/logs/step_04.log
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_04_label_citations/label_citation_functions.py --root /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers --model-path Deep-Citation/Workspace/acl_scicite_wksp_trl/best_model.pt --model-data-dir Deep-Citation/Data --model-class-def Deep-Citation/Data/class_def.json --model-lm scibert --device cpu
|
| 2 |
+
|
| 3 |
+
[INFO] Found 1 paper dirs under /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 4 |
+
Number of data instance: 1941
|
| 5 |
+
[LABELED] 2208.00329
|
| 6 |
+
[SUMMARY] labeled=1, skipped=0, missing_usage=0, empty_contexts=0
|
replay_traces/2208.00329/logs/step_05.log
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_05_verify_uses_extends/verify_uses_extends.py --root /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers --k 0 --batch-size 25
|
| 2 |
+
|
| 3 |
+
/Users/dingzifeng/SciFy-UMBC/demo_2026_07/src/common/llm_client.py:4: FutureWarning:
|
| 4 |
+
|
| 5 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 6 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 7 |
+
See README for more details:
|
| 8 |
+
|
| 9 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 10 |
+
|
| 11 |
+
import google.generativeai as genai
|
| 12 |
+
[INFO] Found 1 paper dirs under /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 13 |
+
[VERIFIED] 2208.00329
|
| 14 |
+
[SUMMARY] verified=1, skipped=0, missing_labels=0, no_candidates=0
|
replay_traces/2208.00329/logs/step_06.log
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_06_extract_paragraphs/extract_arxiv_paragraphs.py --root /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 2 |
+
|
| 3 |
+
/Users/dingzifeng/SciFy-UMBC/demo_2026_07/src/step_06_extract_paragraphs/extract_arxiv_paragraphs.py:47: DeprecationWarning: Python 3.14 will, by default, filter extracted tar archives and reject files or modify their metadata. Use the filter argument to control this behavior.
|
| 4 |
+
tar.extractall(path)
|
| 5 |
+
[INFO] Found 1 paper dirs under /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 6 |
+
[PROCESSED] 2208.00329
|
| 7 |
+
[SUMMARY] processed=1, skipped=0, missing_usage=0, missing_verified=0, empty_citing=0
|
replay_traces/2208.00329/logs/step_07.log
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_07_extract_and_refine/extract_contributions_from_citations.py --root /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 2 |
+
|
| 3 |
+
/Users/dingzifeng/SciFy-UMBC/demo_2026_07/src/common/llm_client.py:4: FutureWarning:
|
| 4 |
+
|
| 5 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 6 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 7 |
+
See README for more details:
|
| 8 |
+
|
| 9 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 10 |
+
|
| 11 |
+
import google.generativeai as genai
|
| 12 |
+
[INFO] Found 1 paper dirs under /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 13 |
+
[LABELED] 2208.00329
|
| 14 |
+
[SUMMARY] labeled=1, skipped=0, missing_verified=0, missing_arxiv_paragraphs=0, no_confirmed=0
|
| 15 |
+
$ /Users/dingzifeng/SciFy-UMBC/demo_2026_07/.venv/bin/python src/step_07_extract_and_refine/refine_and_filter_clusters_llm.py --root /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers --inplace --overwrite
|
| 16 |
+
|
| 17 |
+
/Users/dingzifeng/SciFy-UMBC/demo_2026_07/src/common/llm_client.py:4: FutureWarning:
|
| 18 |
+
|
| 19 |
+
All support for the `google.generativeai` package has ended. It will no longer be receiving
|
| 20 |
+
updates or bug fixes. Please switch to the `google.genai` package as soon as possible.
|
| 21 |
+
See README for more details:
|
| 22 |
+
|
| 23 |
+
https://github.com/google-gemini/deprecated-generative-ai-python/blob/main/README.md
|
| 24 |
+
|
| 25 |
+
import google.generativeai as genai
|
| 26 |
+
[INFO] Found 1 paper dirs under /Users/dingzifeng/SciFy-UMBC/demo_2026_07/hf_space/runs/replay_build/job_1785099279_8df5ecbb/processed_papers
|
| 27 |
+
[REFINED] 2208.00329
|
| 28 |
+
[SUMMARY] refined=1, skipped=0, missing_inputs=0, empty_clusters=0
|
replay_traces/2208.00329/processed_papers/2208.00329/paper_metadata.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"paperId": "e894fb15054d3bc9659060406a12dfd1055ae32e",
|
| 4 |
+
"externalIds": {
|
| 5 |
+
"DBLP": "journals/corr/abs-2208-00329",
|
| 6 |
+
"ArXiv": "2208.00329",
|
| 7 |
+
"DOI": "10.1162/tacl_a_00600",
|
| 8 |
+
"CorpusId": 251223896
|
| 9 |
+
},
|
| 10 |
+
"url": "https://www.semanticscholar.org/paper/e894fb15054d3bc9659060406a12dfd1055ae32e",
|
| 11 |
+
"title": "PASTA: A Dataset for Modeling PArticipant STAtes in Narratives",
|
| 12 |
+
"venue": "Transactions of the Association for Computational Linguistics",
|
| 13 |
+
"year": 2022,
|
| 14 |
+
"referenceCount": 59,
|
| 15 |
+
"citationCount": 7,
|
| 16 |
+
"openAccessPdf": {
|
| 17 |
+
"url": "https://direct.mit.edu/tacl/article-pdf/doi/10.1162/tacl_a_00600/2173956/tacl_a_00600.pdf",
|
| 18 |
+
"status": "GOLD",
|
| 19 |
+
"license": "CCBY",
|
| 20 |
+
"disclaimer": "Notice: Paper or abstract available at https://arxiv.org/abs/2208.00329, which is subject to the license by the author or copyright owner provided with this content. Please go to the source to verify the license and copyright information for your use."
|
| 21 |
+
},
|
| 22 |
+
"tldr": {
|
| 23 |
+
"model": "tldr@v2.0.0",
|
| 24 |
+
"text": "This work introduces a new crowdsourced English-language, Participant States dataset, PASTA, and introduces three state-based reasoning tasks that test for the ability to infer when a state is entailed by a story, to revise a story conditioned on a counterfactual state, and to explain the most likely state change given a revised story."
|
| 25 |
+
},
|
| 26 |
+
"publicationDate": "2022-07-31",
|
| 27 |
+
"authors": [
|
| 28 |
+
{
|
| 29 |
+
"authorId": "72059959",
|
| 30 |
+
"name": "Sayontan Ghosh"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"authorId": "144007901",
|
| 34 |
+
"name": "Mahnaz Koupaee"
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"authorId": "2254303864",
|
| 38 |
+
"name": "I-Sen Chen"
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"authorId": "2034063",
|
| 42 |
+
"name": "Francis Ferraro"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"authorId": "1729918",
|
| 46 |
+
"name": "Nathanael Chambers"
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"authorId": "35217367",
|
| 50 |
+
"name": "Niranjan Balasubramanian"
|
| 51 |
+
}
|
| 52 |
+
],
|
| 53 |
+
"abstract": "Abstract The events in a narrative are understood as a coherent whole via the underlying states of their participants. Often, these participant states are not explicitly mentioned, instead left to be inferred by the reader. A model that understands narratives should likewise infer these implicit states, and even reason about the impact of changes to these states on the narrative. To facilitate this goal, we introduce a new crowdsourced English-language, Participant States dataset, PASTA. This dataset contains inferable participant states; a counterfactual perturbation to each state; and the changes to the story that would be necessary if the counterfactual were true. We introduce three state-based reasoning tasks that test for the ability to infer when a state is entailed by a story, to revise a story conditioned on a counterfactual state, and to explain the most likely state change given a revised story. Experiments show that today\u2019s LLMs can reason about states to some degree, but there is large room for improvement, especially in problems requiring access and ability to reason with diverse types of knowledge (e.g., physical, numerical, factual).1"
|
| 54 |
+
}
|
| 55 |
+
]
|
replay_traces/2208.00329/processed_papers/2208.00329/usage_citing_paragraphs.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
replay_traces/2208.00329/processed_papers/2208.00329/usage_context_labels.json
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"paper_id": "2208.00329",
|
| 3 |
+
"num_contexts": 20,
|
| 4 |
+
"label_set": [
|
| 5 |
+
"Background",
|
| 6 |
+
"Uses",
|
| 7 |
+
"Extends",
|
| 8 |
+
"CompareOrContrast",
|
| 9 |
+
"Motivation",
|
| 10 |
+
"Future"
|
| 11 |
+
],
|
| 12 |
+
"labels": [
|
| 13 |
+
{
|
| 14 |
+
"id": 1,
|
| 15 |
+
"label": "Uses",
|
| 16 |
+
"confidence": 0.9254917502403259,
|
| 17 |
+
"confidence_margin": 0.8626327514648438,
|
| 18 |
+
"cue_span": "",
|
| 19 |
+
"rationale": "scibert_model",
|
| 20 |
+
"citing_paper_id": "c1d02fc3786fb5e5b0543703af95046253109d4e",
|
| 21 |
+
"citing_title": "Inductive Bias Extraction and Matching for LLM Prompts",
|
| 22 |
+
"text": "The PASTA dataset is an extension of ROCStories that substitutes alternative events into the stories, which may or may not match up with the original summary sentence <CITED HERE>."
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"id": 2,
|
| 26 |
+
"label": "Uses",
|
| 27 |
+
"confidence": 0.5409758687019348,
|
| 28 |
+
"confidence_margin": 0.14535099267959595,
|
| 29 |
+
"cue_span": "",
|
| 30 |
+
"rationale": "scibert_model",
|
| 31 |
+
"citing_paper_id": "a6924f6b715327b0f24d044e9e8ef799b3f4a0a2",
|
| 32 |
+
"citing_title": "Electrifying the Story Climax via Axial Self-Attention in GPT-3 Transformer Block",
|
| 33 |
+
"text": "The dataset is often separated into four subsets: training, testing, evaluation, and validation [32\u201339]."
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"id": 3,
|
| 37 |
+
"label": "Background",
|
| 38 |
+
"confidence": 0.8934071660041809,
|
| 39 |
+
"confidence_margin": 0.7892690300941467,
|
| 40 |
+
"cue_span": "",
|
| 41 |
+
"rationale": "scibert_model",
|
| 42 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 43 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 44 |
+
"text": "PASTA <CITED HERE> examined implied states and preturbed these states to examine changes in situational narratives."
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"id": 4,
|
| 48 |
+
"label": "Uses",
|
| 49 |
+
"confidence": 0.9904201626777649,
|
| 50 |
+
"confidence_margin": 0.9823774695396423,
|
| 51 |
+
"cue_span": "",
|
| 52 |
+
"rationale": "scibert_model",
|
| 53 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 54 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 55 |
+
"text": "In the following paragraphs, we briefly describe the PASTA <CITED HERE> and SAGA (Vallu-rupalli et al., 2024) datasets and how these datasets\u2019 annotations relate to conditions and outcomes."
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"id": 5,
|
| 59 |
+
"label": "Background",
|
| 60 |
+
"confidence": 0.9986065030097961,
|
| 61 |
+
"confidence_margin": 0.9977977275848389,
|
| 62 |
+
"cue_span": "",
|
| 63 |
+
"rationale": "scibert_model",
|
| 64 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 65 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 66 |
+
"text": "\u2026of reasoning is challenging because: (1) Conditions that to relate to entity properties and states are not always explicitly stated in a narrative but are implicitly understood through forming a coherent mental representation <CITED HERE> and acquiring this implicit knowledge is not easy."
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"id": 6,
|
| 70 |
+
"label": "Background",
|
| 71 |
+
"confidence": 0.9968738555908203,
|
| 72 |
+
"confidence_margin": 0.9947959780693054,
|
| 73 |
+
"cue_span": "",
|
| 74 |
+
"rationale": "scibert_model",
|
| 75 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 76 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 77 |
+
"text": "\u2026of these conditions, like Sam trusting his/her dad or Sam being a small child, are relevant to the goal, with a high likelihood of influencing the outcome, while other conditions that <CITED HERE>; Vallurupalli et al., 2024). might be true are irrelevant to the outcome\u2014like Sam being male."
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"id": 7,
|
| 81 |
+
"label": "Background",
|
| 82 |
+
"confidence": 0.9994476437568665,
|
| 83 |
+
"confidence_margin": 0.9992915987968445,
|
| 84 |
+
"cue_span": "",
|
| 85 |
+
"rationale": "scibert_model",
|
| 86 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 87 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 88 |
+
"text": "( 4) Large Language Models (LLMs) (Brown et al., 2020; Ouyang et al., 2022, inter alia ), while powerful, do not necessarily perform well on tasks requiring counterfactual reasoning (Fang et al., 2025; Lin, 2004; <CITED HERE>; Qin et al., 2019)."
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"id": 8,
|
| 92 |
+
"label": "Uses",
|
| 93 |
+
"confidence": 0.9994866847991943,
|
| 94 |
+
"confidence_margin": 0.9991527199745178,
|
| 95 |
+
"cue_span": "",
|
| 96 |
+
"rationale": "scibert_model",
|
| 97 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 98 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 99 |
+
"text": "We expand the 3 categories used in PASTA <CITED HERE>, for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026"
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"id": 9,
|
| 103 |
+
"label": "Uses",
|
| 104 |
+
"confidence": 0.9968955516815186,
|
| 105 |
+
"confidence_margin": 0.9944096207618713,
|
| 106 |
+
"cue_span": "",
|
| 107 |
+
"rationale": "scibert_model",
|
| 108 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 109 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 110 |
+
"text": "We leverage two previously released datasets PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig."
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"id": 10,
|
| 114 |
+
"label": "Uses",
|
| 115 |
+
"confidence": 0.9981223940849304,
|
| 116 |
+
"confidence_margin": 0.997296929359436,
|
| 117 |
+
"cue_span": "",
|
| 118 |
+
"rationale": "scibert_model",
|
| 119 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 120 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 121 |
+
"text": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026"
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"id": 11,
|
| 125 |
+
"label": "Uses",
|
| 126 |
+
"confidence": 0.915876567363739,
|
| 127 |
+
"confidence_margin": 0.8748534321784973,
|
| 128 |
+
"cue_span": "",
|
| 129 |
+
"rationale": "scibert_model",
|
| 130 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 131 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 132 |
+
"text": "With this HIT we obtain a new set of goal annotations reusing and modifying the free-form text annotations from the actual story to obtain annotations that are also minimally updated reflecting the process used for obtaining the counterfactual in PASTA <CITED HERE>."
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"id": 12,
|
| 136 |
+
"label": "Background",
|
| 137 |
+
"confidence": 0.9987213015556335,
|
| 138 |
+
"confidence_margin": 0.9978130459785461,
|
| 139 |
+
"cue_span": "",
|
| 140 |
+
"rationale": "scibert_model",
|
| 141 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 142 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 143 |
+
"text": "In PASTA <CITED HERE>, original ROCStories (\u201cactual stories\u201d) have up to three \u201calternative\u201d stories."
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"id": 13,
|
| 147 |
+
"label": "Uses",
|
| 148 |
+
"confidence": 0.8018349409103394,
|
| 149 |
+
"confidence_margin": 0.689209520816803,
|
| 150 |
+
"cue_span": "",
|
| 151 |
+
"rationale": "scibert_model",
|
| 152 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 153 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 154 |
+
"text": "\u2026stories shown: in alternative 1 (top right), a different Figure 1: A participant\u2019s goal inferred from the actual story when applied to 3 alternative stories, drawn from the PASTA dataset <CITED HERE>; slightly varying actions in the stories lead to different goal achievement outcomes."
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"id": 14,
|
| 158 |
+
"label": "Background",
|
| 159 |
+
"confidence": 0.9973942041397095,
|
| 160 |
+
"confidence_margin": 0.996073305606842,
|
| 161 |
+
"cue_span": "",
|
| 162 |
+
"rationale": "scibert_model",
|
| 163 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 164 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 165 |
+
"text": "PASTA <CITED HERE> introduced implied states that stories depend upon and examined the tasks of state inference from alternative stories and story rewriting for alternative states."
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"id": 15,
|
| 169 |
+
"label": "Background",
|
| 170 |
+
"confidence": 0.9965572357177734,
|
| 171 |
+
"confidence_margin": 0.9946063160896301,
|
| 172 |
+
"cue_span": "",
|
| 173 |
+
"rationale": "scibert_model",
|
| 174 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 175 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 176 |
+
"text": "Third, while pre-trained large language models (LLMs) are powerful (Brown et al., 2020; Wei et al., 2022, inter alia ), they may perform poorly on tasks requiring robust reasoning <CITED HERE>; Zellers et al., 2019; Qin et al., 2019)."
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"id": 16,
|
| 180 |
+
"label": "Uses",
|
| 181 |
+
"confidence": 0.9981212019920349,
|
| 182 |
+
"confidence_margin": 0.996633768081665,
|
| 183 |
+
"cue_span": "",
|
| 184 |
+
"rationale": "scibert_model",
|
| 185 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 186 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 187 |
+
"text": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset <CITED HERE>."
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"id": 17,
|
| 191 |
+
"label": "Background",
|
| 192 |
+
"confidence": 0.9918141961097717,
|
| 193 |
+
"confidence_margin": 0.9873102307319641,
|
| 194 |
+
"cue_span": "",
|
| 195 |
+
"rationale": "scibert_model",
|
| 196 |
+
"citing_paper_id": "d723193c0453223803ffb926354cd6d2dee32b06",
|
| 197 |
+
"citing_title": "EvEval: A Comprehensive Evaluation of Event Semantics for Large Language Models",
|
| 198 |
+
"text": "Learning other relations such as event temporality needs more context information and knowledge since it is more subjected to specific situations <CITED HERE>."
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
"id": 18,
|
| 202 |
+
"label": "Background",
|
| 203 |
+
"confidence": 0.9065139293670654,
|
| 204 |
+
"confidence_margin": 0.8178352117538452,
|
| 205 |
+
"cue_span": "",
|
| 206 |
+
"rationale": "scibert_model",
|
| 207 |
+
"citing_paper_id": "293583961efdf22c0905cc04465aa88ea96826ec",
|
| 208 |
+
"citing_title": "Causal schema induction for knowledge discovery",
|
| 209 |
+
"text": ", 2021), of participant states <CITED HERE>; Vallurupalli et al., 2022), and of disease where organisms are conceived of as causative agents, e."
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"id": 19,
|
| 213 |
+
"label": "Background",
|
| 214 |
+
"confidence": 0.9994509816169739,
|
| 215 |
+
"confidence_margin": 0.9992090463638306,
|
| 216 |
+
"cue_span": "",
|
| 217 |
+
"rationale": "scibert_model",
|
| 218 |
+
"citing_paper_id": "293583961efdf22c0905cc04465aa88ea96826ec",
|
| 219 |
+
"citing_title": "Causal schema induction for knowledge discovery",
|
| 220 |
+
"text": "Participant-centeredness is featured in studies of narrative (Propp, 1968; Caselli and Vossen, 2016; Brahman et al., 2021), of participant states <CITED HERE>; Vallurupalli et al., 2022), and of disease where organisms are conceived of as causative agents, e.g., the pathogen tubercle\u2026"
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"id": 20,
|
| 224 |
+
"label": "Background",
|
| 225 |
+
"confidence": 0.9978366494178772,
|
| 226 |
+
"confidence_margin": 0.9965800642967224,
|
| 227 |
+
"cue_span": "",
|
| 228 |
+
"rationale": "scibert_model",
|
| 229 |
+
"citing_paper_id": "2b914691a44ab1c8bcb0104b8079d86908d98729",
|
| 230 |
+
"citing_title": "Who Plays Which Role? Protagonist Detection and Classification in Moral Discourse",
|
| 231 |
+
"text": "Beyond moral discourse, modeling actors and their roles has a long tradition in discourse analysis, for instance, through semantic role labeling (Ruppenhofer et al., 2009; Roth and Lap-ata, 2015; Bornheim et al., 2024) and participant modeling (Tilk et al., 2016; <CITED HERE>."
|
| 232 |
+
}
|
| 233 |
+
],
|
| 234 |
+
"citing_paper_labels": [
|
| 235 |
+
{
|
| 236 |
+
"citing_paper_id": "c1d02fc3786fb5e5b0543703af95046253109d4e",
|
| 237 |
+
"citing_title": "Inductive Bias Extraction and Matching for LLM Prompts",
|
| 238 |
+
"label": "Uses",
|
| 239 |
+
"evidence_context_ids": [
|
| 240 |
+
1
|
| 241 |
+
]
|
| 242 |
+
},
|
| 243 |
+
{
|
| 244 |
+
"citing_paper_id": "a6924f6b715327b0f24d044e9e8ef799b3f4a0a2",
|
| 245 |
+
"citing_title": "Electrifying the Story Climax via Axial Self-Attention in GPT-3 Transformer Block",
|
| 246 |
+
"label": "Uses",
|
| 247 |
+
"evidence_context_ids": [
|
| 248 |
+
2
|
| 249 |
+
]
|
| 250 |
+
},
|
| 251 |
+
{
|
| 252 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 253 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 254 |
+
"label": "Uses",
|
| 255 |
+
"evidence_context_ids": [
|
| 256 |
+
4,
|
| 257 |
+
8,
|
| 258 |
+
9,
|
| 259 |
+
10
|
| 260 |
+
]
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 264 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 265 |
+
"label": "Uses",
|
| 266 |
+
"evidence_context_ids": [
|
| 267 |
+
11,
|
| 268 |
+
13,
|
| 269 |
+
16
|
| 270 |
+
]
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"citing_paper_id": "d723193c0453223803ffb926354cd6d2dee32b06",
|
| 274 |
+
"citing_title": "EvEval: A Comprehensive Evaluation of Event Semantics for Large Language Models",
|
| 275 |
+
"label": "Background",
|
| 276 |
+
"evidence_context_ids": []
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"citing_paper_id": "293583961efdf22c0905cc04465aa88ea96826ec",
|
| 280 |
+
"citing_title": "Causal schema induction for knowledge discovery",
|
| 281 |
+
"label": "Background",
|
| 282 |
+
"evidence_context_ids": []
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
"citing_paper_id": "2b914691a44ab1c8bcb0104b8079d86908d98729",
|
| 286 |
+
"citing_title": "Who Plays Which Role? Protagonist Detection and Classification in Moral Discourse",
|
| 287 |
+
"label": "Background",
|
| 288 |
+
"evidence_context_ids": []
|
| 289 |
+
}
|
| 290 |
+
],
|
| 291 |
+
"final_label": "Uses"
|
| 292 |
+
}
|
replay_traces/2208.00329/processed_papers/2208.00329/usage_contexts.json
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"paper_id": "2208.00329",
|
| 3 |
+
"total_citations": 7,
|
| 4 |
+
"num_contexts": 20,
|
| 5 |
+
"num_citing_with_context": 7,
|
| 6 |
+
"num_citing_without_context": 0,
|
| 7 |
+
"num_influential_citations": 2,
|
| 8 |
+
"num_influential_with_context": 2,
|
| 9 |
+
"influential_contexts": [
|
| 10 |
+
{
|
| 11 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 12 |
+
"title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 13 |
+
"external_ids": {
|
| 14 |
+
"DBLP": "journals/corr/abs-2506-01253",
|
| 15 |
+
"ArXiv": "2506.01253",
|
| 16 |
+
"DOI": "10.48550/arXiv.2506.01253",
|
| 17 |
+
"CorpusId": 279074979
|
| 18 |
+
},
|
| 19 |
+
"contexts": [
|
| 20 |
+
{
|
| 21 |
+
"text": "PASTA <CITED HERE> examined implied states and preturbed these states to examine changes in situational narratives.",
|
| 22 |
+
"text_raw": "PASTA (Ghosh et al., 2023) examined implied states and preturbed these states to examine changes in situational narratives.",
|
| 23 |
+
"intents": []
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"text": "In the following paragraphs, we briefly describe the PASTA <CITED HERE> and SAGA (Vallu-rupalli et al., 2024) datasets and how these datasets\u2019 annotations relate to conditions and outcomes.",
|
| 27 |
+
"text_raw": "In the following paragraphs, we briefly describe the PASTA (Ghosh et al., 2023) and SAGA (Vallu-rupalli et al., 2024) datasets and how these datasets\u2019 annotations relate to conditions and outcomes.",
|
| 28 |
+
"intents": []
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"text": "\u2026of reasoning is challenging because: (1) Conditions that to relate to entity properties and states are not always explicitly stated in a narrative but are implicitly understood through forming a coherent mental representation <CITED HERE> and acquiring this implicit knowledge is not easy.",
|
| 32 |
+
"text_raw": "\u2026of reasoning is challenging because: (1) Conditions that to relate to entity properties and states are not always explicitly stated in a narrative but are implicitly understood through forming a coherent mental representation (Ghosh et al., 2023) and acquiring this implicit knowledge is not easy.",
|
| 33 |
+
"intents": []
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"text": "\u2026of these conditions, like Sam trusting his/her dad or Sam being a small child, are relevant to the goal, with a high likelihood of influencing the outcome, while other conditions that <CITED HERE>; Vallurupalli et al., 2024). might be true are irrelevant to the outcome\u2014like Sam being male.",
|
| 37 |
+
"text_raw": "\u2026of these conditions, like Sam trusting his/her dad or Sam being a small child, are relevant to the goal, with a high likelihood of influencing the outcome, while other conditions that (Ghosh et al., 2023; Vallurupalli et al., 2024). might be true are irrelevant to the outcome\u2014like Sam being male.",
|
| 38 |
+
"intents": []
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"text": "( 4) Large Language Models (LLMs) (Brown et al., 2020; Ouyang et al., 2022, inter alia ), while powerful, do not necessarily perform well on tasks requiring counterfactual reasoning (Fang et al., 2025; Lin, 2004; <CITED HERE>; Qin et al., 2019).",
|
| 42 |
+
"text_raw": "( 4) Large Language Models (LLMs) (Brown et al., 2020; Ouyang et al., 2022, inter alia ), while powerful, do not necessarily perform well on tasks requiring counterfactual reasoning (Fang et al., 2025; Lin, 2004; Ghosh et al., 2023; Qin et al., 2019).",
|
| 43 |
+
"intents": []
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"text": "We expand the 3 categories used in PASTA <CITED HERE>, for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026",
|
| 47 |
+
"text_raw": "We expand the 3 categories used in PASTA (Ghosh et al., 2023), for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026",
|
| 48 |
+
"intents": []
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"text": "We leverage two previously released datasets PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig.",
|
| 52 |
+
"text_raw": "We leverage two previously released datasets PASTA (Ghosh et al., 2023) and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig.",
|
| 53 |
+
"intents": []
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"text": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026",
|
| 57 |
+
"text_raw": "We leverage PASTA (Ghosh et al., 2023) and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026",
|
| 58 |
+
"intents": []
|
| 59 |
+
}
|
| 60 |
+
]
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 64 |
+
"title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 65 |
+
"external_ids": {
|
| 66 |
+
"DBLP": "journals/corr/abs-2408-05793",
|
| 67 |
+
"ArXiv": "2408.05793",
|
| 68 |
+
"DOI": "10.48550/arXiv.2408.05793",
|
| 69 |
+
"CorpusId": 271855571
|
| 70 |
+
},
|
| 71 |
+
"contexts": [
|
| 72 |
+
{
|
| 73 |
+
"text": "With this HIT we obtain a new set of goal annotations reusing and modifying the free-form text annotations from the actual story to obtain annotations that are also minimally updated reflecting the process used for obtaining the counterfactual in PASTA <CITED HERE>.",
|
| 74 |
+
"text_raw": "With this HIT we obtain a new set of goal annotations reusing and modifying the free-form text annotations from the actual story to obtain annotations that are also minimally updated reflecting the process used for obtaining the counterfactual in PASTA (Ghosh et al., 2023).",
|
| 75 |
+
"intents": [
|
| 76 |
+
"methodology"
|
| 77 |
+
]
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"text": "In PASTA <CITED HERE>, original ROCStories (\u201cactual stories\u201d) have up to three \u201calternative\u201d stories.",
|
| 81 |
+
"text_raw": "In PASTA (Ghosh et al., 2023), original ROCStories (\u201cactual stories\u201d) have up to three \u201calternative\u201d stories.",
|
| 82 |
+
"intents": [
|
| 83 |
+
"background"
|
| 84 |
+
]
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"text": "\u2026stories shown: in alternative 1 (top right), a different Figure 1: A participant\u2019s goal inferred from the actual story when applied to 3 alternative stories, drawn from the PASTA dataset <CITED HERE>; slightly varying actions in the stories lead to different goal achievement outcomes.",
|
| 88 |
+
"text_raw": "\u2026stories shown: in alternative 1 (top right), a different Figure 1: A participant\u2019s goal inferred from the actual story when applied to 3 alternative stories, drawn from the PASTA dataset (Ghosh et al., 2023); slightly varying actions in the stories lead to different goal achievement outcomes.",
|
| 89 |
+
"intents": [
|
| 90 |
+
"background"
|
| 91 |
+
]
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"text": "PASTA <CITED HERE> introduced implied states that stories depend upon and examined the tasks of state inference from alternative stories and story rewriting for alternative states.",
|
| 95 |
+
"text_raw": "PASTA (Ghosh et al., 2023) introduced implied states that stories depend upon and examined the tasks of state inference from alternative stories and story rewriting for alternative states.",
|
| 96 |
+
"intents": [
|
| 97 |
+
"background"
|
| 98 |
+
]
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"text": "Third, while pre-trained large language models (LLMs) are powerful (Brown et al., 2020; Wei et al., 2022, inter alia ), they may perform poorly on tasks requiring robust reasoning <CITED HERE>; Zellers et al., 2019; Qin et al., 2019).",
|
| 102 |
+
"text_raw": "Third, while pre-trained large language models (LLMs) are powerful (Brown et al., 2020; Wei et al., 2022, inter alia ), they may perform poorly on tasks requiring robust reasoning (Ghosh et al., 2023; Zellers et al., 2019; Qin et al., 2019).",
|
| 103 |
+
"intents": [
|
| 104 |
+
"background"
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"text": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset <CITED HERE>.",
|
| 109 |
+
"text_raw": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset (Ghosh et al., 2023).",
|
| 110 |
+
"intents": [
|
| 111 |
+
"methodology"
|
| 112 |
+
]
|
| 113 |
+
}
|
| 114 |
+
]
|
| 115 |
+
}
|
| 116 |
+
],
|
| 117 |
+
"citing_papers": [
|
| 118 |
+
{
|
| 119 |
+
"citing_paper_id": "c1d02fc3786fb5e5b0543703af95046253109d4e",
|
| 120 |
+
"title": "Inductive Bias Extraction and Matching for LLM Prompts",
|
| 121 |
+
"external_ids": {
|
| 122 |
+
"ArXiv": "2508.10295",
|
| 123 |
+
"DBLP": "journals/corr/abs-2508-10295",
|
| 124 |
+
"DOI": "10.48550/arXiv.2508.10295",
|
| 125 |
+
"CorpusId": 280649627
|
| 126 |
+
},
|
| 127 |
+
"is_influential": false,
|
| 128 |
+
"contexts": [
|
| 129 |
+
{
|
| 130 |
+
"text": "The PASTA dataset is an extension of ROCStories that substitutes alternative events into the stories, which may or may not match up with the original summary sentence <CITED HERE>.",
|
| 131 |
+
"text_raw": "The PASTA dataset is an extension of ROCStories that substitutes alternative events into the stories, which may or may not match up with the original summary sentence (Ghosh et al., 2023).",
|
| 132 |
+
"intents": []
|
| 133 |
+
}
|
| 134 |
+
]
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
"citing_paper_id": "a6924f6b715327b0f24d044e9e8ef799b3f4a0a2",
|
| 138 |
+
"title": "Electrifying the Story Climax via Axial Self-Attention in GPT-3 Transformer Block",
|
| 139 |
+
"external_ids": {
|
| 140 |
+
"DBLP": "journals/sncs/VD25",
|
| 141 |
+
"DOI": "10.1007/s42979-025-04091-1",
|
| 142 |
+
"CorpusId": 279483975
|
| 143 |
+
},
|
| 144 |
+
"is_influential": false,
|
| 145 |
+
"contexts": [
|
| 146 |
+
{
|
| 147 |
+
"text": "The dataset is often separated into four subsets: training, testing, evaluation, and validation [32\u201339].",
|
| 148 |
+
"text_raw": "The dataset is often separated into four subsets: training, testing, evaluation, and validation [32\u201339].",
|
| 149 |
+
"intents": []
|
| 150 |
+
}
|
| 151 |
+
]
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 155 |
+
"title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 156 |
+
"external_ids": {
|
| 157 |
+
"DBLP": "journals/corr/abs-2506-01253",
|
| 158 |
+
"ArXiv": "2506.01253",
|
| 159 |
+
"DOI": "10.48550/arXiv.2506.01253",
|
| 160 |
+
"CorpusId": 279074979
|
| 161 |
+
},
|
| 162 |
+
"is_influential": true,
|
| 163 |
+
"contexts": [
|
| 164 |
+
{
|
| 165 |
+
"text": "PASTA <CITED HERE> examined implied states and preturbed these states to examine changes in situational narratives.",
|
| 166 |
+
"text_raw": "PASTA (Ghosh et al., 2023) examined implied states and preturbed these states to examine changes in situational narratives.",
|
| 167 |
+
"intents": []
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"text": "In the following paragraphs, we briefly describe the PASTA <CITED HERE> and SAGA (Vallu-rupalli et al., 2024) datasets and how these datasets\u2019 annotations relate to conditions and outcomes.",
|
| 171 |
+
"text_raw": "In the following paragraphs, we briefly describe the PASTA (Ghosh et al., 2023) and SAGA (Vallu-rupalli et al., 2024) datasets and how these datasets\u2019 annotations relate to conditions and outcomes.",
|
| 172 |
+
"intents": []
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"text": "\u2026of reasoning is challenging because: (1) Conditions that to relate to entity properties and states are not always explicitly stated in a narrative but are implicitly understood through forming a coherent mental representation <CITED HERE> and acquiring this implicit knowledge is not easy.",
|
| 176 |
+
"text_raw": "\u2026of reasoning is challenging because: (1) Conditions that to relate to entity properties and states are not always explicitly stated in a narrative but are implicitly understood through forming a coherent mental representation (Ghosh et al., 2023) and acquiring this implicit knowledge is not easy.",
|
| 177 |
+
"intents": []
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"text": "\u2026of these conditions, like Sam trusting his/her dad or Sam being a small child, are relevant to the goal, with a high likelihood of influencing the outcome, while other conditions that <CITED HERE>; Vallurupalli et al., 2024). might be true are irrelevant to the outcome\u2014like Sam being male.",
|
| 181 |
+
"text_raw": "\u2026of these conditions, like Sam trusting his/her dad or Sam being a small child, are relevant to the goal, with a high likelihood of influencing the outcome, while other conditions that (Ghosh et al., 2023; Vallurupalli et al., 2024). might be true are irrelevant to the outcome\u2014like Sam being male.",
|
| 182 |
+
"intents": []
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"text": "( 4) Large Language Models (LLMs) (Brown et al., 2020; Ouyang et al., 2022, inter alia ), while powerful, do not necessarily perform well on tasks requiring counterfactual reasoning (Fang et al., 2025; Lin, 2004; <CITED HERE>; Qin et al., 2019).",
|
| 186 |
+
"text_raw": "( 4) Large Language Models (LLMs) (Brown et al., 2020; Ouyang et al., 2022, inter alia ), while powerful, do not necessarily perform well on tasks requiring counterfactual reasoning (Fang et al., 2025; Lin, 2004; Ghosh et al., 2023; Qin et al., 2019).",
|
| 187 |
+
"intents": []
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"text": "We expand the 3 categories used in PASTA <CITED HERE>, for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026",
|
| 191 |
+
"text_raw": "We expand the 3 categories used in PASTA (Ghosh et al., 2023), for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026",
|
| 192 |
+
"intents": []
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"text": "We leverage two previously released datasets PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig.",
|
| 196 |
+
"text_raw": "We leverage two previously released datasets PASTA (Ghosh et al., 2023) and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig.",
|
| 197 |
+
"intents": []
|
| 198 |
+
},
|
| 199 |
+
{
|
| 200 |
+
"text": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026",
|
| 201 |
+
"text_raw": "We leverage PASTA (Ghosh et al., 2023) and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026",
|
| 202 |
+
"intents": []
|
| 203 |
+
}
|
| 204 |
+
]
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 208 |
+
"title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 209 |
+
"external_ids": {
|
| 210 |
+
"DBLP": "journals/corr/abs-2408-05793",
|
| 211 |
+
"ArXiv": "2408.05793",
|
| 212 |
+
"DOI": "10.48550/arXiv.2408.05793",
|
| 213 |
+
"CorpusId": 271855571
|
| 214 |
+
},
|
| 215 |
+
"is_influential": true,
|
| 216 |
+
"contexts": [
|
| 217 |
+
{
|
| 218 |
+
"text": "With this HIT we obtain a new set of goal annotations reusing and modifying the free-form text annotations from the actual story to obtain annotations that are also minimally updated reflecting the process used for obtaining the counterfactual in PASTA <CITED HERE>.",
|
| 219 |
+
"text_raw": "With this HIT we obtain a new set of goal annotations reusing and modifying the free-form text annotations from the actual story to obtain annotations that are also minimally updated reflecting the process used for obtaining the counterfactual in PASTA (Ghosh et al., 2023).",
|
| 220 |
+
"intents": [
|
| 221 |
+
"methodology"
|
| 222 |
+
]
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"text": "In PASTA <CITED HERE>, original ROCStories (\u201cactual stories\u201d) have up to three \u201calternative\u201d stories.",
|
| 226 |
+
"text_raw": "In PASTA (Ghosh et al., 2023), original ROCStories (\u201cactual stories\u201d) have up to three \u201calternative\u201d stories.",
|
| 227 |
+
"intents": [
|
| 228 |
+
"background"
|
| 229 |
+
]
|
| 230 |
+
},
|
| 231 |
+
{
|
| 232 |
+
"text": "\u2026stories shown: in alternative 1 (top right), a different Figure 1: A participant\u2019s goal inferred from the actual story when applied to 3 alternative stories, drawn from the PASTA dataset <CITED HERE>; slightly varying actions in the stories lead to different goal achievement outcomes.",
|
| 233 |
+
"text_raw": "\u2026stories shown: in alternative 1 (top right), a different Figure 1: A participant\u2019s goal inferred from the actual story when applied to 3 alternative stories, drawn from the PASTA dataset (Ghosh et al., 2023); slightly varying actions in the stories lead to different goal achievement outcomes.",
|
| 234 |
+
"intents": [
|
| 235 |
+
"background"
|
| 236 |
+
]
|
| 237 |
+
},
|
| 238 |
+
{
|
| 239 |
+
"text": "PASTA <CITED HERE> introduced implied states that stories depend upon and examined the tasks of state inference from alternative stories and story rewriting for alternative states.",
|
| 240 |
+
"text_raw": "PASTA (Ghosh et al., 2023) introduced implied states that stories depend upon and examined the tasks of state inference from alternative stories and story rewriting for alternative states.",
|
| 241 |
+
"intents": [
|
| 242 |
+
"background"
|
| 243 |
+
]
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"text": "Third, while pre-trained large language models (LLMs) are powerful (Brown et al., 2020; Wei et al., 2022, inter alia ), they may perform poorly on tasks requiring robust reasoning <CITED HERE>; Zellers et al., 2019; Qin et al., 2019).",
|
| 247 |
+
"text_raw": "Third, while pre-trained large language models (LLMs) are powerful (Brown et al., 2020; Wei et al., 2022, inter alia ), they may perform poorly on tasks requiring robust reasoning (Ghosh et al., 2023; Zellers et al., 2019; Qin et al., 2019).",
|
| 248 |
+
"intents": [
|
| 249 |
+
"background"
|
| 250 |
+
]
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"text": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset <CITED HERE>.",
|
| 254 |
+
"text_raw": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset (Ghosh et al., 2023).",
|
| 255 |
+
"intents": [
|
| 256 |
+
"methodology"
|
| 257 |
+
]
|
| 258 |
+
}
|
| 259 |
+
]
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"citing_paper_id": "d723193c0453223803ffb926354cd6d2dee32b06",
|
| 263 |
+
"title": "EvEval: A Comprehensive Evaluation of Event Semantics for Large Language Models",
|
| 264 |
+
"external_ids": {
|
| 265 |
+
"ArXiv": "2305.15268",
|
| 266 |
+
"DBLP": "journals/corr/abs-2305-15268",
|
| 267 |
+
"DOI": "10.48550/arXiv.2305.15268",
|
| 268 |
+
"CorpusId": 258866165
|
| 269 |
+
},
|
| 270 |
+
"is_influential": false,
|
| 271 |
+
"contexts": [
|
| 272 |
+
{
|
| 273 |
+
"text": "Learning other relations such as event temporality needs more context information and knowledge since it is more subjected to specific situations <CITED HERE>.",
|
| 274 |
+
"text_raw": "Learning other relations such as event temporality needs more context information and knowledge since it is more subjected to specific situations (Ghosh et al., 2022).",
|
| 275 |
+
"intents": [
|
| 276 |
+
"background"
|
| 277 |
+
]
|
| 278 |
+
}
|
| 279 |
+
]
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"citing_paper_id": "293583961efdf22c0905cc04465aa88ea96826ec",
|
| 283 |
+
"title": "Causal schema induction for knowledge discovery",
|
| 284 |
+
"external_ids": {
|
| 285 |
+
"ArXiv": "2303.15381",
|
| 286 |
+
"DBLP": "journals/corr/abs-2303-15381",
|
| 287 |
+
"DOI": "10.48550/arXiv.2303.15381",
|
| 288 |
+
"CorpusId": 257766554
|
| 289 |
+
},
|
| 290 |
+
"is_influential": false,
|
| 291 |
+
"contexts": [
|
| 292 |
+
{
|
| 293 |
+
"text": ", 2021), of participant states <CITED HERE>; Vallurupalli et al., 2022), and of disease where organisms are conceived of as causative agents, e.",
|
| 294 |
+
"text_raw": ", 2021), of participant states (Ghosh et al., 2022; Vallurupalli et al., 2022), and of disease where organisms are conceived of as causative agents, e.",
|
| 295 |
+
"intents": [
|
| 296 |
+
"background"
|
| 297 |
+
]
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"text": "Participant-centeredness is featured in studies of narrative (Propp, 1968; Caselli and Vossen, 2016; Brahman et al., 2021), of participant states <CITED HERE>; Vallurupalli et al., 2022), and of disease where organisms are conceived of as causative agents, e.g., the pathogen tubercle\u2026",
|
| 301 |
+
"text_raw": "Participant-centeredness is featured in studies of narrative (Propp, 1968; Caselli and Vossen, 2016; Brahman et al., 2021), of participant states (Ghosh et al., 2022; Vallurupalli et al., 2022), and of disease where organisms are conceived of as causative agents, e.g., the pathogen tubercle\u2026",
|
| 302 |
+
"intents": [
|
| 303 |
+
"background"
|
| 304 |
+
]
|
| 305 |
+
}
|
| 306 |
+
]
|
| 307 |
+
},
|
| 308 |
+
{
|
| 309 |
+
"citing_paper_id": "2b914691a44ab1c8bcb0104b8079d86908d98729",
|
| 310 |
+
"title": "Who Plays Which Role? Protagonist Detection and Classification in Moral Discourse",
|
| 311 |
+
"external_ids": {
|
| 312 |
+
"DBLP": "conf/eacl/SommerB26",
|
| 313 |
+
"DOI": "10.18653/v1/2026.eacl-srw.27",
|
| 314 |
+
"CorpusId": 286822758
|
| 315 |
+
},
|
| 316 |
+
"is_influential": false,
|
| 317 |
+
"contexts": [
|
| 318 |
+
{
|
| 319 |
+
"text": "Beyond moral discourse, modeling actors and their roles has a long tradition in discourse analysis, for instance, through semantic role labeling (Ruppenhofer et al., 2009; Roth and Lap-ata, 2015; Bornheim et al., 2024) and participant modeling (Tilk et al., 2016; <CITED HERE>.",
|
| 320 |
+
"text_raw": "Beyond moral discourse, modeling actors and their roles has a long tradition in discourse analysis, for instance, through semantic role labeling (Ruppenhofer et al., 2009; Roth and Lap-ata, 2015; Bornheim et al., 2024) and participant modeling (Tilk et al., 2016; Ghosh et al., 2023).",
|
| 321 |
+
"intents": []
|
| 322 |
+
}
|
| 323 |
+
]
|
| 324 |
+
}
|
| 325 |
+
]
|
| 326 |
+
}
|
replay_traces/2208.00329/processed_papers/2208.00329/usage_contributions.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"paper_id": "2208.00329",
|
| 3 |
+
"final_label": "EXTENDS",
|
| 4 |
+
"contributions": [
|
| 5 |
+
{
|
| 6 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 7 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 8 |
+
"label": "USES",
|
| 9 |
+
"paper_claim": "The citing paper adopts the methodology used for obtaining counterfactuals in the target paper to generate minimally updated goal annotations.",
|
| 10 |
+
"claim": "The citing paper adopts the methodology used for obtaining counterfactuals in the target paper to generate minimally updated goal annotations.",
|
| 11 |
+
"cluster_title": "Uses target methodology for dataset creation",
|
| 12 |
+
"cluster_key": "USES|methodology|dataset_creation",
|
| 13 |
+
"evidence_span": "reflecting the process used for obtaining the counterfactual in ~PASTA~<CITED HERE>",
|
| 14 |
+
"rationale": "The citing paper explicitly states that their dataset annotation process uses or mirrors the method used to obtain counterfactuals in the target paper.",
|
| 15 |
+
"contexts": [
|
| 16 |
+
"Target-citing paragraph: We use a third HIT when one or more annotators identify that a participant's goal is inferrable from $S^{c_k}$. With this HIT we obtain a new set of goal annotations reusing and modifying the free-form text annotations from the actual story to obtain annotations that are also minimally updated reflecting the process used for obtaining the counterfactual in ~PASTA~<CITED HERE>. See \\cref{tab:HITS-annotation} for the annotations and the HITs used for obtaining them.",
|
| 17 |
+
"Target-citing paragraph: \\subsubsection{Worker Selection and Qualifications}\n\\label{app:HIT_information:qualifications}\nFor our initial alpha runs of the HITs we used all workers who meet our community standard quality criteria, such as requiring a 98\\% or greater HIT acceptance rate and the completion of 1000 approved HITs. In addition, we required the worker's stated location to be in the USA, UK, Canada, Australia, or New Zealand. We used the location requirement to avoid language-based artifacts given the language-dependent semantic phenomena and the high subjectivity our work can elicit. \nWe did not use requester-generated qualification tests, though in early iterations we found that annotators who had completed at least 50 HITs in our prior work~<CITED HERE> provided the most reliable annotations; the vast majority of our responses are from this group."
|
| 18 |
+
],
|
| 19 |
+
"source": "arxiv_paragraphs"
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 23 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 24 |
+
"label": "USES",
|
| 25 |
+
"paper_claim": "The authors leverage the PASTA dataset and its annotations to generate and validate story outcomes.",
|
| 26 |
+
"claim": "The authors leverage the PASTA dataset and its annotations to generate and validate story outcomes.",
|
| 27 |
+
"cluster_title": "Uses target dataset for data generation",
|
| 28 |
+
"cluster_key": "USES|dataset|data_generation",
|
| 29 |
+
"evidence_span": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions.",
|
| 30 |
+
"rationale": "The citing paper explicitly uses the PASTA dataset and its annotated conditions to generate outcomes for SAGA and News Stories, as well as to validate them.",
|
| 31 |
+
"contexts": [
|
| 32 |
+
"Target sentence: We expand the 3 categories used in PASTA <CITED HERE>, for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026",
|
| 33 |
+
"Target sentence: We leverage two previously released datasets PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig.",
|
| 34 |
+
"Target sentence: We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026"
|
| 35 |
+
],
|
| 36 |
+
"source": "usage_contexts_fallback"
|
| 37 |
+
}
|
| 38 |
+
],
|
| 39 |
+
"source": "arxiv_paragraphs",
|
| 40 |
+
"fallback_citing_ids": [
|
| 41 |
+
"ab73ca481256767addce56b20eb48ac183169e01"
|
| 42 |
+
]
|
| 43 |
+
}
|
replay_traces/2208.00329/processed_papers/2208.00329/usage_discovery_from_contributions.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"paper_id": "2208.00329",
|
| 3 |
+
"decision": "",
|
| 4 |
+
"justification": "",
|
| 5 |
+
"clusters": [
|
| 6 |
+
{
|
| 7 |
+
"cluster_id": "C1",
|
| 8 |
+
"count": "1",
|
| 9 |
+
"representative_claim": "Uses target methodology for dataset creation",
|
| 10 |
+
"cluster_key": "USES|methodology|dataset_creation",
|
| 11 |
+
"cluster_title": "Uses target methodology for dataset creation",
|
| 12 |
+
"claim_indices": [
|
| 13 |
+
"0"
|
| 14 |
+
],
|
| 15 |
+
"source_cluster_ids": [
|
| 16 |
+
"C1"
|
| 17 |
+
],
|
| 18 |
+
"merge_rationale": "The citing paper adopts the methodology used in the target paper to generate its own dataset."
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"cluster_id": "C2",
|
| 22 |
+
"count": "1",
|
| 23 |
+
"representative_claim": "Uses target dataset for data generation",
|
| 24 |
+
"cluster_key": "USES|dataset|data_generation",
|
| 25 |
+
"cluster_title": "Uses target dataset for data generation",
|
| 26 |
+
"claim_indices": [
|
| 27 |
+
"1"
|
| 28 |
+
],
|
| 29 |
+
"source_cluster_ids": [
|
| 30 |
+
"C2"
|
| 31 |
+
],
|
| 32 |
+
"merge_rationale": "The citing paper uses the PASTA dataset and its annotations directly to generate and validate story outcomes."
|
| 33 |
+
}
|
| 34 |
+
],
|
| 35 |
+
"dropped_clusters": [],
|
| 36 |
+
"cluster_refine_method": "llm_centroid_merge_filter",
|
| 37 |
+
"cluster_refine_source": "usage_contributions.json"
|
| 38 |
+
}
|
replay_traces/2208.00329/processed_papers/2208.00329/usage_uses_extends_verified.json
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"paper_id": "2208.00329",
|
| 3 |
+
"target": {
|
| 4 |
+
"title": "PASTA: A Dataset for Modeling PArticipant STAtes in Narratives",
|
| 5 |
+
"first_author_last": "Ghosh",
|
| 6 |
+
"year": "2022"
|
| 7 |
+
},
|
| 8 |
+
"candidates_total": 9,
|
| 9 |
+
"candidates_considered": 9,
|
| 10 |
+
"verification_batch_size": 25,
|
| 11 |
+
"verification_num_batches": 1,
|
| 12 |
+
"candidates_selected": 4,
|
| 13 |
+
"verified": [
|
| 14 |
+
{
|
| 15 |
+
"id": 8,
|
| 16 |
+
"label": "EXTENDS",
|
| 17 |
+
"cue_span": "We expand the 3 categories used in PASTA",
|
| 18 |
+
"rationale": "The citing paper explicitly expands upon the categorical framework introduced in the target paper.",
|
| 19 |
+
"text": "We expand the 3 categories used in PASTA <CITED HERE>, for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026",
|
| 20 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 21 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 22 |
+
"original_label": "Uses"
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"id": 10,
|
| 26 |
+
"label": "USES",
|
| 27 |
+
"cue_span": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes",
|
| 28 |
+
"rationale": "The authors explicitly state they are leveraging the target dataset to generate and validate outcomes.",
|
| 29 |
+
"text": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026",
|
| 30 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 31 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 32 |
+
"original_label": "Uses"
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"id": 16,
|
| 36 |
+
"label": "USES",
|
| 37 |
+
"cue_span": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset",
|
| 38 |
+
"rationale": "The authors used stories specifically from the PASTA dataset for their own annotation process.",
|
| 39 |
+
"text": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset <CITED HERE>.",
|
| 40 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 41 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 42 |
+
"original_label": "Uses"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"id": 9,
|
| 46 |
+
"label": "USES",
|
| 47 |
+
"cue_span": "We leverage two previously released datasets PASTA",
|
| 48 |
+
"rationale": "The authors explicitly mention leveraging the PASTA dataset as part of their work.",
|
| 49 |
+
"text": "We leverage two previously released datasets PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig.",
|
| 50 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 51 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 52 |
+
"original_label": "Uses"
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"id": 4,
|
| 56 |
+
"label": "NOT_CONFIRMED",
|
| 57 |
+
"cue_span": "",
|
| 58 |
+
"rationale": "The sentence only indicates an intention to describe the datasets, not a specific use or extension of them.",
|
| 59 |
+
"text": "In the following paragraphs, we briefly describe the PASTA <CITED HERE> and SAGA (Vallu-rupalli et al., 2024) datasets and how these datasets\u2019 annotations relate to conditions and outcomes.",
|
| 60 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 61 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 62 |
+
"original_label": "Uses"
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"id": 1,
|
| 66 |
+
"label": "NOT_CONFIRMED",
|
| 67 |
+
"cue_span": "",
|
| 68 |
+
"rationale": "The sentence provides a description of what the target paper introduces without stating that the citing paper uses it.",
|
| 69 |
+
"text": "The PASTA dataset is an extension of ROCStories that substitutes alternative events into the stories, which may or may not match up with the original summary sentence <CITED HERE>.",
|
| 70 |
+
"citing_paper_id": "c1d02fc3786fb5e5b0543703af95046253109d4e",
|
| 71 |
+
"citing_title": "Inductive Bias Extraction and Matching for LLM Prompts",
|
| 72 |
+
"original_label": "Uses"
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"id": 11,
|
| 76 |
+
"label": "NOT_CONFIRMED",
|
| 77 |
+
"cue_span": "",
|
| 78 |
+
"rationale": "The sentence mentions that the resulting annotations reflect a process from the target paper but lacks an explicit adoption cue.",
|
| 79 |
+
"text": "With this HIT we obtain a new set of goal annotations reusing and modifying the free-form text annotations from the actual story to obtain annotations that are also minimally updated reflecting the process used for obtaining the counterfactual in PASTA <CITED HERE>.",
|
| 80 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 81 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 82 |
+
"original_label": "Uses"
|
| 83 |
+
},
|
| 84 |
+
{
|
| 85 |
+
"id": 13,
|
| 86 |
+
"label": "NOT_CONFIRMED",
|
| 87 |
+
"cue_span": "",
|
| 88 |
+
"rationale": "The sentence lacks an explicit citing-paper actor phrase such as 'we' or 'our' as required by the instructions.",
|
| 89 |
+
"text": "\u2026stories shown: in alternative 1 (top right), a different Figure 1: A participant\u2019s goal inferred from the actual story when applied to 3 alternative stories, drawn from the PASTA dataset <CITED HERE>; slightly varying actions in the stories lead to different goal achievement outcomes.",
|
| 90 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 91 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 92 |
+
"original_label": "Uses"
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"id": 2,
|
| 96 |
+
"label": "NOT_CONFIRMED",
|
| 97 |
+
"cue_span": "",
|
| 98 |
+
"rationale": "The sentence is a general description of dataset subsets and lacks an explicit adoption cue by the citing authors.",
|
| 99 |
+
"text": "The dataset is often separated into four subsets: training, testing, evaluation, and validation [32\u201339].",
|
| 100 |
+
"citing_paper_id": "a6924f6b715327b0f24d044e9e8ef799b3f4a0a2",
|
| 101 |
+
"citing_title": "Electrifying the Story Climax via Axial Self-Attention in GPT-3 Transformer Block",
|
| 102 |
+
"original_label": "Uses"
|
| 103 |
+
}
|
| 104 |
+
],
|
| 105 |
+
"confirmed": [
|
| 106 |
+
{
|
| 107 |
+
"id": 8,
|
| 108 |
+
"label": "EXTENDS",
|
| 109 |
+
"cue_span": "We expand the 3 categories used in PASTA",
|
| 110 |
+
"rationale": "The citing paper explicitly expands upon the categorical framework introduced in the target paper.",
|
| 111 |
+
"text": "We expand the 3 categories used in PASTA <CITED HERE>, for error analysis on 200 random states on the story state inference task, to 4 categories and group all conditions into these as follows: (a) Physical: This category includes natural physical attributes of an entity such as size, age,\u2026",
|
| 112 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 113 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 114 |
+
"original_label": "Uses"
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"id": 10,
|
| 118 |
+
"label": "USES",
|
| 119 |
+
"cue_span": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes",
|
| 120 |
+
"rationale": "The authors explicitly state they are leveraging the target dataset to generate and validate outcomes.",
|
| 121 |
+
"text": "We leverage PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) datasets to generate outcomes of SAGA and News Stories and validate these using both our generated and PASTA\u2019s crowd-annotated conditions. outcome is implicitly understood through constructing counterfactual mental\u2026",
|
| 122 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 123 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 124 |
+
"original_label": "Uses"
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"id": 16,
|
| 128 |
+
"label": "USES",
|
| 129 |
+
"cue_span": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset",
|
| 130 |
+
"rationale": "The authors used stories specifically from the PASTA dataset for their own annotation process.",
|
| 131 |
+
"text": "We annotated ROC stories (Mostafazadeh et al., 2016) that have corresponding alternative stories in the PASTA dataset <CITED HERE>.",
|
| 132 |
+
"citing_paper_id": "494f3c55553161be831f725a7b55c6095470c5cb",
|
| 133 |
+
"citing_title": "SAGA: A Participant-specific Examination of Story Alternatives and Goal Applicability for a Deeper Understanding of Complex Events",
|
| 134 |
+
"original_label": "Uses"
|
| 135 |
+
},
|
| 136 |
+
{
|
| 137 |
+
"id": 9,
|
| 138 |
+
"label": "USES",
|
| 139 |
+
"cue_span": "We leverage two previously released datasets PASTA",
|
| 140 |
+
"rationale": "The authors explicitly mention leveraging the PASTA dataset as part of their work.",
|
| 141 |
+
"text": "We leverage two previously released datasets PASTA <CITED HERE> and SAGA (Vallurupalli et al., 2024) consisting of participants\u2019 goal and state annotations (shown in Fig.",
|
| 142 |
+
"citing_paper_id": "ab73ca481256767addce56b20eb48ac183169e01",
|
| 143 |
+
"citing_title": "CoRE: Condition-based Reasoning for Identifying Outcome Variance in Complex Events",
|
| 144 |
+
"original_label": "Uses"
|
| 145 |
+
}
|
| 146 |
+
],
|
| 147 |
+
"confirmed_extends": 1,
|
| 148 |
+
"confirmed_uses": 3,
|
| 149 |
+
"final_label": "EXTENDS"
|
| 150 |
+
}
|