Статьи

Работа со службой Bluemix Personality Insights

Продолжая играть с  IBM Bluemix , на этой неделе я провел некоторое время, играя с   сервисом Personality Insights . Этот сервис использует IBM Watson для анализа текстового ввода и определения личностных аспектов автора. Он фокусируется на трех областях анализа:

  • Определение  потребностей  автора. Эти потребности сужаются до двенадцати основных областей и оцениваются по шкале от 0 до 100 процентилей. Потребности: волнение, гармония, любопытство, идеал, близость, самовыражение, свобода, любовь, практичность, стабильность, вызов и структура.
  • Определение того, что  ценности  автора. Ватсон считает, что это важно для автора. Как и в случае с потребностями, ценности сосредоточены на наборе основных элементов: самопреодоление / помощь другим, сохранение / традиция, гедонизм / получение удовольствия от жизни, самосовершенствование / достижение успеха и открытость к изменениям / волнению.
  • Наконец, служба PI сообщает о «Большой пятерке» — это модель личности, которая пытается описать, как человек взаимодействует с миром.

Как вы можете себе представить, это довольно глубокие вещи, и, честно говоря, моя жена, которая работает над ее степенью социологии, вероятно, лучше понимала бы результаты. Вы можете просмотреть  полную документацию,  а также ознакомиться с  документацией по API для получения дополнительной информации.

Для моей демонстрации я решил попробовать что-нибудь интересное. Сервис PI работает лучше всего, когда в нем не менее 3500 слов. Типичное сообщение в блоге может содержать около пятисот слов, и, поскольку в типичном RSS-канале содержится десять элементов, я решил создать приложение, которое будет анализировать RSS-канал и пытаться определить личность автора. Я назвал это  Сканированием Личности Блога . Я дам ссылку на демо через минуту, но давайте сначала посмотрим на код.

Сначала мы рассмотрим файл app.js для приложения Node. Это довольно тривиально, поскольку есть только два просмотра — домашняя страница и API для отправки URL-адреса RSS.

/*jshint node:false */
/* global console,require */
var express = require('express');
var hbs = require('hbs');
var url = require('url');

hbs.registerHelper('raw-helper', function(options) {
  return options.fn();
});

var rssReader = require('./rssreader.js');
var insightsAPI = require('./insights.js');

// setup middleware
var app = express();
app.use(app.router);
app.use(express.errorHandler());
app.use(express.static(__dirname + '/public')); //setup static public directory
app.set('view engine', 'html');
app.engine('html', hbs.__express);
app.set('views', __dirname + '/views'); //optional since express defaults to CWD/views

// render index page
app.get('/', function(req, res){
	res.render('index');
});

app.get('/parse', function(req, res) {
	var url_parts = url.parse(req.url, true);
	var query = url_parts.query;
	if(!query.rss) {
		res.json({error:"Invalid data sent."});
		return;
	}
	rssReader.parse(query.rss, function(err,content) {
		if(err) {
			res.json(err);
		} else {
			console.log('bak with content, len is '+content.length);
			insightsAPI.parse(query.rss, query.rss, content, function(data) {
				console.log('back from IAPI');
				//console.log(JSON.stringify(data));
				res.json(data);	
			});
		}
	});
});

// There are many useful environment variables available in process.env.
// VCAP_APPLICATION contains useful information about a deployed application.
var appInfo = JSON.parse(process.env.VCAP_APPLICATION || "{}");
// TODO: Get application information and use it in your app.

// VCAP_SERVICES contains all the credentials of services bound to
// this application. For details of its content, please refer to
// the document or sample of each service.
if(process.env.VCAP_SERVICES) {
	var services = JSON.parse(process.env.VCAP_SERVICES || "{}");
	console.log(services);
	var apiUrl = services.personality_insights[0].credentials.url;
	var apiUsername = services.personality_insights[0].credentials.username;
	var apiPassword = services.personality_insights[0].credentials.password;
} else {
	var credentials = require('./credentials.json');
	var apiUrl = credentials.apiUrl;
	var apiUsername = credentials.apiUsername;
	var apiPassword = credentials.apiPassword;
}
insightsAPI.setAuth(apiUrl, apiUsername, apiPassword);
					
// The IP address of the Cloud Foundry DEA (Droplet Execution Agent) that hosts this application:
var host = (process.env.VCAP_APP_HOST || 'localhost');
// The port on the DEA for communication with the application:
var port = (process.env.VCAP_APP_PORT || 3000);
// Start server
app.listen(port, host);
console.log('App started on port ' + port);

Не очень захватывающе, и я не поделился бы этим, как обычно, но я специально хотел назвать те моменты, на которые смотрят process.env.VCAP_SERVICES. Вот как мое приложение собирает учетные данные API при работе в среде Bluemix.

