DeepSeekService.php 14 KB

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