求助,Obsidian Copilot插件怎么用通义千问的api啊

前段时间通义千问大降价,趁机入手了api,结果没找到相似的攻略,不会用,求助。


遇到的问题

我尝试在Copilot插件中使用,在GPT菜单下填入了我的api和官方推荐的网址,如下

官方推荐如上

之后使用插件发现没有反应,是我设置的有问题吗?求大佬指点

这个一直没有反应

可能比较复杂, 希望别是跨域的事


还是先了解问题出在哪吧

Copilot 的选项最后那里, 可以启用 debug 模式,
启用后, 打开 Ob 控制台 Ctrl+Shift+i 的状态下, 发送简单一条对话, 看看报的是啥错?

Access to fetch at ‘https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions’ from origin ‘app://obsidian.md’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
plugin:copilot:83303

POST https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions net::ERR_FAILED

显示了这个问题,问了AI说是CORS(跨源资源共享)相关的,大佬,这个能解决吗?

实测成功在 Copilot 里使用 OpenAI 风格 API 的 qwen-turbo

简单说, 确实得先解决跨域问题

这方面我也是现学的, 也理解的不太好,
希望熟悉的朋友帮忙提些建议, 指出错误


解决 Copilot 以代理 endpoint 调通义千问时的跨域问题

我目前找到的最简单方案是 cors-anywhere
原理是自造中转服务器, 它既是服务端又是客户端, 得以居中协调两侧都动不了的那些设置

建议先拿这项目搭在 heroku 的 demo 试试

试跑时要注意:

  • 这个 demo 需要先访问它页面, 点击按钮解锁, 才能用
  • demo 部署在 herokuapp 的, 得自己解决访问问题 (包括随后在 Ob 里用时, 也得开着)
  • Copilot 里的设置 “OpenAI Proxy Base URL”
    • 原先的 https://dashscope.aliyuncs.com/compatible-mode/v1
    • 要改为 https://cors-anywhere.herokuapp.com/https://dashscope.aliyuncs.com/compatible-mode/v1
    • 其余如模型名字 (qwen-turbo), api_key (sk-xxxx) 之类都不变

注意这个就是测试用的: 安全, 速度, 便捷都可疑

如果跑通了, 打算长期就这么用了, 才建议去本地搭建 cors-anywhere 自己用


完后, 还有个坑我也踩过了, 也建议一并改掉:

通义千问要求 top_p 不得为 1, 可以给改成 0.9
代码在 .obsidian/plugins/copilot/main.js 第 86027 行

    Object.defineProperty(this, "topP", {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 1    // 通义千问不允许是 1, 给改成 0.5~0.9
    });

改掉这个值, 完后重启 Obsidian (找个好点的文本编辑器, 这文件有 4mb)

PS. 这细节也见 如何在 Obsidian 中使用 AI 国产大模型?(KIMI,通义千问) 搜 “报错处理” 那一节

顺便记录一些查这问题时的信息…

1
OpenAI Proxy base url CORS errors · Issue #360 · logancyang/obsidian-copilot 已经有人给 Copilot 报 bug 了

2
CORS problem with library - Developers: Plugin & API - Obsidian Forum
Make HTTP requests from plugins - Developers: Plugin & API - Obsidian Forum 提到了自建中间件服务器, 提到了 node-fetch, 提到了可以用 Ob 提供的 request() 以及 requestUrl() 替代 fetch() 理由是 Ob 给的这俩自带跨域?

3
据说可以加 mode: 'no-cors' 就能解决

fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', {
  mode: 'no-cors'
});

但是上述几个办法, 要么没太理解, 要么试了没搞出来

应该找找比较全面的讲跨域的资料

补充跨域原理的资料, 清楚讲了来龙去脉 为什么给你设置重重障碍?讲一讲Web开发中的跨域


此外对于 Obsidian Copilot 的部分, 也可能有疏漏:

  • 目前我只尝试了侧栏对话, 但 Copilot 也支持 RAG (单笔记QA, 全库QA) 所以 embedding 那个部分, 也得跑通了才行

