DeepSeekService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. <?php
  2. namespace App\Services;
  3. use thiagoalessio\TesseractOCR\TesseractOCR;
  4. /**
  5. * DeepSeek服务管理-服务类
  6. * @author laravel开发员
  7. * @since 2020/11/11
  8. * @package App\Services
  9. */
  10. class DeepSeekService extends BaseService
  11. {
  12. // 静态对象
  13. protected static $instance = null;
  14. protected $debug = true;
  15. protected $expireTime = 7200; // 缓存日志时长
  16. protected $apiKey = '';
  17. protected $apiName = '';
  18. protected $apiurl = '';
  19. protected $chaturl = 'https://chat.deepseek.com/api';
  20. // 接口地址
  21. protected $apiUrls = [
  22. // 授权登录
  23. 'deepseek-chat' => '/chat/completions',
  24. 'deepseek-reasoner' => '/chat/completions',
  25. 'upload' => '/v1/upload',
  26. 'generations' => '/v1/images/generations',
  27. 'generate' => '/v1/text/generate',
  28. 'uploadFile' => '/files',
  29. ];
  30. public function __construct()
  31. {
  32. set_time_limit(0);
  33. $this->apiUrl = ConfigService::make()->getConfigByCode('dk_base_url');
  34. $this->apiKey = ConfigService::make()->getConfigByCode('dk_api_key');
  35. $this->apiName = ConfigService::make()->getConfigByCode('dk_api_name');
  36. }
  37. /**
  38. * 静态入口
  39. * @return static|null
  40. */
  41. public static function make()
  42. {
  43. if (!self::$instance) {
  44. self::$instance = new static();
  45. }
  46. return self::$instance;
  47. }
  48. /**
  49. * AI 分析接口
  50. * @param $params
  51. * @param string $model
  52. * @return array|false|mixed
  53. */
  54. public function apiRequest($params, $model='deepseek-chat')
  55. {
  56. if(empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiName)){
  57. $this->error = 'AI接口参数未配置';
  58. return false;
  59. }
  60. $headers = [
  61. 'Content-Type: application/json',
  62. 'Accept: application/json',
  63. 'Authorization: Bearer ' . $this->apiKey
  64. ];
  65. $answer = isset($params['answer'])? $params['answer'] : '';
  66. $topic = isset($params['topic'])? $params['topic'] : '';
  67. $score = isset($params['score'])? $params['score'] : 0;
  68. //$type = isset($params['type'])? $params['type'] : 1; // 1-文本,2-图片
  69. $content = '你是一个答题高手,请给描述中问题的答案评分,并尝试按 JSON:{"score": 8,"topic":"this is topic content","analysis":"this is topic analysis"}返回';
  70. $message = "请给题目【{$topic}】总分{$score}的答案【{$answer}】评分?";
  71. $format = 'json_object';
  72. $data = [
  73. 'model'=> $model,
  74. 'messages'=> [
  75. [
  76. 'role' => 'system',
  77. 'content' => $content
  78. ],
  79. [
  80. 'role' => 'user',
  81. 'content' => $message
  82. ],
  83. ],
  84. 'stream' => false, //false 非流 true//流返回,需要前端追加和保持长连接
  85. 'response_format'=>[
  86. 'type'=> $format //返回格式(text,json_object)
  87. ],
  88. "max_tokens"=>2048, //最大返回token数
  89. ];
  90. $url = $this->apiUrl.$this->apiUrls[$model];
  91. $this->saveLog("caches:dkApi:{$model}:request_1_".date('YmdHis'), ['url'=>$url,'data'=>$data]);
  92. $result = aiRequest($url, json_encode($data), 60, $headers);
  93. $result = $result? json_decode(trim($result),true) : [];
  94. if(empty($result)){
  95. $this->error = '答案验证失败';
  96. return false;
  97. }
  98. $this->saveLog("caches:dkApi:{$model}:request_2_".date('YmdHis'), ['url'=>$url,'data'=>$data,'result'=>$result]);
  99. $choices = isset($result['choices'])? $result['choices'] : [];
  100. $choiceData = isset($choices[0]['message'])? $choices[0]['message'] : [];
  101. $choiceContent = isset($choiceData['content'])? $choiceData['content'] : '';
  102. $content = $choiceContent? str_replace('\n','', $choiceContent) : '';
  103. $content = $content? preg_replace("/^```json/",'', $content) : '';
  104. $content = $content? rtrim($content,'```') : '';
  105. $content = $content? json_decode($content, true) : [];
  106. return $content;
  107. }
  108. /**
  109. * 文件上传和处理(支持图像和文档)
  110. */
  111. public function uploadAndProcessFile($filePath, $prompt = '请处理这个文件') {
  112. // 首先上传文件获取文件ID
  113. $fileId = $this->uploadFile($filePath);
  114. if (!$fileId) {
  115. return ['error' => '文件上传失败'];
  116. }
  117. // 使用文件ID进行对话
  118. $messages = [
  119. [
  120. 'role' => 'user',
  121. 'content' => $prompt,
  122. 'file_ids' => [$fileId]
  123. ]
  124. ];
  125. return $this->chat($messages);
  126. }
  127. /**
  128. * 上传文件到DeepSeek
  129. */
  130. public function uploadFile($imageUrl) {
  131. $uploadUrl = 'http://127.0.5.12/api/upload/image';
  132. // $uploadUrl = $this->apiUrl.$this->apiUrls['uploadFile'];
  133. $filePath = ATTACHMENT_PATH.get_image_path($imageUrl);
  134. if (!file_exists($filePath)) {
  135. return false;
  136. }
  137. $mimeType = mime_content_type($filePath);
  138. $fileName = basename($filePath);
  139. // 准备文件数据
  140. $fileData = [
  141. 'file' => new \CURLFile($filePath, $mimeType, $fileName),
  142. 'purpose' => 'vision' // 或者 'assistants',根据用途
  143. ];
  144. $ch = curl_init();
  145. curl_setopt_array($ch, [
  146. CURLOPT_URL => $uploadUrl,
  147. CURLOPT_RETURNTRANSFER => true,
  148. CURLOPT_POST => true,
  149. CURLOPT_POSTFIELDS => $fileData,
  150. CURLOPT_HTTPHEADER => [
  151. 'Authorization: Bearer ' . $this->apiKey
  152. ],
  153. CURLOPT_TIMEOUT => 30
  154. ]);
  155. $response = curl_exec($ch);
  156. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  157. dump($response);
  158. curl_close($ch);
  159. if ($httpCode === 200) {
  160. $result = json_decode($response, true);
  161. return $result['id'] ?? false;
  162. }
  163. return false;
  164. }
  165. /**
  166. * 上传图片
  167. * @param $image
  168. * @return false
  169. */
  170. public function analyzeImageByFile($fileIds, $prompt='请分析读取这张图片内容')
  171. {
  172. if(empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiName)){
  173. $this->error = 'AI接口参数未配置';
  174. return false;
  175. }
  176. $headers = [
  177. 'Content-Type: application/json',
  178. 'Accept: application/json',
  179. 'Authorization: Bearer ' . $this->apiKey
  180. ];
  181. // $filePath = ATTACHMENT_PATH.get_image_path($imageUrl);
  182. // $imageData = base64_encode(file_get_contents($filePath));
  183. // $mineType = mime_content_type($filePath);
  184. $data = [
  185. 'model'=> 'deepseek-reasoner',
  186. 'messages'=> [
  187. [
  188. 'role' => 'user',
  189. 'content' => $prompt,
  190. 'file_ids'=> ['file-bc202f5a-0894-478c-b22c-c79c9f8b8298']
  191. ]
  192. ],
  193. // 'stream' => false, //false 非流 true//流返回,需要前端追加和保持长连接
  194. // 'response_format'=>[
  195. // 'type'=> 'text' //返回格式(text,json_object)
  196. // ],
  197. "max_tokens"=>2048, //最大返回token数
  198. ];
  199. $model = 'deepseek-reasoner';
  200. $url = $this->apiUrl.$this->apiUrls[$model];
  201. $this->saveLog("caches:dkApi:{$model}:request_1_".date('YmdHis'), ['url'=>$url,'data'=>$data]);
  202. $result = aiRequest($url, json_encode($data), 60, $headers);
  203. dump($result);
  204. var_dump($result);
  205. }
  206. /**
  207. * 上传Base64图片
  208. * @param $image
  209. * @return false
  210. */
  211. public function analyzeImageByBase64($imageUrl, $prompt='请分析读取这张图片内容')
  212. {
  213. if(empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiName)){
  214. $this->error = 'AI接口参数未配置';
  215. return false;
  216. }
  217. $headers = [
  218. 'Content-Type: application/json',
  219. 'Accept: application/json',
  220. 'Authorization: Bearer ' . $this->apiKey
  221. ];
  222. $filePath = ATTACHMENT_PATH.get_image_path($imageUrl);
  223. $imageData = base64_encode(file_get_contents($filePath));
  224. $mineType = mime_content_type($filePath);
  225. $data = [
  226. 'purpose'=>'vision',
  227. 'file'=> $imageData,
  228. 'filename'=> basename($filePath),
  229. 'mine_type'=> $mineType
  230. ];
  231. $model = 'uploadFile';
  232. $url = $this->apiUrl.$this->apiUrls['uploadFile'];
  233. $this->saveLog("caches:dkApi:{$model}:request_1_".date('YmdHis'), ['url'=>$url,'data'=>$data]);
  234. $result = aiRequest($url, json_encode($data), 60, $headers);
  235. dump($result);
  236. var_dump($result);
  237. }
  238. /**
  239. * 上传图片
  240. * @param $image
  241. * @return false
  242. */
  243. public function analyzeImage($imageUrl, $prompt='请分析读取这张图片内容')
  244. {
  245. if(empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiName)){
  246. $this->error = 'AI接口参数未配置';
  247. return false;
  248. }
  249. $headers = [
  250. 'Content-Type: application/json',
  251. 'Accept: application/json',
  252. 'Authorization: Bearer ' . $this->apiKey
  253. ];
  254. $filePath = ATTACHMENT_PATH.get_image_path($imageUrl);
  255. $imageData = base64_encode(file_get_contents($filePath));
  256. $mineType = mime_content_type($filePath);
  257. $data = [
  258. 'model'=> 'deepseek-reasoner',
  259. 'messages'=> [
  260. [
  261. 'role' => 'user',
  262. 'content' => [
  263. [
  264. 'type'=>'text',
  265. 'text'=> $prompt
  266. ],
  267. [
  268. 'type'=>'image_url',
  269. 'image_url'=> [
  270. 'url'=>"data:{$mineType};base64,{$imageData}"
  271. ]
  272. ]
  273. ]
  274. ]
  275. ],
  276. // 'stream' => false, //false 非流 true//流返回,需要前端追加和保持长连接
  277. // 'response_format'=>[
  278. // 'type'=> 'text' //返回格式(text,json_object)
  279. // ],
  280. "max_tokens"=>2048, //最大返回token数
  281. ];
  282. $model = 'deepseek-reasoner';
  283. $url = $this->apiUrl.$this->apiUrls[$model];
  284. $this->saveLog("caches:dkApi:{$model}:request_1_".date('YmdHis'), ['url'=>$url,'data'=>$data]);
  285. $result = aiRequest($url, json_encode($data), 60, $headers);
  286. dump($result);
  287. var_dump($result);
  288. }
  289. /**
  290. * 生成图片
  291. * @param $image
  292. * @return false
  293. */
  294. public function makeImage($prompt)
  295. {
  296. if(empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiName)){
  297. $this->error = 'AI接口参数未配置';
  298. return false;
  299. }
  300. $headers = [
  301. 'Content-Type: application/json',
  302. 'Accept: application/json',
  303. 'Authorization: Bearer ' . $this->apiKey
  304. ];
  305. $url = $this->apiUrl.$this->apiUrls['deepseek-chat'];
  306. $data = [
  307. 'prompt'=> $prompt,
  308. 'model'=>'deepseek-image',
  309. 'size'=> '1024x1024',
  310. ];
  311. $result = aiRequest($url, json_encode($data), 10, $headers);
  312. var_dump($result);
  313. }
  314. /**
  315. * 获取题目内容
  316. * @param $imageUrl
  317. * @return false
  318. */
  319. public function getImageTopicData($imageUrl)
  320. {
  321. if(empty($imageUrl)){
  322. return false;
  323. }
  324. // 图片获取数据
  325. if(preg_match("/(images|temp)/", $imageUrl)){
  326. $path = get_image_path($imageUrl);
  327. $ocr = new TesseractOCR(ATTACHMENT_PATH.$path);
  328. $data = $ocr->psm(3)
  329. ->horc()
  330. ->lang('chi_sim','eng','chi_tra')
  331. ->run(60);
  332. return $data?str_replace("\n",'\n', $data):'';
  333. }
  334. return $imageUrl;
  335. }
  336. /**
  337. * 保存缓存日志
  338. * @param $cacheKey
  339. * @param $message
  340. * @param $expireTime
  341. */
  342. public function saveLog($cacheKey, $message, $expireTime=0, $open=false)
  343. {
  344. if(env('APP_DEBUG') || $open){
  345. RedisService::set($cacheKey, $message, $expireTime||$this->expireTime);
  346. }
  347. }
  348. }