summaryrefslogtreecommitdiffstats
path: root/sample-apps
diff options
context:
space:
mode:
authorLester Solbakken <lesters@yahoo-inc.com>2016-06-30 16:22:38 +0200
committerLester Solbakken <lesters@yahoo-inc.com>2016-06-30 16:22:38 +0200
commitc56c752cf29c6db497c1dc3640ab134fbd3274a3 (patch)
tree3d6fc69a54787c6d7c75bb481249c0ac883f1236 /sample-apps
parent96fe10cc0440fbd8fa2a6c1a47d65761af9cf4f2 (diff)
Add basic blog-recommendation sample app
Diffstat (limited to 'sample-apps')
-rw-r--r--sample-apps/blog-recommendation/README.md55
-rw-r--r--sample-apps/blog-recommendation/pom.xml85
-rw-r--r--sample-apps/blog-recommendation/src/main/application/deployment.xml5
-rw-r--r--sample-apps/blog-recommendation/src/main/application/search/query-profiles/types/root.xml3
-rw-r--r--sample-apps/blog-recommendation/src/main/application/searchdefinitions/blog_post.sd66
-rw-r--r--sample-apps/blog-recommendation/src/main/application/searchdefinitions/user.sd26
-rw-r--r--sample-apps/blog-recommendation/src/main/application/services.xml36
-rw-r--r--sample-apps/blog-recommendation/src/main/java/com/yahoo/example/BlogTensorSearcher.java43
-rw-r--r--sample-apps/blog-recommendation/src/main/java/com/yahoo/example/UserProfileSearcher.java89
9 files changed, 408 insertions, 0 deletions
diff --git a/sample-apps/blog-recommendation/README.md b/sample-apps/blog-recommendation/README.md
new file mode 100644
index 00000000000..0b6a00c9743
--- /dev/null
+++ b/sample-apps/blog-recommendation/README.md
@@ -0,0 +1,55 @@
+Basic Search Application
+==================
+Start by [deploying a sample application](http://vespa.corp.yahoo.com/6/documentation/developing-with-vespa.html).
+
+### Find the endpoint
+
+When you have successfully deployed your own compiled version of the application above, you need to find the name of the "endpoint".
+The endpoint is used for feeding and searching for data.
+**Please allow a few minutes for the endpoint to appear after deployment**
+You can find this endpoint by doing:
+ ```sh
+
+ mvn vespa:endpoints | grep Endpoints
+ ```
+
+You can also find it by looking at the [Hosted Vespa Dashboard](http://dashboard.vespa.corp.yahoo.com).
+
+
+### Feed and search
+ 1. **Feed** the data that is to be searched
+ ```sh
+
+ # Feeding two documents
+ curl -X POST --data-binary @music-data-1.json <endpoint url>/document/v1/music/music/docid/1 | python -m json.tool
+ curl -X POST --data-binary @music-data-2.json <endpoint url>/document/v1/music/music/docid/2 | python -m json.tool
+
+ ```
+
+For feeding many documents fast and reliable, checkout [feeding example](https://git.corp.yahoo.com/vespa-samples/basic-feeding-client)
+
+ 2. **Visit documents
+
+ Since we do not have many documents we can list them all
+ ```sh
+
+ # All documents
+ curl <endpoint url>/document/v1/music/music/docid | python -m json.tool
+
+ # Document with id 1
+ curl <endpoint url>/document/v1/music/music/docid/1 | python -m json.tool
+
+ ```
+
+ 3. **Search**
+ We can also search for documents:
+ ```sh
+
+ curl '<endpoint url>/search/?query=bad' | python -m json.tool
+
+
+ ```
+
+### Next step: from development to production
+See [continuous deployments](http://vespa.corp.yahoo.com/6/documentation/continuous-deployment.html) for how to implement continuous deployments for production.
+See [RESTified Document Operation API](http://vespa.corp.yahoo.com/6/documentation/document_api_v1.html) for documentation about the REST API for document operations.
diff --git a/sample-apps/blog-recommendation/pom.xml b/sample-apps/blog-recommendation/pom.xml
new file mode 100644
index 00000000000..6e55e109f43
--- /dev/null
+++ b/sample-apps/blog-recommendation/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.yahoo.example</groupId>
+ <artifactId>recommendation</artifactId>
+ <packaging>container-plugin</packaging>
+ <version>0.0.1</version>
+ <name>application</name>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <test.hide>true</test.hide>
+ <application>blog-recommendation</application>
+ <instance>default</instance>
+ </properties>
+
+ <parent>
+ <groupId>com.yahoo.vespa.tenant</groupId>
+ <artifactId>base</artifactId>
+ <version>RELEASE</version>
+ </parent>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>ymaven</id>
+ <url>http://ymaven.corp.yahoo.com:9999/proximity/repository/public</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>maven2-repository.dev.java.net</id>
+ <url>http://download.java.net/maven/2</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ <pluginRepository>
+ <id>vespa-maven-release</id>
+ <name>vespa-maven-release</name>
+ <url>http://edge.artifactory.yahoo.com:8000/artifactory/vespa-maven-libs-release-local</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+ <repositories>
+ <repository>
+ <id>ymaven</id>
+ <url>http://ymaven.corp.yahoo.com:9999/proximity/repository/public</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>vespa-maven-release</id>
+ <name>vespa-maven-release</name>
+ <url>http://edge.artifactory.yahoo.com:8000/artifactory/vespa-maven-libs-release-local</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Werror</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sample-apps/blog-recommendation/src/main/application/deployment.xml b/sample-apps/blog-recommendation/src/main/application/deployment.xml
new file mode 100644
index 00000000000..0a2397bbca9
--- /dev/null
+++ b/sample-apps/blog-recommendation/src/main/application/deployment.xml
@@ -0,0 +1,5 @@
+<deployment version='1.0'>
+ <test />
+ <staging />
+ <prod/>
+</deployment>
diff --git a/sample-apps/blog-recommendation/src/main/application/search/query-profiles/types/root.xml b/sample-apps/blog-recommendation/src/main/application/search/query-profiles/types/root.xml
new file mode 100644
index 00000000000..6362bbe48ce
--- /dev/null
+++ b/sample-apps/blog-recommendation/src/main/application/search/query-profiles/types/root.xml
@@ -0,0 +1,3 @@
+<query-profile-type id="root" inherits="native">
+ <field name="ranking.features.query(user_item_cf)" type="tensor(user_item_cf[10])" />
+</query-profile-type>
diff --git a/sample-apps/blog-recommendation/src/main/application/searchdefinitions/blog_post.sd b/sample-apps/blog-recommendation/src/main/application/searchdefinitions/blog_post.sd
new file mode 100644
index 00000000000..1b92822e425
--- /dev/null
+++ b/sample-apps/blog-recommendation/src/main/application/searchdefinitions/blog_post.sd
@@ -0,0 +1,66 @@
+search blog_post {
+ document blog_post {
+ field date_gmt type string {
+ indexing: summary
+ }
+ field language type string {
+ indexing: summary
+ }
+
+ field author type string {
+ indexing: summary
+ }
+
+ field url type string {
+ indexing: summary
+ }
+
+ field title type string {
+ indexing: summary | index
+ }
+
+ field blog type string {
+ indexing: summary
+ }
+
+ field post_id type string {
+ indexing: summary
+ }
+
+ field tags type array<string> {
+ indexing: summary
+ }
+
+ field blogname type string {
+ indexing: summary
+ }
+
+ field content type string {
+ indexing: summary | index
+ }
+
+ field categories type array<string> {
+ indexing: summary | index
+ }
+
+ field user_item_cf type tensor {
+ indexing: summary | attribute
+ attribute: tensor(user_item_cf[10])
+ }
+
+ field has_user_item_cf type byte {
+ indexing: summary | attribute
+ attribute: fast-search
+ }
+
+ }
+
+ rank-profile tensor {
+ first-phase {
+ expression {
+ sum(query(user_item_cf) * attribute(user_item_cf))
+ }
+ }
+ }
+
+}
diff --git a/sample-apps/blog-recommendation/src/main/application/searchdefinitions/user.sd b/sample-apps/blog-recommendation/src/main/application/searchdefinitions/user.sd
new file mode 100644
index 00000000000..0c2a6e50aa6
--- /dev/null
+++ b/sample-apps/blog-recommendation/src/main/application/searchdefinitions/user.sd
@@ -0,0 +1,26 @@
+search user {
+ document user {
+
+ field user_id type string {
+ indexing: summary | attribute
+ attribute: fast-search
+ }
+
+ field has_read_items type array<string> {
+ indexing: summary | attribute
+ }
+
+ # see http://vespa.corp.yahoo.com/6/documentation/reference/search-definitions.html#tensor-type-spec
+ field user_item_cf type tensor {
+ indexing: summary | attribute
+ attribute: tensor(user_item_cf[10])
+ }
+
+ field has_user_item_cf type byte {
+ indexing: summary | attribute
+ attribute: fast-search
+ }
+
+ }
+
+}
diff --git a/sample-apps/blog-recommendation/src/main/application/services.xml b/sample-apps/blog-recommendation/src/main/application/services.xml
new file mode 100644
index 00000000000..d1650eaca8c
--- /dev/null
+++ b/sample-apps/blog-recommendation/src/main/application/services.xml
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<services version='1.0' xmlns:deploy="vespa" xmlns:preprocess="properties">
+
+ <jdisc id='default' version='1.0'>
+
+ <search>
+ <chain id='blog' inherits='vespa'>
+ <searcher bundle='recommendation' id='com.yahoo.example.BlogTensorSearcher' />
+ </chain>
+ <chain id='user' inherits='vespa'>
+ <searcher bundle='recommendation' id='com.yahoo.example.UserProfileSearcher' />
+ </chain>
+ <chain id='default' inherits='vespa'>
+ <searcher bundle='recommendation' id='com.yahoo.example.UserProfileSearcher' />
+ <searcher bundle='recommendation' id='com.yahoo.example.BlogTensorSearcher' />
+ </chain>
+ </search>
+ <document-api/>
+ <nodes count='1'/>
+ </jdisc>
+
+ <content id='content' version='1.0'>
+ <redundancy>1</redundancy>
+ <documents>
+ <document mode='index' type='blog_post'/>
+ <document mode='index' type='user'/>
+ </documents>
+ <nodes count='1'/>
+ <engine>
+ <proton>
+ <searchable-copies>1</searchable-copies>
+ </proton>
+ </engine>
+ </content>
+
+</services>
diff --git a/sample-apps/blog-recommendation/src/main/java/com/yahoo/example/BlogTensorSearcher.java b/sample-apps/blog-recommendation/src/main/java/com/yahoo/example/BlogTensorSearcher.java
new file mode 100644
index 00000000000..f9ff27c4813
--- /dev/null
+++ b/sample-apps/blog-recommendation/src/main/java/com/yahoo/example/BlogTensorSearcher.java
@@ -0,0 +1,43 @@
+package com.yahoo.example;
+
+import com.yahoo.prelude.query.IntItem;
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.Searcher;
+import com.yahoo.search.querytransform.QueryTreeUtil;
+import com.yahoo.search.searchchain.Execution;
+import com.yahoo.tensor.Tensor;
+
+public class BlogTensorSearcher extends Searcher {
+
+ @Override
+ public Result search(Query query, Execution execution) {
+
+ Object userItemCfProperty = query.properties().get("user_item_cf");
+ if (userItemCfProperty != null) {
+
+ // Modify the query by restricting to blog_posts...
+ query.getModel().setRestrict("blog_post");
+
+ // ... that has a tensor field fed
+ QueryTreeUtil.andQueryItemWithRoot(query, new IntItem(1, "has_user_item_cf"));
+
+ // Modify the ranking by using the 'tensor' rank-profile (as defined in blog_post.sd)...
+ query.properties().set(new CompoundName("ranking"), "tensor");
+
+ // ... and setting 'query(user_item_cf)' used in that rank-profile
+ query.getRanking().getFeatures().put("query(user_item_cf)", toTensor(userItemCfProperty));
+ }
+
+ return execution.search(query);
+ }
+
+ private Tensor toTensor(Object tensor) {
+ if (tensor instanceof Tensor) {
+ return (Tensor) tensor;
+ }
+ return Tensor.from(tensor.toString());
+ }
+
+}
diff --git a/sample-apps/blog-recommendation/src/main/java/com/yahoo/example/UserProfileSearcher.java b/sample-apps/blog-recommendation/src/main/java/com/yahoo/example/UserProfileSearcher.java
new file mode 100644
index 00000000000..63bf4110e67
--- /dev/null
+++ b/sample-apps/blog-recommendation/src/main/java/com/yahoo/example/UserProfileSearcher.java
@@ -0,0 +1,89 @@
+package com.yahoo.example;
+
+import com.yahoo.data.access.Inspectable;
+import com.yahoo.data.access.Inspector;
+import com.yahoo.prelude.query.WordItem;
+import com.yahoo.processing.request.CompoundName;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.Searcher;
+import com.yahoo.search.result.Hit;
+import com.yahoo.search.searchchain.Execution;
+import com.yahoo.search.searchchain.SearchChain;
+import com.yahoo.tensor.MapTensorBuilder;
+import com.yahoo.tensor.Tensor;
+
+import java.util.Iterator;
+import java.util.Map;
+
+public class UserProfileSearcher extends Searcher {
+
+ public Result search(Query query, Execution execution) {
+
+ Object userIdProperty = query.properties().get("user_id");
+ if (userIdProperty != null) {
+
+ // Retrieve user profile...
+ Tensor userProfile = retrieveUserProfile(userIdProperty.toString(), execution);
+
+ // ... and add user profile to query properties so BlogTensorSearcher can pick it up
+ query.properties().set(new CompoundName("user_item_cf"), userProfile);
+
+ if (query.isTraceable(9)) {
+ String tensorRepresentation = userProfile != null ? userProfile.toString() : "";
+ query.trace("Setting user profile to :" + tensorRepresentation, 9);
+ }
+ }
+
+ return execution.search(query);
+ }
+
+
+ private Tensor retrieveUserProfile(String userId, Execution execution) {
+ Query query = new Query();
+ query.getModel().setRestrict("user");
+ query.getModel().getQueryTree().setRoot(new WordItem(userId, "user_id"));
+ query.setHits(1);
+
+ SearchChain vespaChain = execution.searchChainRegistry().getComponent("vespa");
+ Result result = new Execution(vespaChain, execution.context()).search(query);
+
+ // This is needed to get the actual summary data
+ execution.fill(result);
+
+ Hit hit = getFirstHit(result);
+ if (hit != null) {
+ Object userItemCf = hit.getField("user_item_cf");
+ if (userItemCf instanceof Inspectable) {
+ return convertTensor((Inspectable) userItemCf);
+ }
+ }
+ return null;
+ }
+
+ private Hit getFirstHit(Result result) {
+ Iterator<Hit> hiterator = result.hits().deepIterator();
+ return hiterator.hasNext() ? hiterator.next() : null;
+ }
+
+ private Tensor convertTensor(Inspectable field) {
+ MapTensorBuilder tensorBuilder = new MapTensorBuilder();
+
+ Inspector cells = field.inspect().field("cells");
+ for (Inspector cell : cells.entries()) {
+ MapTensorBuilder.CellBuilder cellBuilder = tensorBuilder.cell();
+
+ Inspector address = cell.field("address");
+ for (Map.Entry<String, Inspector> entry : address.fields()) {
+ String dim = entry.getKey();
+ String label = entry.getValue().asString();
+ cellBuilder.label(dim, label);
+ }
+
+ Inspector value = cell.field("value");
+ cellBuilder.value(value.asDouble());
+ }
+ return tensorBuilder.build();
+ }
+
+}