-
Notifications
You must be signed in to change notification settings - Fork 57
#25 Issue: Wikidata URI Grounding #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
bd4b34c
3d7011b
ae9b7a9
70850be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| { | ||
| "java.configuration.updateBuildConfiguration": "interactive" | ||
| "java.configuration.updateBuildConfiguration": "interactive", | ||
| "java.compile.nullAnalysis.mode": "automatic" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| package chatbot.lib.api; | ||
|
|
||
| import chatbot.lib.Constants; | ||
| import chatbot.lib.Utility; | ||
| import chatbot.lib.request.TemplateType; | ||
| import chatbot.lib.response.ResponseData; | ||
| import chatbot.lib.response.ResponseType; | ||
| import org.apache.jena.query.*; | ||
| import org.apache.jena.sparql.engine.http.QueryEngineHTTP; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.util.*; | ||
|
|
||
| /** | ||
| * Service for querying the Wikidata SPARQL endpoint to retrieve entity | ||
| * information. | ||
| * Mirrors the interface of the DBpedia SPARQL class but targets Wikidata. | ||
| */ | ||
| public class WikidataSPARQL { | ||
| private static final Logger logger = LoggerFactory.getLogger(WikidataSPARQL.class); | ||
|
|
||
| private static final String ENDPOINT = "https://query.wikidata.org/sparql"; | ||
| private static final String PREFIXES = "PREFIX wd: <http://www.wikidata.org/entity/>\n" + | ||
| "PREFIX wdt: <http://www.wikidata.org/prop/direct/>\n" + | ||
| "PREFIX wikibase: <http://wikiba.se/ontology#>\n" + | ||
| "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n" + | ||
| "PREFIX schema: <http://schema.org/>\n"; | ||
|
|
||
| public WikidataSPARQL() { | ||
| } | ||
|
|
||
| public String buildQuery(String query) { | ||
| return PREFIXES + query; | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves entity information from Wikidata for the given URI. | ||
| * Returns a ResponseData card with label, description, image, Wikipedia link, | ||
| * and Wikidata link. | ||
| */ | ||
| public ResponseData getEntityInformation(String uri) { | ||
| String entityId = Utility.extractWikidataEntityId(uri); | ||
| if (entityId == null) { | ||
| return null; | ||
| } | ||
|
|
||
| String query = buildQuery( | ||
| "SELECT ?label ?description ?image ?articleEN WHERE {\n" + | ||
| " wd:" + entityId + " rdfs:label ?label . FILTER(lang(?label) = 'en') .\n" + | ||
| " OPTIONAL { wd:" + entityId | ||
| + " schema:description ?description . FILTER(lang(?description) = 'en') }\n" + | ||
| " OPTIONAL { wd:" + entityId + " wdt:P18 ?image }\n" + | ||
| " OPTIONAL {\n" + | ||
| " ?articleEN schema:about wd:" + entityId + " ;\n" + | ||
| " schema:isPartOf <https://en.wikipedia.org/> .\n" + | ||
| " }\n" + | ||
| "} LIMIT 1"); | ||
|
|
||
| QueryExecution queryExecution = null; | ||
| ResponseData responseData = null; | ||
|
|
||
| try { | ||
| queryExecution = executeQuery(query); | ||
| Iterator<QuerySolution> results = queryExecution.execSelect(); | ||
| if (results.hasNext()) { | ||
| QuerySolution result = results.next(); | ||
| responseData = new ResponseData(); | ||
|
|
||
| // Label | ||
| String label = result.get("label").asLiteral().getString(); | ||
| responseData.setTitle(label); | ||
|
|
||
| // Description | ||
| if (result.get("description") != null) { | ||
| responseData.setText(result.get("description").asLiteral().getString()); | ||
| } | ||
|
|
||
| // Thumbnail / Image | ||
| if (result.get("image") != null) { | ||
| responseData.setImage(result.get("image").toString()); | ||
| } | ||
|
|
||
| // Wikipedia link | ||
| if (result.get("articleEN") != null) { | ||
| responseData.addButton(new ResponseData.Button("View in Wikipedia", ResponseType.BUTTON_LINK, | ||
| result.get("articleEN").toString())); | ||
| } | ||
|
|
||
| // Wikidata link | ||
| responseData.addButton(new ResponseData.Button("View in Wikidata", ResponseType.BUTTON_LINK, uri)); | ||
|
|
||
| // Learn More button (reuses the existing template mechanism) | ||
| responseData.addButton(new ResponseData.Button("Learn More", ResponseType.BUTTON_PARAM, | ||
| TemplateType.LEARN_MORE + Utility.STRING_SEPARATOR + uri + Utility.STRING_SEPARATOR + label)); | ||
| } | ||
| } catch (Exception e) { | ||
| logger.error("Error querying Wikidata for entity: " + uri, e); | ||
| } finally { | ||
| if (queryExecution != null) { | ||
| queryExecution.close(); | ||
| } | ||
| } | ||
| return responseData; | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the English label for a Wikidata entity. | ||
| */ | ||
| public String getLabel(String uri) { | ||
| String entityId = Utility.extractWikidataEntityId(uri); | ||
| if (entityId == null) | ||
| return null; | ||
|
|
||
| String query = buildQuery( | ||
| "SELECT ?label WHERE {\n" + | ||
| " wd:" + entityId + " rdfs:label ?label . FILTER(lang(?label) = 'en') .\n" + | ||
| "} LIMIT 1"); | ||
|
|
||
| QueryExecution queryExecution = executeQuery(query); | ||
| String label = null; | ||
|
|
||
| try { | ||
| Iterator<QuerySolution> results = queryExecution.execSelect(); | ||
| if (results.hasNext()) { | ||
| label = results.next().get("label").asLiteral().getString(); | ||
| } | ||
| } | ||
| catch (Exception e) { | ||
| logger.error("Error querying Wikidata for label: " + uri, e); | ||
| } | ||
| finally { | ||
| if (queryExecution != null) { | ||
| queryExecution.close(); | ||
| } | ||
| } | ||
| return label; | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Executes a SPARQL query against the Wikidata endpoint. | ||
| */ | ||
| public QueryExecution executeQuery(String queryString) { | ||
| logger.info("Wikidata SPARQL Query is:\n" + queryString); | ||
| Query query = QueryFactory.create(queryString); | ||
| QueryEngineHTTP queryEngine = (QueryEngineHTTP) QueryExecutionFactory.sparqlService(ENDPOINT, query); | ||
| queryEngine.addParam("timeout", String.valueOf(Constants.API_TIMEOUT)); | ||
| // Wikidata requires a User-Agent header | ||
| queryEngine.addParam("format", "json"); | ||
aliiakbarkhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return queryEngine; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,9 +22,6 @@ | |||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Created by ramgathreya on 7/1/17. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public class QANARY { | ||||||||||||||||||||||
| private static final Logger logger = LoggerFactory.getLogger(QANARY.class); | ||||||||||||||||||||||
| private static final String URL = "http://qanswer-core1.univ-st-etienne.fr/api/gerbil"; | ||||||||||||||||||||||
|
|
@@ -36,50 +33,55 @@ public QANARY() { | |||||||||||||||||||||
| this.client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private String makeRequest(String question) { | ||||||||||||||||||||||
| private String makeRequest(String question, String knowledgeBase) { | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| HttpPost httpPost = new HttpPost(URL); | ||||||||||||||||||||||
| List<NameValuePair> params = new ArrayList<>(); | ||||||||||||||||||||||
| params.add(new BasicNameValuePair("query", question)); | ||||||||||||||||||||||
| // params.add(new BasicNameValuePair("lang", "it")); | ||||||||||||||||||||||
| params.add(new BasicNameValuePair("kb", "dbpedia")); | ||||||||||||||||||||||
| params.add(new BasicNameValuePair("kb", knowledgeBase)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, Consts.UTF_8); | ||||||||||||||||||||||
| httpPost.setEntity(entity); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| HttpResponse response = client.execute(httpPost); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Error Scenario | ||||||||||||||||||||||
| if(response.getStatusLine().getStatusCode() >= 400) { | ||||||||||||||||||||||
| logger.error("QANARY Server could not answer due to: " + response.getStatusLine()); | ||||||||||||||||||||||
| if (response.getStatusLine().getStatusCode() >= 400) { | ||||||||||||||||||||||
| logger.error("QANARY Server could not answer for kb=" + knowledgeBase + " due to: " | ||||||||||||||||||||||
| + response.getStatusLine()); | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return EntityUtils.toString(response.getEntity()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| catch(Exception e) { | ||||||||||||||||||||||
| logger.error(e.getMessage()); | ||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||
| logger.error("QANARY request failed for kb=" + knowledgeBase + ": " + e.getMessage()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Calls QANARY Service then returns resulting data as a list of Data Objects | ||||||||||||||||||||||
| public QAService.Data search(String question) throws Exception { | ||||||||||||||||||||||
| private QAService.Data parseResponse(String response) throws Exception { | ||||||||||||||||||||||
| QAService.Data data = new QAService.Data(); | ||||||||||||||||||||||
| String response = makeRequest(question); | ||||||||||||||||||||||
| if(response != null) { | ||||||||||||||||||||||
| if (response != null) { | ||||||||||||||||||||||
| ObjectMapper mapper = new ObjectMapper(); | ||||||||||||||||||||||
| JsonNode rootNode = mapper.readTree(response); | ||||||||||||||||||||||
| JsonNode answers = mapper.readTree(rootNode.findValue("questions").get(0).get("question").get("answers").getTextValue()); | ||||||||||||||||||||||
| JsonNode questions = rootNode.findValue("questions"); | ||||||||||||||||||||||
| if (questions == null || !questions.isArray() || questions.size() == 0) { | ||||||||||||||||||||||
| return data; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| JsonNode questionNode = questions.get(0).get("question"); | ||||||||||||||||||||||
| if (questionNode == null || questionNode.get("answers") == null) { | ||||||||||||||||||||||
| return data; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| JsonNode answers = mapper.readTree(questionNode.get("answers").getTextValue()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (answers != null) { | ||||||||||||||||||||||
| JsonNode bindings = answers.get("results").get("bindings"); | ||||||||||||||||||||||
| for(JsonNode binding : bindings) { | ||||||||||||||||||||||
| for (JsonNode binding : bindings) { | ||||||||||||||||||||||
| Iterator<Map.Entry<String, JsonNode>> nodes = binding.getFields(); | ||||||||||||||||||||||
| while (nodes.hasNext()) { | ||||||||||||||||||||||
| Map.Entry<String, JsonNode> entry = nodes.next(); | ||||||||||||||||||||||
| JsonNode value = entry.getValue(); | ||||||||||||||||||||||
| switch(value.get("type").getTextValue()) { | ||||||||||||||||||||||
| switch (value.get("type").getTextValue()) { | ||||||||||||||||||||||
| case "uri": | ||||||||||||||||||||||
| data.addURI(value.get("value").getTextValue()); | ||||||||||||||||||||||
| break; | ||||||||||||||||||||||
|
|
@@ -94,4 +96,33 @@ public QAService.Data search(String question) throws Exception { | |||||||||||||||||||||
| return data; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public QAService.Data search(String question) throws Exception { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| QAService.Data data = new QAService.Data(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Query DBpedia KB | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| QAService.Data dbpediaData = parseResponse(makeRequest(question, "dbpedia")); | ||||||||||||||||||||||
| data.addData(dbpediaData, false); | ||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||
| logger.error("DBpedia QANARY query failed: " + e.getMessage()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // If DBpedia yielded an answer, return early so we don't pay | ||||||||||||||||||||||
| // the extra latency waiting for Wikidata. | ||||||||||||||||||||||
| if (!data.getUris().isEmpty() || !data.getLiterals().isEmpty()) { | ||||||||||||||||||||||
| return data; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+111
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t short-circuit on DBpedia literals. At Line 113, returning when Minimal fix- if (!data.getUris().isEmpty() || !data.getLiterals().isEmpty()) {
+ if (!data.getUris().isEmpty()) {
return data;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Query Wikidata KB | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| QAService.Data wikidataData = parseResponse(makeRequest(question, "wikidata")); | ||||||||||||||||||||||
| data.addData(wikidataData, false); | ||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||
| logger.error("Wikidata QANARY query failed, continuing with DBpedia results only: " + e.getMessage()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
aliiakbarkhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return data; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.