Llm.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. namespace app\admin\controller\ps;
  3. use app\common\controller\Backend;
  4. use app\common\library\ps\LlmClient;
  5. use think\Db;
  6. /**
  7. * 大模型设置:线上拆版 + 本地简单任务
  8. *
  9. * @icon fa fa-cloud
  10. */
  11. class Llm extends Backend
  12. {
  13. protected $noNeedRight = [];
  14. /**
  15. * @var array
  16. */
  17. protected $keys = [
  18. 'llm_online_enable',
  19. 'llm_online_provider',
  20. 'llm_online_base_url',
  21. 'llm_online_api_key',
  22. 'llm_online_model',
  23. 'llm_online_vision_model',
  24. 'llm_online_timeout',
  25. 'llm_base_url',
  26. 'llm_api_key',
  27. 'llm_timeout',
  28. 'llm_text_model',
  29. 'llm_vision_model',
  30. 'llm_vision_complex',
  31. ];
  32. public function index()
  33. {
  34. if ($this->request->isPost()) {
  35. $row = $this->request->post('row/a');
  36. if (!is_array($row)) {
  37. $this->error('参数错误');
  38. }
  39. $oldKey = (string)Db::name('config')->where('name', 'llm_online_api_key')->value('value');
  40. $postedKey = isset($row['llm_online_api_key']) ? trim((string)$row['llm_online_api_key']) : '';
  41. if ($postedKey === '') {
  42. $row['llm_online_api_key'] = $oldKey;
  43. }
  44. $row['llm_online_enable'] = !empty($row['llm_online_enable']) && $row['llm_online_enable'] !== '0' ? '1' : '0';
  45. foreach ($this->keys as $name) {
  46. if (!array_key_exists($name, $row)) {
  47. continue;
  48. }
  49. $exists = Db::name('config')->where('name', $name)->find();
  50. if ($exists) {
  51. Db::name('config')->where('name', $name)->update(['value' => (string)$row[$name]]);
  52. }
  53. }
  54. $this->writeSiteFile();
  55. $this->success('已保存。拆版将优先使用线上视觉模型,本地 Ollama 仍用于简单任务。');
  56. }
  57. $site = [];
  58. foreach ($this->keys as $name) {
  59. $site[$name] = (string)Db::name('config')->where('name', $name)->value('value');
  60. }
  61. $this->view->assign('site', $site);
  62. $this->view->assign('keySaved', $site['llm_online_api_key'] !== '');
  63. $this->view->assign('providers', [
  64. 'dashscope' => '阿里云通义(推荐,qwen-vl-max)',
  65. 'openai' => 'OpenAI',
  66. 'moonshot' => '月之暗面 Kimi',
  67. 'zhipu' => '智谱 GLM',
  68. 'siliconflow' => '硅基流动',
  69. 'volcengine' => '火山方舟',
  70. 'deepseek' => 'DeepSeek(仅文本,不能拆版)',
  71. 'custom' => '自定义 OpenAI 兼容接口',
  72. ]);
  73. return $this->view->fetch();
  74. }
  75. /**
  76. * 测试本地 Ollama
  77. */
  78. public function testLocal()
  79. {
  80. $row = $this->request->post('row/a', []);
  81. try {
  82. $client = new LlmClient([
  83. 'profile' => 'local',
  84. 'base_url' => $row['llm_base_url'] ?? '',
  85. 'timeout' => (int)($row['llm_timeout'] ?? 30),
  86. ]);
  87. $models = $client->listModels();
  88. $msg = '本地 Ollama 已连通,共 ' . count($models) . ' 个模型';
  89. if ($models) {
  90. $msg .= ':' . implode('、', array_slice($models, 0, 8));
  91. }
  92. $this->success($msg, null, [
  93. 'models' => array_slice($models, 0, 30),
  94. ]);
  95. } catch (\Exception $e) {
  96. $this->error($e->getMessage());
  97. }
  98. }
  99. /**
  100. * 测试线上接口(用表单当前值,可先测再保存)
  101. */
  102. public function testOnline()
  103. {
  104. $row = $this->request->post('row/a', []);
  105. $apiKey = trim((string)($row['llm_online_api_key'] ?? ''));
  106. if ($apiKey === '') {
  107. $apiKey = (string)Db::name('config')->where('name', 'llm_online_api_key')->value('value');
  108. }
  109. if ($apiKey === '') {
  110. $this->error('请填写线上 API Key');
  111. }
  112. $model = trim((string)($row['llm_online_model'] ?? ''));
  113. if ($model === '') {
  114. $model = 'qwen-plus';
  115. }
  116. try {
  117. $client = new LlmClient([
  118. 'profile' => 'online',
  119. 'force' => true,
  120. 'online_enable'=> '1',
  121. 'base_url' => $row['llm_online_base_url'] ?? '',
  122. 'api_key' => $apiKey,
  123. 'text_model' => $model,
  124. 'vision_model' => $row['llm_online_vision_model'] ?? '',
  125. 'timeout' => (int)($row['llm_online_timeout'] ?? 60),
  126. ]);
  127. $reply = $client->chat('只回复两个字:成功', $model, false);
  128. $this->success('线上接口可用', null, [
  129. 'model' => $model,
  130. 'reply' => mb_substr(trim((string)$reply), 0, 80),
  131. ]);
  132. } catch (\Exception $e) {
  133. $this->error($e->getMessage());
  134. }
  135. }
  136. /**
  137. * 与系统配置共用 fa_config,直接重写 extra/site.php,避免无 general/config/edit 权限时写不进去。
  138. */
  139. protected function writeSiteFile()
  140. {
  141. $config = [];
  142. $rows = Db::name('config')->select();
  143. foreach ($rows as $value) {
  144. if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'], true)) {
  145. $value['value'] = explode(',', (string)$value['value']);
  146. }
  147. if ($value['type'] === 'array') {
  148. $value['value'] = (array)json_decode((string)$value['value'], true);
  149. }
  150. $config[$value['name']] = $value['value'];
  151. }
  152. file_put_contents(
  153. CONF_PATH . 'extra' . DS . 'site.php',
  154. "<?php\n\nreturn " . var_export($config, true) . ";\n"
  155. );
  156. }
  157. }