你好,您提到了pkmer这篇文章,这里通义千问的部分是我写的,但是我只测试了text-gen插件,如果您有完整的Copilot解决方案,欢迎您来补充细节

1 个赞

感谢您的文章! 如果我找到了便捷办法, 我一定会帮助补充的

目前情况是, 对于 Copilot 插件我只跑通了 cors-anywhere 方案, 这我知道是挺麻烦的

  • 首先得额外运行个服务
  • 对于新手, 想要自建, 是 git clone → 安装 node → npm install → 安装好慢得改国内镜像… 这一套步骤能过来, 估计还不如换别的同类 AI 插件

所以目前我打算看看这两个:

  1. 为啥 TextGen 直接一个 CORS Bypass 开关就能解决跨域? 然后设法运用到 Copilot 里面
  2. 如果上一条我搞不定, 我去找找有没有国内现成可用的类似 cors-anywhere 服务, 或本地版预编译 exe 的服务, 或 Ob 插件专供这种服务

但这些我一时半会弄不出来, 大家如果能有啥好办法, 也欢迎提提建议

我找到一个很邪门的方法,写了一个插件把原生 fetch 换成了 node-fetch 的 fetch

结果它真的成功了,但是原生 fetch 和 node-fetch 总归是有点小区别,不敢保证没有其他副作用

1 个赞

感谢指路! 我顺着这个 node-fetch 学习一下

这个方法目前来看还挺好用的,甚至连流式都没问题,我之前尝试过一个更邪门的方法来绕过 cors 的问题,绕是绕过了,但是不支持流式传输。

感谢两位大佬的研究,欢迎你们来pkmer文章补充和贡献

可以给作者提PR,虽然现在作者消失了 :rofl:

这两天, 在 Copilot 使用通义千问的问题上取得了一些进展
感谢楼里大家的帮忙!

我把目前知道办法总结为三种方案:


1 修改 fetch 方案

来自 lazyloong 的研究

该思路是临时替换掉 window.fetch, 改成可以跨域的替代品 node-fetch

以下为插件的具体代码

太长了, 点击展开详细代码

首先 clone sample-plugin 完后需要调整三个文件

// 调整 manifest.json 的描述
{
	"id": "alternative-fetch-replacer",
	"name": "Alternative Fetch Replacer",
	"version": "1.0.0",
	"minAppVersion": "0.15.0",
	"description": "临时替换 window.fetch 为 node-fetch 以绕开同源策略限制",
	"author": "Obsidian",
	"authorUrl": "https://obsidian.md",
	"fundingUrl": "https://obsidian.md/pricing",
	"isDesktopOnly": false
}
// 调整 esbuild.config.mjs 增加一行
......
external: [
        ......
		"@codemirror/view",
		"@lezer/common",
		"@lezer/highlight",
		"node:*",   // <-- 增加这一行, 其余不变
		"@lezer/lr",
		...builtins],
......
// 完全修改掉 main.ts
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, requestUrl } from 'obsidian';
import fetch from "node-fetch";
type FetchFunction = typeof window.fetch;

// 这个 as unknown 啥意思? 
// 目前已熟练掌握了 "类型体操" 四个字的拼写, 其余都不懂
const node_fetch: FetchFunction = fetch as unknown as FetchFunction;

export default class NoProblemsFetch extends Plugin {
    originalFetch: any;
    async onload () {
        this.originalFetch = window.fetch;
		console.log(`预备使用 node-fetch 替代 window.fetch`);
		
        window.fetch = node_fetch;
		
		const fetch_funcs = [this.originalFetch, node_fetch, window.fetch];
		const current_fetch_desc = fetch_funcs[2] === fetch_funcs[0] ? '原有的fetch':'插件提供的fetch';
		console.log(fetch_funcs);
		console.log(`当前fetch为: ${current_fetch_desc} \n以上三个函数分别是 [原始fetch, 新node-fetch, 目前采用fetch]`);
		new Notice(`使用 node-fetch 替代 window.fetch\n现在是: ${current_fetch_desc}`);
    }
    onunload () {
		console.log(`预备撤销对 window.fetch 的更改`);

		window.fetch = this.originalFetch;

		const fetch_funcs = [this.originalFetch, node_fetch, window.fetch];
		const current_fetch_desc = fetch_funcs[2] === fetch_funcs[0] ? '原有的fetch':'插件提供的fetch';
		console.log(fetch_funcs);
		console.log(`当前fetch为: ${current_fetch_desc} \n以上三个函数分别是 [原始fetch, 新node-fetch, 目前采用fetch]`);
		new Notice(`撤销对 window.fetch 的更改\n现在是: ${current_fetch_desc}`);
    }
}

