Skip to content

Commit

Permalink
Merge pull request #12 from TarsLab/claude
Browse files Browse the repository at this point in the history
Claude
  • Loading branch information
ae86jack authored Aug 16, 2024
2 parents 1bdcd8c + 658179c commit 9eb4c83
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 120 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# 简介

Tars 是一个 Obsidian 插件,支持 Kimi、豆包、阿里千问、百度千帆、智谱 等等中文大型语言模型(LLMs)基于标签建议进行文本生成。Tars 这个名字来源于电影《星际穿越》中的机器人 Tars。
Tars 是一个 Obsidian 插件,基于标签建议进行文本生成,支持 Claude、OpenAI、Kimi、豆包、阿里千问、智谱、深度求索、百度千帆等。Tars 这个名字来源于电影《星际穿越》中的机器人 Tars。

## 特性

Expand All @@ -23,9 +23,10 @@ Tars 是一个 Obsidian 插件,支持 Kimi、豆包、阿里千问、百度千

## AI 服务提供商

- [Claude](https://claude.ai)
- [OpenAI](https://platform.openai.com/api-keys)
- [Kimi](https://www.moonshot.cn)
- [Doubao 豆包](https://www.volcengine.com/product/doubao)
- [OpenAI](https://platform.openai.com/api-keys)
- [Qianfan 百度千帆](https://qianfan.cloud.baidu.com)
- [Qwen 阿里千问](https://dashscope.console.aliyun.com)
- [Zhipu 智谱](https://open.bigmodel.cn/)
Expand All @@ -35,9 +36,15 @@ Tars 是一个 Obsidian 插件,支持 Kimi、豆包、阿里千问、百度千

## 如何使用

在设置页面添加一个 AI 助手,设置 API 密钥,然后在编辑器中使用相应的标签来触发 AI 助手。
在设置页面添加一个 AI 助手,设置 API 密钥,然后在编辑器中使用相应的标签来触发 AI 助手。通过对话形式来触发,先有用户消息,然后才能触发 AI 助手回答问题。

```text
#我 : 1+1=?(用户消息)
(隔开一个空行)
#Claude : (触发)
```

如果在设置页面的 AI 助手中没有你想要的 model 类型,服务器地址需要中转,可以在设置中的“覆盖输入参数”进行配置,输入 JSON 格式,例如 `{"model":"你想要的model", "baseURL": "中转地址"}`
如果在设置页面的 AI 助手中没有你想要的 model 类型,或者服务器地址需要自定义,可以在设置中的“覆盖输入参数”进行配置,输入 JSON 格式,例如 `{"model":"你想要的model", "baseURL": "自定义地址"}`

## 对话语法

Expand Down
15 changes: 11 additions & 4 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# Introduction

Tars is an Obsidian plugin that supports text generation by Kimi, Doubao, Ali Qianwen, Baidu Qianfan, Zhipu, and other Chinese large language models (LLMs) based on tag suggestions. The name Tars comes from the robot Tars in the movie "Interstellar".
Tars is an Obsidian plugin that supports text generation based on tag suggestions, using services like Claude, OpenAI, Kimi, Doubao, Qwen, Zhipu, DeepSeek, QianFan & more. The name Tars comes from the robot Tars in the movie "Interstellar".

## Features

Expand All @@ -23,9 +23,10 @@ Tars is an Obsidian plugin that supports text generation by Kimi, Doubao, Ali Qi

## AI providers

- [Claude](https://claude.ai)
- [OpenAI](https://platform.openai.com/api-keys)
- [Kimi](https://www.moonshot.cn)
- [Doubao](https://www.volcengine.com/product/doubao)
- [OpenAI](https://platform.openai.com/api-keys)
- [Qianfan](https://qianfan.cloud.baidu.com)
- [Qwen](https://dashscope.console.aliyun.com)
- [Zhipu](https://open.bigmodel.cn/)
Expand All @@ -35,9 +36,15 @@ If the AI provider you want is not in the list above, you can propose a specific

## How to use

Add an AI assistant in the settings page, set the API key, and then use the corresponding tag in the editor to trigger the AI assistant.
Add an AI assistant in the settings page, set the API key, and then use the corresponding tag in the editor to trigger the AI assistant. Trigger through a conversation form, with user messages first, then trigger the AI assistant to answer questions.

```text
#User : 1+1=?(user message)
(blank line)
#Claude :(trigger)
```

If the model type you want is not in the AI assistant on the settings page, you can configure it in the "Override input parameters" in the settings, input JSON format, for example `{"model":"your desired model"}`.
If the model type you want is not in the AI assistant in the settings page, or the server address needs to be customized, you can configure it in the "Override input parameters" in the settings, input JSON format, for example `{"model":"your model", "baseURL": "your url"}`.

## Conversations syntax

Expand Down
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"id": "tars",
"name": "Tars",
"version": "0.3.1",
"version": "0.4.0",
"minAppVersion": "1.5.8",
"description": "Use Kimi and other Chinese LLMs for text generation based on tag suggestions.",
"description": "Text generation based on tag suggestions, using Claude, OpenAI, Kimi, Doubao, Qwen, Zhipu, DeepSeek, QianFan & more.",
"author": "Tarslab",
"authorUrl": "https://github.com/tarslab",
"isDesktopOnly": true
Expand Down
75 changes: 28 additions & 47 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-tars",
"version": "0.3.1",
"version": "0.4.0",
"description": "Use Kimi and other Chinese LLMs for text generation based on tag suggestions.",
"main": "main.js",
"scripts": {
Expand All @@ -12,12 +12,12 @@
"author": "C Jack<https://github.com/ae86jack>",
"license": "MIT",
"devDependencies": {
"@anthropic-ai/sdk": "^0.25.1",
"@types/node": "^16.18.101",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"https-proxy-agent": "^7.0.5",
"jose": "^5.2.4",
"node-fetch": "^3.3.2",
"obsidian": "latest",
Expand Down
8 changes: 6 additions & 2 deletions src/lang/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ export default {
Model: 'Model',
'Select the model to use': 'Select the model to use',
'Input the model to use': 'Input the model to use',
'Please enter a number': 'Please enter a number',
'Minimum value is 256': 'Minimum value is 256',
'Proxy URL': 'Proxy URL',
'Invalid URL': 'Invalid URL',
'Override input parameters': 'Override input parameters',
'Developer feature, in JSON format, for example, {"model": "gptX"} can override the model input parameter.':
'Developer feature, in JSON format, for example, {"model": "gptX"} can override the model input parameter.',
'Developer feature, in JSON format. e.g. {"model": "your model", "baseURL": "your url"}':
'Developer feature, in JSON format. e.g. {"model": "your model", "baseURL": "your url"}',
'Remove AI assistant': 'Remove AI assistant',
Remove: 'Remove',

Expand Down
8 changes: 6 additions & 2 deletions src/lang/locale/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ export default {
Model: '模型',
'Select the model to use': '选择要使用的模型',
'Input the model to use': '输入要使用的模型',
'Please enter a number': '请输入一个数字',
'Minimum value is 256': '最小值是256',
'Proxy URL': '代理 URL',
'Invalid URL': '无效的 URL',
'Override input parameters': '覆盖输入参数',
'Developer feature, in JSON format, for example, {"model": "gptX"} can override the model input parameter.':
'开发者功能,json格式, 比如{"model": "gptX"}可以覆盖model输入参数,如果model下拉框没有对应的模型,想要使用新的模型,可以在这里输入',
'Developer feature, in JSON format. e.g. {"model": "your model", "baseURL": "your url"}':
'开发者功能,以 JSON 格式。例如 {"model": "你想要的model", "baseURL": "自定义地址"}',
'Remove AI assistant': '移除 AI 助手',
Remove: '移除',

Expand Down
94 changes: 63 additions & 31 deletions src/providers/claude.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,71 @@
import Anthropic from '@anthropic-ai/sdk'

import { HttpsProxyAgent } from 'https-proxy-agent'
import { t } from 'src/lang/helper'
import { BaseOptions, Message, SendRequest, Vendor } from '.'
import fetch from 'node-fetch'
import { BaseOptions, Message, SendRequest, Vendor, Optional } from '.'

interface ClaudeMessage {
role: 'user' | 'assistant'
content: string
}
type ClaudeOptions = BaseOptions & Pick<Optional, 'max_tokens' | 'proxyUrl'>

const sendRequestFunc = (settings: BaseOptions): SendRequest =>
async function* (rawMessages: Message[]) {
const sendRequestFunc = (settings: ClaudeOptions): SendRequest =>
async function* (messages: Message[]) {
const { parameters, ...optionsExcludingParams } = settings
const options = { ...optionsExcludingParams, ...parameters } // 这样的设计,让parameters 可以覆盖掉前面的设置 optionsExcludingParams
const { apiKey, baseURL, model, ...remains } = options
const { apiKey, baseURL, model, max_tokens, proxyUrl, ...remains } = options
if (!apiKey) throw new Error(t('API key is required'))

const messages = rawMessages.filter((m) => m.role === 'user' || m.role == 'assistant') as ClaudeMessage[]
const client = new Anthropic({
apiKey,
baseURL
})

const stream = client.messages.stream({
const [system_msg, messagesWithoutSys] =
messages[0].role === 'system' ? [messages[0], messages.slice(1)] : [null, messages]
const headers = {
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01',
'X-Api-Key': apiKey
}
const body = {
model,
messages,
max_tokens: 1024
// ...remains
system: system_msg?.content,
max_tokens,
messages: messagesWithoutSys,
stream: true,
...remains
}
console.debug('proxyUrl', proxyUrl)
console.debug('claude api body', JSON.stringify(body))
const response = await fetch(baseURL, {
method: 'POST',
headers,
body: JSON.stringify(body),
agent: proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined
})

// for await (const part of stream) {
// const text = part.choices[0]?.delta?.content
// if (!text) continue
// yield text
// }
for await (const event of stream) {
console.log('event', event)
yield 'todo'
if (!response || !response.body) {
throw new Error('No response')
}

const decoder = new TextDecoder('utf-8')
// 参考 https://docs.anthropic.com/en/api/messages-streaming
for await (const chunk of response.body) {
const lines = decoder.decode(Buffer.from(chunk))
// console.debug('lines', lines)
const [firstLine, secondLine, _] = lines.split('\n')
if (!firstLine.startsWith('event: ')) {
// {"type":"error","error":{"type":"invalid_request_error","message":"max_tokens: 8192 > 4096, which is the maximum allowed number of output tokens for claude-3-opus-20240229"}}
throw new Error(lines)
}

const event = firstLine.slice('event: '.length).trim()
const dataStr = secondLine.slice('data:'.length)
const data = JSON.parse(dataStr)
switch (event) {
case 'content_block_delta':
yield data.delta.text
break
case 'message_delta':
if (data.delta.stop_reason !== 'end_turn') {
throw new Error(`Unexpected stop reason: ${data.delta.stop_reason}`)
}
break
case 'error':
throw new Error(data.error.message)
}
}
}

Expand All @@ -45,11 +75,13 @@ export const claudeVendor: Vendor = {
name: 'Claude',
defaultOptions: {
apiKey: '',
baseURL: 'https://fast.bemore.lol',
baseURL: 'https://api.anthropic.com/v1/messages',
model: models[0],
max_tokens: 1024,
proxyUrl: '',
parameters: {}
},
} as ClaudeOptions,
sendRequestFunc,
models,
websiteToObtainKey: ''
websiteToObtainKey: 'https://console.anthropic.com'
}
4 changes: 3 additions & 1 deletion src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface ProviderSettings {
options: BaseOptions
}

export interface SecretOptions extends BaseOptions {
export interface Optional {
apiSecret: string
proxyUrl: string
max_tokens: number
}
Loading

0 comments on commit 9eb4c83

Please sign in to comment.