UpgradeAITranslation.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <?php
  2. namespace App\Console\Commands;
  3. use Illuminate\Console\Command;
  4. use Illuminate\Support\Facades\Log;
  5. use Illuminate\Support\Facades\Http;
  6. use App\Services\OpenAIService;
  7. use App\Services\AIModelService;
  8. use App\Services\SentenceService;
  9. use App\Services\SearchPaliDataService;
  10. use App\Http\Controllers\AuthController;
  11. use App\Models\PaliText;
  12. use App\Models\PaliSentence;
  13. use App\Models\Sentence;
  14. use App\Helpers\LlmResponseParser;
  15. use App\Http\Api\ChannelApi;
  16. use App\Tools\Tools;
  17. class UpgradeAITranslation extends Command
  18. {
  19. /**
  20. * The name and signature of the console command.
  21. * php artisan upgrade:ai.translation translation --book=141 --para=535
  22. * @var string
  23. */
  24. protected $signature = 'upgrade:ai.translation {type} {--book=} {--para=} {--resume} {--model=} ';
  25. /**
  26. * The console command description.
  27. *
  28. * @var string
  29. */
  30. protected $description = 'Command description';
  31. protected $sentenceService;
  32. protected $modelService;
  33. protected $openAIService;
  34. protected $model;
  35. protected $modelToken;
  36. protected $workChannel;
  37. protected $accessToken;
  38. /**
  39. * Create a new command instance.
  40. *
  41. * @return void
  42. */
  43. public function __construct(
  44. AIModelService $model,
  45. SentenceService $sent,
  46. OpenAIService $openAI
  47. ) {
  48. $this->modelService = $model;
  49. $this->sentenceService = $sent;
  50. $this->openAIService = $openAI;
  51. parent::__construct();
  52. }
  53. /**
  54. * Execute the console command.
  55. *
  56. * @return int
  57. */
  58. public function handle()
  59. {
  60. if ($this->option('model')) {
  61. $this->model = $this->modelService->getModelById($this->option('model'));
  62. $this->info("model:{$this->model['model']}");
  63. $this->modelToken = AuthController::getUserToken($this->model['uid']);
  64. }
  65. $this->workChannel = ChannelApi::getById($this->ask('请输入结果channel'));
  66. $books = [];
  67. if ($this->option('book')) {
  68. $books = [$this->option('book')];
  69. } else {
  70. $books = range(1, 217);
  71. }
  72. foreach ($books as $key => $book) {
  73. $maxParagraph = PaliText::where('book', $book)->max('paragraph');
  74. $paragraphs = range(1, $maxParagraph);
  75. if ($this->option('para')) {
  76. $paragraphs = [$this->option('para')];
  77. }
  78. foreach ($paragraphs as $key => $paragraph) {
  79. $this->info($this->argument('type') . " {$book}-{$paragraph}");
  80. $data = [];
  81. switch ($this->argument('type')) {
  82. case 'translation':
  83. $data = $this->aiPaliTranslate($book, $paragraph);
  84. break;
  85. case 'nissaya':
  86. $data = $this->aiNissayaTranslate($book, $paragraph);
  87. default:
  88. # code...
  89. break;
  90. }
  91. $this->save($data);
  92. }
  93. }
  94. return 0;
  95. }
  96. private function getPaliContent($book, $para)
  97. {
  98. $sentenceService = app(SearchPaliDataService::class);
  99. $sentences = PaliSentence::where('book', $book)
  100. ->where('paragraph', $para)
  101. ->orderBy('word_begin')
  102. ->get();
  103. if (!$sentences) {
  104. return null;
  105. }
  106. $json = [];
  107. foreach ($sentences as $key => $sentence) {
  108. $content = $sentenceService->getSentenceText($book, $para, $sentence->word_begin, $sentence->word_end);
  109. $id = "{$book}-{$para}-{$sentence->word_begin}-{$sentence->word_end}";
  110. $json[] = ['id' => $id, 'content' => $content['markdown']];
  111. }
  112. return $json;
  113. }
  114. private function aiPaliTranslate($book, $para)
  115. {
  116. $prompt = <<<md
  117. 你是一个巴利语翻译助手。
  118. pali 是巴利原文的一个段落,json格式, 每条记录是一个句子。包括id 和 content 两个字段
  119. 请翻译这个段落为简体中文。
  120. 翻译要求
  121. 1. 语言风格为现代汉语书面语,不要使用古汉语或者半文半白。
  122. 2. 译文严谨,完全贴合巴利原文,不要加入自己的理解
  123. 3. 巴利原文中的黑体字在译文中也使用黑体。其他标点符号跟随巴利原文,但应该替换为相应的汉字全角符号
  124. 输出格式jsonl
  125. 输出id 和 content 两个字段,
  126. id 使用巴利原文句子的id ,
  127. content 为中文译文
  128. 直接输出jsonl数据,无需解释
  129. **输出范例**
  130. {"id":"1-2-3-4","content":"译文"}
  131. {"id":"2-3-4-5","content":"译文"}
  132. md;
  133. $pali = $this->getPaliContent($book, $para);
  134. $originalText = "```json\n" . json_encode($pali, JSON_UNESCAPED_UNICODE) . "\n```";
  135. Log::debug($originalText);
  136. if (!$this->model) {
  137. Log::error('model is invalid');
  138. return [];
  139. }
  140. $startAt = time();
  141. $response = $this->openAIService->setApiUrl($this->model['url'])
  142. ->setModel($this->model['model'])
  143. ->setApiKey($this->model['key'])
  144. ->setSystemPrompt($prompt)
  145. ->setTemperature(0.0)
  146. ->setStream(false)
  147. ->send("# pali\n\n{$originalText}\n\n");
  148. $completeAt = time();
  149. $translationText = $response['choices'][0]['message']['content'] ?? '[]';
  150. Log::debug($translationText);
  151. $json = [];
  152. if (is_string($translationText)) {
  153. $json = LlmResponseParser::jsonl($translationText);
  154. }
  155. return $json;
  156. }
  157. private function aiWBW($book, $para) {}
  158. private function aiNissayaTranslate($book, $para)
  159. {
  160. $sysPrompt = <<<md
  161. 你是一个佛教翻译专家,精通巴利文和缅文
  162. ## 翻译要求:
  163. - 请将nissaya单词表中的巴利文和缅文分别翻译为中文
  164. - 输入格式为 巴利文:缅文
  165. - 一行是一条记录,翻译的时候,请不要拆分一行中的巴利文单词或缅文单词,一行中出现多个单词的,一起翻译
  166. - 输出csv格式内容,分隔符为"$",
  167. - 字段如下:巴利文\$巴利文的中文译文\$缅文\$缅文的中文译文 #两个译文的语义相似度(%)
  168. **范例**:
  169. pana\$然而\$ဝါဒန္တရကား\$教义之说 #60%
  170. 直接输出csv, 无需其他内容
  171. 用```包裹的行为注释内容,也需要翻译和解释。放在最后面。如果没有```,无需处理
  172. md;
  173. $sentences = Sentence::nissaya()
  174. ->language('my') // 过滤缅文
  175. ->where('book_id', $book)
  176. ->where('paragraph', $para)
  177. ->orderBy('strlen')
  178. ->get();
  179. $result = [];
  180. foreach ($sentences as $key => $sentence) {
  181. $nissaya = [];
  182. $rows = explode("\n", $sentence->content);
  183. foreach ($rows as $key => $row) {
  184. if (strpos('=', $row) >= 0) {
  185. $factors = explode("=", $row);
  186. $nissaya[] = Tools::MyToRm($factors[0]) . ':' . end($factors);
  187. } else {
  188. $nissaya[] = $row;
  189. }
  190. }
  191. $nissayaText = json_encode(implode("\n", $nissaya), JSON_UNESCAPED_UNICODE);
  192. Log::debug($nissayaText);
  193. $startAt = time();
  194. $response = $this->openAIService->setApiUrl($this->model['url'])
  195. ->setModel($this->model['model'])
  196. ->setApiKey($this->model['key'])
  197. ->setSystemPrompt($sysPrompt)
  198. ->setTemperature(0.7)
  199. ->setStream(false)
  200. ->send("# nissaya\n\n{$nissayaText}\n\n");
  201. $complete = time() - $startAt;
  202. $content = $response['choices'][0]['message']['content'] ?? '';
  203. Log::debug("ai response in {$complete}s content=" . $content);
  204. $id = "{$sentence->book_id}-{$sentence->paragraph}-{$sentence->word_start}-{$sentence->word_end}";
  205. $result[] = [
  206. 'id' => $id,
  207. 'content' => $content,
  208. ];
  209. }
  210. return $result;
  211. }
  212. private function save($data)
  213. {
  214. //写入句子库
  215. $sentData = [];
  216. $sentData = array_map(function ($n) {
  217. $sId = explode('-', $n['id']);
  218. return [
  219. 'book_id' => $sId[0],
  220. 'paragraph' => $sId[1],
  221. 'word_start' => $sId[2],
  222. 'word_end' => $sId[3],
  223. 'channel_uid' => $this->workChannel['id'],
  224. 'content' => $n['content'],
  225. 'content_type' => $n['content_type'] ?? 'markdown',
  226. 'lang' => $this->workChannel['lang'],
  227. 'status' => $this->workChannel['status'],
  228. 'editor_uid' => $this->model['uid'],
  229. ];
  230. }, $data);
  231. foreach ($sentData as $key => $value) {
  232. $this->sentenceService->save($value);
  233. }
  234. }
  235. }