编译方法是

npm install
npm run build  # 之后会拿到 main.js 文件

如果不熟悉 nodejs 弄这些可能会有困难
所以也把插件打包下载放在这里 download alter-fetch-replacer
(尽量还是自己编译, 尽量别用莫名来源的插件, 尤其这种改了基础函数的)

使用注意:

  • 先启用这个插件, 再启用 Copilot
  • 安装方式同其他插件, 手动放在 .obsidian/plugins/ 下面
  • 需要明白这是在干什么: 它替换了全局的 window.fetch, 不好说对别的插件有啥影响

仍存疑问

为啥 TextGen 直接一个 CORS Bypass 开关就能解决跨域? 然后设法运用到 Copilot 里面

因为 TextGen 自造了服务器来解决, 见代码 base.tsx#L165 proxy-service.ts

那么 Copilot 有没有做这个事? 有 (端口为 55301)
但是它只为了 Claude 系列做了代理, 我想给千问也改改, 没弄出来


2 绕行方案

思路是设法代理这个 “通义千问 endpoint”

目前找到了 songquanpeng/one-api 适合干这事, 安装简单, releases 里有本地单文件直接运行

对 One API 的极简介绍: 聚合各平台大模型, 统一以 OpenAI 的格式呈现, 本地部署, 自己给自己当中间商

使用起来大致是

原先 Obsidian Copilot 配置为:

1 在 Copilot 里面填自定义代理 GPT 地址
https://dashscope.aliyuncs.com/compatible-mode/v1
2 在 Copilot 里面填通义千问 apikey (sk-xxxxxx 阿里大模型那个key)
结果: 配完后应该不能用, 有跨域问题


现在要改为:
1 
在本地运行 One API 
并访问界面地址 http://localhost:3000 (默认密码 root 123456) 
在其中配置 渠道 和 令牌
渠道: 添加一个通义千问模型 (通义千问 apikey 要填, 完后点测试并跑通)
令牌: 生成一个, 复制下来

2 
在 Copilot 里面 "OpenAI Proxy Base URL" 那里, 改填 One API 给出的地址
http://localhost:3000/v1
在 Copilot 里面填 One API 的令牌 (sk-xxxxxx 注意不是通义的 key)

完后就可以正常用 Obsidian Copilot 了

附 "渠道"的设置图为

优点:

  • 可解任何其他大模型在各种 client 里的类似跨域问题

3 cors anywhere 方案

之前已经说过, 不再详述

这个方案确实麻烦, 硬找两个优点:

  • 有助于加深理解 CORS … 比如最近我刚知道:
    • “同源策略” = 为了安全, 有必要对某些访问场景施加限制
    • “CORS” = 在认可这些限制的必要性的前提下, 找办法 “适当放松同源策略”
      • 感觉自己一直误解了这些词
  • 除了这个大模型的问题, 它还能解其他类型的访问限制, 比如最近 sspai 图片增加了外链限制, 我发现, 启动一个 cors-anywhere 本地服务可以临时应对
    • 笔记里的 https://cdnfile.sspai.com 批量给替换成 http://localhost:8080/https://cdnfile.sspai.com
    • 启用 cors-anywhere 会发现笔记外链图好了 (有时得抄个自己的 cookie 用在里面)

4 总结

再次感谢楼里大家的帮忙!

以上难免有自己理解不对, 试验不周全之处, 遇到了再完善吧
Obsidian, 年轻人的第一款前端实战训练营…