Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3286df6450 |
+1
-26
@@ -1,28 +1,3 @@
|
|||||||
## Refining the agent output
|
## Refining the agent output
|
||||||
|
|
||||||
Experiments modifying pipeline
|
TODO: Table and document experiments
|
||||||
|
|
||||||
| Model | % Correct | % Change |
|
|
||||||
|------------------|----------:|---------:|
|
|
||||||
| BASELINE | 33 | 0 |
|
|
||||||
| Improv Prompt | 39.96 | 0.21 |
|
|
||||||
| Add Examples | 44.67 | 0.35 |
|
|
||||||
| Date | 45.51 | 0.38 |
|
|
||||||
| Chain of Thought | 43.38 | 0.31 |
|
|
||||||
| Self-Critique | 44.36 | 0.34 |
|
|
||||||
|
|
||||||
Experiments with different model types:
|
|
||||||
| Model | % Correct | % Change |
|
|
||||||
|-------------------------------|----------:|---------:|
|
|
||||||
| gpt-5-mini | 33 | 0 |
|
|
||||||
| gpt-5.4-mini | 32.4 | -0.02 |
|
|
||||||
| llama3.1:8b-instruct-q4_K_M | ? | ? |
|
|
||||||
| qwen3.5:9b | 0 | -100 |
|
|
||||||
|
|
||||||
%age valid URLS
|
|
||||||
| Model | Number | % Age |
|
|
||||||
|-------------------------------|----------:|---------:|
|
|
||||||
| gpt-5-mini | 22/405 | 5.43 |
|
|
||||||
| gpt-5.4-mini | 29/278 | 10.43 |
|
|
||||||
| llama3.1:8b-instruct-q4_K_M | ? | ? |
|
|
||||||
| qwen3.5:9b | 0 | 0 |
|
|
||||||
@@ -9,7 +9,7 @@ export function createModelNode(tools: any, promptPath: string): GraphNode<typeo
|
|||||||
const sysPrompt = await hydratePrompt(promptPath, state);
|
const sysPrompt = await hydratePrompt(promptPath, state);
|
||||||
|
|
||||||
const model = new ChatOpenAI({
|
const model = new ChatOpenAI({
|
||||||
model: "gpt-4.1-mini"
|
model: "gpt-5-mini"
|
||||||
});
|
});
|
||||||
const modelWithTools = model.bindTools(Object.values(tools));
|
const modelWithTools = model.bindTools(Object.values(tools));
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ export const triggerEventSetup: GraphNode<typeof MessagesState> = async (state)
|
|||||||
let nc = state?.messages?.at(-1)?.content ?? "" //keep a copy of normalized trigger event. Again two things, womp womp
|
let nc = state?.messages?.at(-1)?.content ?? "" //keep a copy of normalized trigger event. Again two things, womp womp
|
||||||
|
|
||||||
//Now give in-context examples. hopwfully we can self-teach?
|
//Now give in-context examples. hopwfully we can self-teach?
|
||||||
let similarityResults = await rankExampleTriggerEvents(state.disinformationTitle)
|
// let similarityResults = await rankExampleTriggerEvents(state.disinformationTitle)
|
||||||
|
|
||||||
let messages : BaseMessage[] = similarityResults.map((item) => {
|
// let messages : BaseMessage[] = similarityResults.map((item) => {
|
||||||
return new AIMessage(`- Event: ${item.rawtext} \n\n - Claims and given scores: ${item.cleantext}`)
|
// return new AIMessage(`- Event: ${item.rawtext} \n\n - Claims and given scores: ${item.cleantext}`)
|
||||||
})
|
// })
|
||||||
|
|
||||||
return { messages: messages, disinformationTitle: state.disinformationTitle, normalizedClaim: nc };
|
return { disinformationTitle: state.disinformationTitle, normalizedClaim: nc };
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { GraphNode } from "@langchain/langgraph";
|
import { GraphNode } from "@langchain/langgraph";
|
||||||
import { MessagesState, ProposedTriggerEventArray } from "../state";
|
import { MessagesState, ProposedTriggerEventArray } from "../state";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { jsonrepair } from 'jsonrepair'
|
import { queryScraper } from "../tools/webSearch";
|
||||||
|
import { rankAndDisplayData } from "../tools/triggerEventTools";
|
||||||
|
|
||||||
export const verificationSetup: GraphNode<typeof MessagesState> = async (state) => {
|
export const verificationSetup: GraphNode<typeof MessagesState> = async (state) => {
|
||||||
//this is kinda doing two things, but having two nodes for it seems overkill
|
//this is kinda doing two things, but having two nodes for it seems overkill
|
||||||
@@ -10,29 +11,15 @@ export const verificationSetup: GraphNode<typeof MessagesState> = async (state)
|
|||||||
logger.warn("No trigger events in memory, parsing")
|
logger.warn("No trigger events in memory, parsing")
|
||||||
|
|
||||||
let genResponse = state.messages.at(-1)?.content.toString() ?? "";
|
let genResponse = state.messages.at(-1)?.content.toString() ?? "";
|
||||||
|
const parsed = ProposedTriggerEventArray.parse(JSON.parse(genResponse));
|
||||||
|
|
||||||
const repaired = jsonrepair(genResponse);
|
for (let i = 0; i < parsed.length; i++) {
|
||||||
|
const search = parsed[i].SearchQuery
|
||||||
|
// const data = await queryScraper(search);
|
||||||
|
// const output = await rankAndDisplayData(data, search);
|
||||||
|
|
||||||
let parsed;
|
// parsed[i].context = output;
|
||||||
|
parsed[i].context = "NONE"
|
||||||
try {
|
|
||||||
const json = JSON.parse(repaired);
|
|
||||||
|
|
||||||
if (Array.isArray(json)) {
|
|
||||||
parsed = ProposedTriggerEventArray.parse(json);
|
|
||||||
} else {
|
|
||||||
// try grab first value
|
|
||||||
const firstValue = Object.values(json)[0];
|
|
||||||
|
|
||||||
if (Array.isArray(firstValue)) {
|
|
||||||
parsed = ProposedTriggerEventArray.parse(firstValue);
|
|
||||||
} else {
|
|
||||||
throw new Error("No array found in JSON");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
logger.error(`Failed to parse LLM response: ${err.message}`);
|
|
||||||
throw new Error(`Failed to parse LLM response: ${err}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { proposedTriggerEvent: parsed, proposedTriggerEventIndex: 0 };
|
return { proposedTriggerEvent: parsed, proposedTriggerEventIndex: 0 };
|
||||||
|
|||||||
Generated
-10
@@ -20,7 +20,6 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"exponential-backoff": "^3.1.3",
|
"exponential-backoff": "^3.1.3",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"jsonrepair": "^3.13.3",
|
|
||||||
"langchain": "^1.2.14",
|
"langchain": "^1.2.14",
|
||||||
"selenium-webdriver": "^4.40.0",
|
"selenium-webdriver": "^4.40.0",
|
||||||
"tldts": "^7.0.23",
|
"tldts": "^7.0.23",
|
||||||
@@ -2076,15 +2075,6 @@
|
|||||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/jsonrepair": {
|
|
||||||
"version": "3.13.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.3.tgz",
|
|
||||||
"integrity": "sha512-BTznj0owIt2CBAH/LTo7+1I5pMvl1e1033LRl/HUowlZmJOIhzC0zbX5bxMngLkfT4WnzPP26QnW5wMr2g9tsQ==",
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"jsonrepair": "bin/cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jszip": {
|
"node_modules/jszip": {
|
||||||
"version": "3.10.1",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"exponential-backoff": "^3.1.3",
|
"exponential-backoff": "^3.1.3",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"jsonrepair": "^3.13.3",
|
|
||||||
"langchain": "^1.2.14",
|
"langchain": "^1.2.14",
|
||||||
"selenium-webdriver": "^4.40.0",
|
"selenium-webdriver": "^4.40.0",
|
||||||
"tldts": "^7.0.23",
|
"tldts": "^7.0.23",
|
||||||
|
|||||||
@@ -8,19 +8,13 @@ Produce up-to 5 specific "trigger events" that happened that could have led to t
|
|||||||
Remember the time frame of the disinformation campaign: ###CDATE###
|
Remember the time frame of the disinformation campaign: ###CDATE###
|
||||||
Include no information or events that would not have been available at the time.
|
Include no information or events that would not have been available at the time.
|
||||||
|
|
||||||
You MEED TO use the tools available to you in order to produce up to date information on URL and search query, else you will be wrong and the analysis invalid.
|
|
||||||
You NEED TO use the web search and open URL tools to ensure page validity or else all work upto this point will have to be discarded.
|
|
||||||
|
|
||||||
|
|
||||||
Produce no more text other than the json.
|
Produce no more text other than the json.
|
||||||
|
|
||||||
Include a concise but specific search query that can be looked up on a search engine in order to allow for the verification.
|
Include a concise but specific search query that can be looked up on a search engine in order to allow for the verification.
|
||||||
|
|
||||||
Include a url to a source for your trigger event (not a web search, a specific url from a reputuable source). Do not use OAI cite, include url as text in response.
|
Include a url to a source for your trigger event (not a web search, a specific url from a reputuable source). Do not use OAI cite, include url as text in response.
|
||||||
|
|
||||||
Include the date that the event happened ("March 2022" for exmaple)
|
Use a JSON format with each entry containing "Event,ReasoningWhyRelevant,SearchQuery,Url".
|
||||||
|
|
||||||
Use a JSON format with each entry containing "Event,ReasoningWhyRelevant,SearchQuery,Url,Date".
|
|
||||||
|
|
||||||
Multiple tool invocations should be requested at once, if applicable.
|
Multiple tool invocations should be requested at once, if applicable.
|
||||||
Use your abilities to look between the lines and produce some insightful analysis, thinking both short and long term.
|
Use your abilities to look between the lines and produce some insightful analysis, thinking both short and long term.
|
||||||
@@ -30,9 +24,4 @@ Events will be reordered as part of processing, each statement must stand alone
|
|||||||
The preceeding messages act as examples of previous responses to potentially ficitonal events and scores given.
|
The preceeding messages act as examples of previous responses to potentially ficitonal events and scores given.
|
||||||
Analysis should only be completed for proposed events that would graner >0.7 points
|
Analysis should only be completed for proposed events that would graner >0.7 points
|
||||||
|
|
||||||
This pipeline is running well pasy your knowledge cutoff.
|
|
||||||
Any URLs will change signigicantly over time.
|
|
||||||
You MEED TO use the tools available to you in order to produce up to date information on URL and search query, else you will be wrong and the analysis invalid.
|
|
||||||
You NEED TO use the web search and open URL tools to ensure page validity or else all work upto this point will have to be discarded.
|
|
||||||
|
|
||||||
Lets go through it step by step
|
Lets go through it step by step
|
||||||
@@ -9,7 +9,6 @@ export const ProposedTriggerEvent = z.object({
|
|||||||
ReasoningWhyRelevant: z.string(),
|
ReasoningWhyRelevant: z.string(),
|
||||||
SearchQuery: z.string(),
|
SearchQuery: z.string(),
|
||||||
Url: z.url(),
|
Url: z.url(),
|
||||||
Date: z.string(),
|
|
||||||
context: z.string().optional(),
|
context: z.string().optional(),
|
||||||
score: z.number().optional()
|
score: z.number().optional()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ const CACHE_PATH = "../data/csv.cache.json";
|
|||||||
|
|
||||||
const JSONL_PATH = "../data/input.jsonl"
|
const JSONL_PATH = "../data/input.jsonl"
|
||||||
|
|
||||||
const BM25_MIN_DOCS = 3;
|
|
||||||
|
|
||||||
type EmbeddingCache = {
|
type EmbeddingCache = {
|
||||||
rawtexts: string[];
|
rawtexts: string[];
|
||||||
cleantexts: string[];
|
cleantexts: string[];
|
||||||
@@ -289,20 +287,8 @@ async function embedText(text: string): Promise<number[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildBM25(texts: string[]) {
|
function buildBM25(texts: string[]) {
|
||||||
let paddedTexts = texts;
|
logger.info("Building BM25 index (%s docs)...", texts.length);
|
||||||
|
|
||||||
if (texts.length < BM25_MIN_DOCS) {
|
|
||||||
const needed = BM25_MIN_DOCS - texts.length;
|
|
||||||
logger.error(
|
|
||||||
"Corpus too small for BM25 (%s docs, need %s+), padding with %s dummy doc(s)",
|
|
||||||
texts.length,
|
|
||||||
BM25_MIN_DOCS,
|
|
||||||
needed
|
|
||||||
);
|
|
||||||
paddedTexts = [...texts, ...Array(needed).fill("placeholder dummy document")];
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Building BM25 index (%s docs)...", paddedTexts.length);
|
|
||||||
const bm25 = bm25Factory();
|
const bm25 = bm25Factory();
|
||||||
|
|
||||||
bm25.defineConfig({
|
bm25.defineConfig({
|
||||||
@@ -316,7 +302,7 @@ function buildBM25(texts: string[]) {
|
|||||||
nlp.tokens.removeWords,
|
nlp.tokens.removeWords,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
paddedTexts.forEach((text, i) => {
|
texts.forEach((text, i) => {
|
||||||
bm25.addDoc({ text }, i);
|
bm25.addDoc({ text }, i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,92 +1,32 @@
|
|||||||
import { Builder, Browser } from "selenium-webdriver";
|
import { Builder, Browser } from "selenium-webdriver";
|
||||||
import firefox from "selenium-webdriver/firefox";
|
import firefox from "selenium-webdriver/firefox";
|
||||||
import { backOff } from "exponential-backoff";
|
|
||||||
import { logger } from "../utils/logger";
|
|
||||||
|
|
||||||
export async function extractWebpageContent(url: string) : Promise<string[]>{
|
export async function extractWebpageContent(url: string) : Promise<string[]>{
|
||||||
try {
|
|
||||||
const response = await backOff(async () => {
|
|
||||||
return await extractWebpageContentWorker(url);
|
|
||||||
}, {
|
|
||||||
numOfAttempts: 10,
|
|
||||||
startingDelay: 500,
|
|
||||||
timeMultiple: 2,
|
|
||||||
jitter: "full",
|
|
||||||
maxDelay: 50000,
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
} catch (err: any) {
|
|
||||||
logger.error(`Failed out of retry loop for URL "${url}", returning placeholder to pipeline`);
|
|
||||||
return ["API EXCEPTION"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function extractWebpageContentWorker(url: string): Promise<string[]> {
|
|
||||||
let driver;
|
|
||||||
try {
|
|
||||||
const options = new firefox.Options();
|
const options = new firefox.Options();
|
||||||
options.addArguments("--headless");
|
options.addArguments("--headless");
|
||||||
driver = await new Builder()
|
|
||||||
.forBrowser(Browser.FIREFOX)
|
|
||||||
.setFirefoxOptions(options)
|
|
||||||
.build();
|
|
||||||
} catch (err: any) {
|
|
||||||
const desc = `Failed to launch Firefox driver: ${err.message}`;
|
|
||||||
logger.error(desc);
|
|
||||||
throw new Error(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
await driver.get(url);
|
|
||||||
} catch (err: any) {
|
|
||||||
const desc = `Failed to navigate to URL "${url}": ${err.message}`;
|
|
||||||
logger.error(desc);
|
|
||||||
throw new Error(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let driver = await new Builder().forBrowser(Browser.FIREFOX).setFirefoxOptions(options).build()
|
||||||
try {
|
try {
|
||||||
|
await driver.get(url)
|
||||||
await driver.wait(async () => {
|
await driver.wait(async () => {
|
||||||
return await driver.executeScript(
|
return await driver.executeScript(
|
||||||
"return document.readyState === 'complete'"
|
"return document.readyState === 'complete'"
|
||||||
);
|
);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
} catch (err: any) {
|
|
||||||
logger.error(`Page load timed out for "${url}", attempting to read partial content: ${err.message}`);
|
|
||||||
// do not throw, attempt to read
|
|
||||||
}
|
|
||||||
|
|
||||||
let readableText: string;
|
const readableText = await driver.executeScript(
|
||||||
try {
|
|
||||||
readableText = await driver.executeScript(
|
|
||||||
"return document.body.innerText;"
|
"return document.body.innerText;"
|
||||||
) as string;
|
) as string;
|
||||||
} catch (err: any) {
|
|
||||||
const desc = `Failed to extract page text from "${url}": ${err.message}`;
|
|
||||||
logger.error(desc);
|
|
||||||
throw new Error(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredLines = readableText
|
const filteredLines = readableText
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.map(line => line.trim())
|
.map(line => line.trim())
|
||||||
.filter(line => line.split(/\s+/).length > 1);
|
.filter(line => line.split(/\s+/).length > 1);
|
||||||
|
|
||||||
if (filteredLines.length === 0) {
|
|
||||||
const desc = `No content extracted from "${url}"`;
|
|
||||||
logger.error(desc);
|
|
||||||
throw new Error(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredLines;
|
return filteredLines;
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
await driver.quit()
|
||||||
await driver.quit();
|
|
||||||
} catch (err: any) {
|
|
||||||
logger.error(`Failed to quit Firefox driver cleanly: ${err.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(await extractWebpageContent("https://www.bbc.co.uk/news/live/c74wd01egvyt"))
|
//console.log(await extractWebpageContent("https://www.bbc.co.uk/news/live/c74wd01egvyt"))
|
||||||
// console.log(await extractWebpageContent("https://badcertificate.int.jeynes.uk/"))
|
|
||||||
@@ -5,7 +5,7 @@ set -e
|
|||||||
run_agent () {
|
run_agent () {
|
||||||
echo "Starting LangGraph agent..."
|
echo "Starting LangGraph agent..."
|
||||||
cd agent
|
cd agent
|
||||||
npx @langchain/langgraph-cli dev --host 127.0.0.1
|
npx @langchain/langgraph-cli dev
|
||||||
}
|
}
|
||||||
|
|
||||||
run_ensemble_service () {
|
run_ensemble_service () {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ datasets
|
|||||||
# ROBERTA
|
# ROBERTA
|
||||||
scikit-learn
|
scikit-learn
|
||||||
transformers[torch]
|
transformers[torch]
|
||||||
sentence_transformers
|
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
numpy
|
numpy
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ async function processRecord(record: any): Promise<ResultRecord> {
|
|||||||
input: buildAgentInput(record),
|
input: buildAgentInput(record),
|
||||||
streamMode: "values",
|
streamMode: "values",
|
||||||
config: {
|
config: {
|
||||||
recursion_limit: 100
|
recursion_limit: 50
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
import json
|
|
||||||
import argparse
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
||||||
from selenium import webdriver
|
|
||||||
from selenium.webdriver.chrome.options import Options
|
|
||||||
from selenium.common.exceptions import WebDriverException, TimeoutException, StaleElementReferenceException
|
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
def init_driver():
|
|
||||||
options = Options()
|
|
||||||
options.headless = True
|
|
||||||
options.add_argument("--disable-gpu")
|
|
||||||
options.add_argument("--no-sandbox")
|
|
||||||
options.add_argument("--headless")
|
|
||||||
options.add_argument("--disable-blink-features=AutomationControlled")
|
|
||||||
options.add_argument("--window-size=1920,1080")
|
|
||||||
prefs = {
|
|
||||||
"profile.managed_default_content_settings.images": 2, # block images
|
|
||||||
"profile.default_content_setting_values.stylesheets": 2, # block CSS
|
|
||||||
"profile.managed_default_content_settings.cookies": 2, # optional
|
|
||||||
}
|
|
||||||
options.add_experimental_option("prefs", prefs)
|
|
||||||
|
|
||||||
driver = webdriver.Chrome(options=options)
|
|
||||||
driver.set_page_load_timeout(30)
|
|
||||||
return driver
|
|
||||||
|
|
||||||
def is_root_url(url):
|
|
||||||
parsed = urlparse(url)
|
|
||||||
return parsed.path in ("", "/")
|
|
||||||
|
|
||||||
def is_404_page(driver):
|
|
||||||
"""Safely check for 404, handling stale elements."""
|
|
||||||
try:
|
|
||||||
title = driver.title.lower()
|
|
||||||
body_text = driver.find_element("tag name", "body").text.lower()
|
|
||||||
return "404" in title or "404" in body_text
|
|
||||||
except StaleElementReferenceException:
|
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def check_url_selenium(url):
|
|
||||||
driver = None
|
|
||||||
try:
|
|
||||||
driver = init_driver()
|
|
||||||
driver.get(url)
|
|
||||||
# 404 check
|
|
||||||
if is_404_page(driver):
|
|
||||||
return False, "404 page detected"
|
|
||||||
# Root URL after redirects
|
|
||||||
final_url = driver.current_url
|
|
||||||
if is_root_url(final_url):
|
|
||||||
return False, f"Redirected to root URL ({final_url})"
|
|
||||||
return True, None
|
|
||||||
except (WebDriverException, TimeoutException) as e:
|
|
||||||
return False, str(e)
|
|
||||||
finally:
|
|
||||||
if driver:
|
|
||||||
driver.quit()
|
|
||||||
|
|
||||||
def process_event(event):
|
|
||||||
"""Process an event only if score > 0.4."""
|
|
||||||
score = event.get("score", 0)
|
|
||||||
if score <= 0.4:
|
|
||||||
return None, False, "Score too low"
|
|
||||||
url = event.get("Url")
|
|
||||||
if not url:
|
|
||||||
return None, False, "No URL"
|
|
||||||
is_valid, error_msg = check_url_selenium(url)
|
|
||||||
event["url_valid"] = is_valid
|
|
||||||
return url, is_valid, error_msg
|
|
||||||
|
|
||||||
def process_jsonl_file(file_path, max_workers=4):
|
|
||||||
invalid_urls = []
|
|
||||||
valid_urls = 0
|
|
||||||
|
|
||||||
# Gather events with score > 0.4
|
|
||||||
urls_to_check = []
|
|
||||||
with open(file_path, "r", encoding="utf-8") as f:
|
|
||||||
for line in f:
|
|
||||||
line_data = json.loads(line)
|
|
||||||
if line_data.get("status") != "success":
|
|
||||||
continue
|
|
||||||
for event in line_data.get("events", []):
|
|
||||||
if event.get("score", 0) > 0.4:
|
|
||||||
urls_to_check.append(event)
|
|
||||||
|
|
||||||
total_urls = len(urls_to_check)
|
|
||||||
|
|
||||||
# ThreadPoolExecutor with tqdm progress bar
|
|
||||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
||||||
future_to_event = {executor.submit(process_event, e): e for e in urls_to_check}
|
|
||||||
for future in tqdm(as_completed(future_to_event), total=total_urls, desc="Checking URLs"):
|
|
||||||
url, is_valid, error_msg = future.result()
|
|
||||||
if not is_valid and url:
|
|
||||||
invalid_urls.append((url, error_msg))
|
|
||||||
else:
|
|
||||||
valid_urls += 1
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
if invalid_urls:
|
|
||||||
print("\nList of invalid URLs and reasons:")
|
|
||||||
for url, err in invalid_urls:
|
|
||||||
print(f"{url} --> {err}")
|
|
||||||
print("\n=== URL Validation Summary ===")
|
|
||||||
print(f"Total URLs processed: {total_urls}")
|
|
||||||
print(f"Valid URLs (loaded successfully): {valid_urls}")
|
|
||||||
print(f"Invalid URLs: {len(invalid_urls)}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(description="Validate URLs in JSONL file events using Selenium")
|
|
||||||
parser.add_argument("file_path", type=str, help="Path to the JSONL file")
|
|
||||||
parser.add_argument("--workers", type=int, default=4, help="Number of parallel Selenium workers")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
process_jsonl_file(args.file_path, max_workers=args.workers)
|
|
||||||
Reference in New Issue
Block a user