За чтением RSS  отвечает  пакет NPM feedparser . Это тот же, который я использовал для ColdFusionBloggers.org . Я пропущу этот код, так как он не такой захватывающий.

Самое интересное происходит в коде, который используется для взаимодействия со службой PI:

var https = require('https');
var querystring = require('querystring');
var url = require('url');

var apiUsername;
var apiPassword;
var apiUrl;
var apiHost;
var apiPath;

function setAuth(apiurl, u, p) {
	apiUrl = apiurl;
	apiUsername=u;
	apiPassword=p;
	var parts = url.parse(apiUrl);
	apiHost = parts.host;
	apiPath = parts.pathname;
}

function sendInsights(user,source,input,cb) {
	//cb(fake);return;
	var data = {"contentItems":[]};
	var item = {};
	item.userid = user;
	item.sourceid = source;
	this.id = this.userid + '_'+this.sourceid;
	item.contenttype = "text/plain";
	//todo - remove html from input. the service does it, but we can do it ourselves
	item.language = "en";
	item.content = input;
	
	data.contentItems.push(item);
	
	var postData = JSON.stringify(data);
	
	
	var options = {
		host: apiHost,
		port: 443, 
		path: apiPath + "/v2/profile",
		headers: {
			'Authorization': 'Basic ' + new Buffer(apiUsername + ':' + apiPassword).toString('base64'),
			'Content-Type':'application/json',
			'Content-Length': Buffer.byteLength(postData)
		},
		method:"post"
	};
	console.log(options);
	var req = https.request(options, function(resp) {
		var body = "";
		resp.on("data", function(chunk) {
			body += chunk;
		});
		
		resp.on("end", function() {
			//console.log("done");console.log(body);
			cb(JSON.parse(body));
		});
		
	});
	req.write(postData);
	req.end();
	
};

var InsightAPI = {
	setAuth:setAuth,
	parse:sendInsights
};

module.exports = InsightAPI;

Хорошо, возможно, «захватывающий» немного. Честно говоря, это всего лишь HTTP-хит и JSON-ответ. Просто — но в этом-то и дело. Хороший сервис должен быть довольно простым в использовании.

Остальные просто представляли результаты. Ребята из Bluemix создали классное демо  с графиками и прочим  , но я решил сделать его простым и просто отобразить значения — отсортировано. Я использовал Handlebars, чтобы сделать его немного приятнее, и это меня немного смутило. Мне никогда не приходило в голову подумать, что произойдет, когда я использую шаблон Handlebars для клиентской части в представлении, которое запускается приложением Node.js, использующим Handlebars также на клиенте. Как вы можете догадаться, сначала это не сработало. Если вы посмотрите на этот первый список кода, вы увидите помощника с именем raw-helper. Мне нужно было добавить это, чтобы я мог использовать синтаксис Handlebar в моем представлении и чтобы сервер игнорировал его. Вот как это выглядит в index.html:

<script id="reportTemplate" type="text/x-handlebars-template">
	{{{{raw-helper}}}}
	<div class="row">
		<div class="col-md-4">
			<h2>Values</h2>
			{{#each values}}
			<div class="row">
				<div class="col-md-6"><strong>{{name}}</strong></div>
				<div class="col-md-6">{{perc percentage}}</div>
			</div>
			{{/each}}			
		</div>
		<div class="col-md-4">
			<h2>Needs</h2>
			{{#each needs}}
			<div class="row">
				<div class="col-md-6"><strong>{{name}}</strong></div>
				<div class="col-md-6">{{perc percentage}}</div>
			</div>
			{{/each}}
		</div>
		<div class="col-md-4">
			<h2>The Big 5</h2>
			{{#each big5}}
			<div class="row">
				<div class="col-md-6"><strong>{{name}}</strong></div>
				<div class="col-md-6">{{perc percentage}}</div>
			</div>
				{{#each children}}
					<div class="row">
						<div class="col-md-offset12 col-md-5 text-muted">{{name}}</div>
						<div class="col-md-6 text-muted">{{perc percentage}}</div>
					</div>
				{{/each}}

			{{/each}}
		</div>
	</div>
	{{{{/raw-helper}}}}
</script>

После того, как я заработал это, я в основном был в порядке, но потом я сделал глупое дерьмо, как добавление помощника в Node.js app.js, когда он мне действительно был нужен в клиентском app.js. Я, наверное, не должен был называть эти файлы одинаковыми. Так как же выглядят результаты? Я собираюсь дать ссылку на демо, конечно, но вот несколько примеров. Во-первых, мой собственный блог:

pi_ray

Далее Грубер  отважного огненного шара :

pi_df

И, наконец,  блог Сары Пэйлин :

pi_sp

Хотите попробовать сами? Проверьте демо здесь:  http://bloginsights.mybluemix.net/ . Вы можете увидеть весь исходный код проекта здесь:  https://github.com/cfjedimaster/bloginsights .