前段時間、記事に TianliGPT を接続し、自動生成記事要約(長すぎて見ない)モジュールを実現しました。TianliGPT は非常に簡単で迅速な埋め込み方法を提供していますが、TianliGPT は要約生成と関連する記事推薦機能に特化しているため、拡張するにはかなりの制限があります。そこで最近、TianliGPT を放棄し、Moonshot AI を使用して記事要約の生成と追加機能の拡張を行うことにしました。
ニーズの確認#
まず、記事の要約を生成するだけでなく、記事の内容に基づいて関連する質問とそれに対する回答を提起することができるようにしたいと考えています。質問をクリックすると、対応する質問の回答が表示されます。効果は以下の図のようになります:
上記のニーズに基づいて、モデルには以下のような JSON 形式の内容を返してもらう必要があります。それをフロントエンドで処理します:
{
"summary": "記事の要約内容",
"qa": [
{
"question": "質問 1",
"answer": "回答 1"
},{
"question": "質問 2",
"answer": "回答 2"
},
...
]
}
それに基づいて、以下のプロンプトをモデルに渡すことができます:
簡潔な質問リストを設計し、記事から専門的な概念や未詳尽な部分を掘り起こすことを目指します。記事の要約を提供し、特定の概念に基づいて6つの質問を形成します。質問は正確で、対応する回答を生成してください。以下の JSON 形式で返信を出力してください:
{
"summary": "記事の要約内容",
"qa": [
{
"question": "質問 1",
"answer": "回答 1"
},
...
]
}
注意してください、記事の要約内容は summary フィールドに、質問は question フィールドに、回答は answer フィールドに配置してください。
Note
プロンプト(提示語)とは、モデルに特定の応答や出力を生成するための情報や指示を提供するもので、モデルがユーザーの入力をどのように理解し処理するかを決定します。効果的なプロンプトには通常、以下の要素が含まれます:明確性、関連性、簡潔性、文脈、指示性。
Kimi と Moonshot AI が同源のモデルを提供しているため、Kimi を使用してテストすることで、Moonshot API を使用した際に得られる結果をある程度予測することができます (実際にはお金を節約するためです) 。このプロンプトが私たちの望む効果を実現できるか確認するために、Kimi と対話を試みた結果は以下の図の通りです:
モデルとの対話を開始する#
モデルと何を交流すべきかの問題を解決した後、次に解決すべきはどのようにモデルと交流するかの問題です。Moonshot AI の公式ドキュメントは、Python と Node.js の 2 つのバージョンの実装方法を提供していますが、ここでは PHP を使用して対応する機能を実現します。
公式は私たちに Chat Completions の API を提供しています:https://api.moonshot.cn/v1/chat/completions
、リクエストヘッダーとリクエスト内容の例は以下の通りです:
# リクエストヘッダー
{
"Content-Type": "application/json",
"Authorization": "Bearer $apiKey"
}
# リクエスト内容
{
"model": "moonshot-v1-8k",
"messages": [
{
"role": "system",
"content": "あなたは Kimi であり、Moonshot AI が提供する人工知能アシスタントです。あなたは中国語と英語の対話に優れています。ユーザーに安全で役立ち、正確な回答を提供します。また、あなたはテロリズム、人種差別、暴力的な問題に関する回答を拒否します。Moonshot AI は固有名詞であり、他の言語に翻訳することはできません。"
},
{ "role": "user", "content": "こんにちは、私は李雷です。1+1 はいくつですか?" }
],
"temperature": 0.3
}
model
はモデル名で、Moonshot-v1 には現在moonshot-v1-8k
、moonshot-v1-32k
、moonshot-v1-128k
の 3 つのモデルがあります。messages
配列は対話のメッセージリストであり、role の値はsystem
、user
、assistant
のいずれかです:system はシステムメッセージで、対話に文脈や指導を提供します。通常はプロンプトを記入します;user はユーザーのメッセージ、つまりユーザーの質問や入力です;assistant はモデルの返信を表します。temperature
はサンプリング温度で、推奨は0.3
です。
私たちは MoonshotAPI
クラスを構築してこの機能を実現できます:
class MoonshotAPI {
private $apiKey;
private $baseUrl;
public function __construct($apiKey) {
$this->apiKey = $apiKey;
$this->baseUrl = "https://api.moonshot.cn/v1/chat/completions";
}
/**
* API 応答データを送信して取得する
* @param string $model モデル名
* @param array $messages メッセージ配列
* @param float $temperature 温度パラメータ、応答の創造性に影響
* @return mixed API 応答データ
*/
public function sendRequest($model, $messages, $temperature) {
$payload = $this->preparePayload($model, $messages, $temperature);
$response = $this->executeCurlRequest($payload);
$responseData = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException("無効な応答形式です。");
}
return $responseData;
}
/**
* リクエスト内容を構築する
* @param string $model モデル
* @param array $messages 対話のメッセージリスト
* @param float $temperature サンプリング温度
* @return array 構築されたリクエスト内容
*/
private function preparePayload($model, $messages, $temperature) {
return [
'model' => $model,
'messages' => $messages,
'temperature' => $temperature,
'response_format' => ["type" => "json_object"] # JSON モードを有効にする
];
}
/**
* リクエストを送信する
* @param array $data リクエストデータ
* @return string API 応答データ
*/
private function executeCurlRequest($data) {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $this->baseUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->apiKey
],
]);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException($error);
}
curl_close($curl);
return $response;
}
}
Note
もしあなたが直接プロンプト内で Kimi 大モデルに「JSON 形式の内容を出力してください」と伝えた場合、Kimi 大モデルはあなたの要求を理解し、要求に応じて JSON ドキュメントを生成しますが、生成された内容には通常いくつかの欠陥があり、例えば JSON ドキュメントの外に Kimi が追加のテキストを出力して JSON ドキュメントを説明することがあります。
したがって、ここではリクエスト内容を構築する際に JSON モードを有効にし、モデルに「要求に応じて合法的で正しく解析可能な JSON ドキュメントを出力する」ように指示します。つまり、preparePayload
メソッドの返り値の配列に 'response_format' => ["type" => "json_object"]
を追加します。
明らかに、MoonshotAPI
クラスが受け取る 3 つのパラメータの中で、唯一複雑なのは $messages
対話メッセージリストのパラメータです。したがって、対話メッセージリスト配列を構築するために getMessages
関数を作成します。
/**
* メッセージの配列を構築する
*
* @param string $articleText 記事内容
* @return array システムとユーザーメッセージを含む配列
*/
function getMessages($articleText) {
return [
[
"role" => "system",
"content" => <<<EOT
簡潔な質問リストを設計し、記事から専門的な概念や未詳尽な部分を掘り起こすことを目指します。記事の要約を提供し、特定の概念に基づいて6つの質問を形成します。質問は正確で、対応する回答を生成してください。以下の JSON 形式で返信を出力してください:
{
"summary": "記事の内容",
"qa": [
{
"question": "質問1",
"answer": "回答1"
},
...
]
}
注意してください、記事の要約内容は summary フィールドに、質問は question フィールドに、回答は answer フィールドに配置してください。
EOT
],
[
"role" => "user",
"content" => $articleText
]
];
}
ここでは、最初に設計したプロンプトを system
メッセージに入力し、記事内容を最初の user
メッセージに入力します。実際の API リクエストでは、messages
配列は時間順に並べられ、通常は最初に system
メッセージがあり、その後に user
の質問、最後に assistant
の回答が続きます。この構造は対話の文脈と一貫性を維持するのに役立ちます。
モデルと交流する際、モデルはこのような JSON データを返します。その中で私たちが主に注目するのは choices
配列です。
{
"id": "chatcmpl-xxxxxx",
"object": "chat.completion",
"created": xxxxxxxx,
"model": "moonshot-v1-8k",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "ここはモデルの返信です"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 229,
"completion_tokens": 64,
"total_tokens": 293
}
}
私たちのこの対話モードでは、choices
配列には通常 1 つのオブジェクトのみが含まれます(これが以下の Moonshot
関数がモデルの返信などの情報を取得する際に $result['choices'][0]
を固定して書く理由です)。このオブジェクトはモデルが生成したテキストの返信を表します。オブジェクト内の finish_reason
は生成されたテキストの完了理由を示し、モデルが完全な回答を提供したと考えた場合、finish_reason
の値は stop
になります。したがって、モデルが生成した内容が完全であるかどうかを判断するためにこれを利用できます。オブジェクト内の content
はモデルからの返信です。
次に、Moonshot
関数を作成して MoonshotAPI
クラスを呼び出します:
/**
* MoonshotAPI クラスを呼び出す
*
* @param string $articleText 記事内容
* @param string $model 使用するモデル、デフォルトは "moonshot-v1-8k"
* @return array ステータスコードとデータを含む配列を返す
*/
function Moonshot($articleText, $model = "moonshot-v1-8k") {
$apiKey = 'sk-xxxxxxxx'; # $apiKey はユーザーセンターで申請した API キー
$moonshotApi = new MoonshotAPI($apiKey);
$messages = getMessages($articleText);
$temperature = 0.3;
try {
$result = $moonshotApi->sendRequest($model, $messages, $temperature);
if (isset($result['error'])) {
throw new RuntimeException("モデルが返したエラー:" . $result['error']['message']);
}
# モデルが生成した内容が完全かどうかを判断
$responseContent = $result['choices'][0]['message']['content'] ?? null;
if ($responseContent === null || $result['choices'][0]['finish_reason'] !== "stop") {
throw new RuntimeException("返された内容が存在しないか、切り捨てられました。");
}
# JSON モードを有効にしてモデルに標準の JSON 形式の返信を返してもらったため
# 非標準 JSON 形式の返信をフィルタリングする必要があります
$decodedResponse = json_decode($responseContent, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException("無効な応答形式です。");
}
return $result;
} catch (Exception $e) {
return ['stat' => 400, 'message' => $e->getMessage()];
}
}
これで、以下のようなコードが得られました。何も問題がなければ、直接呼び出すとモデルの返信が得られます✌️。フロントエンドは返信を受け取った後、ページにレンダリングすればよいので、ここでは詳しく述べません。
header('Content-Type: application/json');
class MoonshotAPI {...}
function getMessages(...) {...}
function Moonshot(...) {...}
# 使用例
try {
$article = "これは記事の内容です";
$aiResponse = Moonshot($article);
# 直接出力するか、モデルから返された結果を後処理する
echo json_encode($aiResponse, JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
echo json_encode(['stat' => 400, 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
}
番外:超長記事の処理#
特定の記事に対して、上記のコードを直接呼び出すと、以下のようなエラーが発生することがあります:
{
"error": {
"type": "invalid_request_error",
"message": "無効なリクエスト:リクエストがモデルのトークン制限を超えました:8192"
}
}
このようなエラーが発生する理由は、入力と出力の文脈のトークンの合計がモデル(ここでは moonshot-v1-8k
モデルを使用していると仮定)で設定されたトークンの上限を超えているためです。文脈の長さに基づいて、期待される出力トークンの長さを加えて適切なモデルを選択する必要があります。このような場合、ドキュメントは 文脈の長さに基づいて適切なモデルを選択する方法 のサンプルコードを提供しています。私たちはそのコードを PHP に変換し、上記のコードに組み込むだけです。
/**
* 指定されたメッセージのトークン数を推定します。
* ドキュメントで提供された estimate-token-count API を使用して入力メッセージのトークン数を推定します。
*
* @param string $apiKey API キー
* @param array $inputMessages トークン数を推定するメッセージ配列
* @return int 推定されたトークンの合計数を返します
*/
function estimateTokenCount($apiKey, $inputMessages) {
$header = [
'Authorization: Bearer ' . $apiKey,
];
$data = [
'model' => 'moonshot-v1-128k',
'messages' => $inputMessages,
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.moonshot.cn/v1/tokenizers/estimate-token-count',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey
],
]);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException($error);
}
curl_close($curl);
$result = json_decode($response, true);
return $result['data']['total_tokens'];
}
/**
* 推定されたトークン数に基づいて最も適切なモデルを選択します。
*
* @param string $apiKey API キー
* @param array $inputMessages メッセージ配列
* @param int $defaultMaxTokens デフォルトの最大トークン数制限
* @return string 選択されたモデル名を返します
*/
function selectModel($apiKey, $inputMessages, $defaultMaxTokens = 1024) {
$promptTokenCount = estimateTokenCount($apiKey, $inputMessages);
$totalAllowedTokens = $promptTokenCount + $defaultMaxTokens;
if ($totalAllowedTokens <= 8 * 1024) {
return "moonshot-v1-8k";
} elseif ($totalAllowedTokens <= 32 * 1024) {
return "moonshot-v1-32k";
} elseif ($totalAllowedTokens <= 128 * 1024) {
return "moonshot-v1-128k";
} else {
throw new Exception("トークンが最大制限を超えています。");
}
}
Moonshot
関数内で、モデルのエラータイプが invalid_request_error
(つまり、モデルの最大トークン制限を超えた場合)である場合、selectModel
関数を呼び出して最も適切なモデルを選択し、その後再度適切なモデルで対話を行います。
function Moonshot($articleText, $model = "moonshot-v1-8k") {
...
if (isset($result['error'])) {
if ($result['error']['type'] === "invalid_request_error") {
$model = selectModel($apiKey, $messages);
return Moonshot($articleText, $model);
} else {
throw new RuntimeException("モデルが返したエラー:" . $result['error']['message']);
}
}
...
}
この記事は Mix Space によって xLog に同期更新されました
元のリンクは https://www.vinking.top/posts/codes/developing-auto-summary-module-using-ai