Use direct LLM config for qqzot
This commit is contained in:
parent
2d15c23975
commit
f14378c0b4
20
README.md
20
README.md
|
|
@ -55,3 +55,23 @@ QQzot 不负责:
|
||||||
- 需要调用 LLM 生成笔记或表格时,使用 AwesomeGPT 或 DeepSeek 相关环境配置。
|
- 需要调用 LLM 生成笔记或表格时,使用 AwesomeGPT 或 DeepSeek 相关环境配置。
|
||||||
|
|
||||||
不要把 API key 写入 skill 文件、Obsidian 笔记、Git 提交、终端输出或聊天记录。
|
不要把 API key 写入 skill 文件、Obsidian 笔记、Git 提交、终端输出或聊天记录。
|
||||||
|
## LLM 直接配置
|
||||||
|
|
||||||
|
`qqzot` 默认使用脚本直接读取环境变量或 `.env`,不再默认读取 Zotero 的 AwesomeGPT/ZoteroGPT preferences。
|
||||||
|
|
||||||
|
推荐配置:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:DEEPSEEK_API_KEY="..."
|
||||||
|
$env:DEEPSEEK_MODEL="deepseek-chat"
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以使用中性命名:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:QQZOT_LLM_API_KEY="..."
|
||||||
|
$env:QQZOT_LLM_BASE_URL="https://api.deepseek.com"
|
||||||
|
$env:QQZOT_LLM_MODEL="deepseek-chat"
|
||||||
|
```
|
||||||
|
|
||||||
|
只有显式加 `--use-awesomegpt-prefs` 时,脚本才会读取 AwesomeGPT/ZoteroGPT 的旧配置。
|
||||||
|
|
|
||||||
18
SKILL.md
18
SKILL.md
|
|
@ -27,8 +27,12 @@ Boundary:
|
||||||
- Default Obsidian vault, only when a script needs templates or an output path:
|
- Default Obsidian vault, only when a script needs templates or an output path:
|
||||||
`C:\Users\qyh15\Documents\Obsidian Vault`.
|
`C:\Users\qyh15\Documents\Obsidian Vault`.
|
||||||
- `ZOTERO_API_KEY` is required only for Zotero Web API writes.
|
- `ZOTERO_API_KEY` is required only for Zotero Web API writes.
|
||||||
- LLM settings come from `AWESOMEGPT_API_KEY`, `AWESOMEGPT_BASE_URL`,
|
- LLM settings come from direct script configuration, not Zotero AwesomeGPT by
|
||||||
`AWESOMEGPT_MODEL`, or AwesomeGPT preferences in the default Zotero profile.
|
default. Prefer `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and optional
|
||||||
|
`DEEPSEEK_MODEL`; or use `QQZOT_LLM_API_KEY`, `QQZOT_LLM_BASE_URL`, and
|
||||||
|
`QQZOT_LLM_MODEL` for a provider-neutral config.
|
||||||
|
- AwesomeGPT/ZoteroGPT preferences are legacy fallback only. Use
|
||||||
|
`--use-awesomegpt-prefs` explicitly if the user asks to reuse those settings.
|
||||||
|
|
||||||
Never store API keys in skill files, vault helpers, Git commits, zip files,
|
Never store API keys in skill files, vault helpers, Git commits, zip files,
|
||||||
terminal output, or chat. If keys appear in logs or chat, tell the user to
|
terminal output, or chat. If keys appear in logs or chat, tell the user to
|
||||||
|
|
@ -53,6 +57,16 @@ Use the smallest Zotero operation that answers the request:
|
||||||
State clearly before live writes that Zotero child notes will be created.
|
State clearly before live writes that Zotero child notes will be created.
|
||||||
Use `--dry-run` for first-time validation or template changes.
|
Use `--dry-run` for first-time validation or template changes.
|
||||||
|
|
||||||
|
Default LLM config should be direct:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:DEEPSEEK_API_KEY="..."
|
||||||
|
$env:DEEPSEEK_MODEL="deepseek-chat"
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not rely on AwesomeGPT preferences unless the command includes
|
||||||
|
`--use-awesomegpt-prefs`.
|
||||||
|
|
||||||
Single item:
|
Single item:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,14 @@
|
||||||
Generate an AI literature note from Zotero metadata and save it as a Zotero child note.
|
Generate an AI literature note from Zotero metadata and save it as a Zotero child note.
|
||||||
|
|
||||||
Required environment variables:
|
Required environment variables:
|
||||||
AWESOMEGPT_API_KEY DeepSeek/OpenAI-compatible API key
|
DEEPSEEK_API_KEY or QQZOT_LLM_API_KEY
|
||||||
AWESOMEGPT_BASE_URL Example: https://api.deepseek.com
|
|
||||||
AWESOMEGPT_MODEL Example: deepseek-v4-pro
|
|
||||||
ZOTERO_API_KEY Zotero Web API key with library write permission
|
ZOTERO_API_KEY Zotero Web API key with library write permission
|
||||||
|
|
||||||
|
Optional LLM environment variables:
|
||||||
|
DEEPSEEK_BASE_URL or QQZOT_LLM_BASE_URL, default: https://api.deepseek.com
|
||||||
|
DEEPSEEK_MODEL or QQZOT_LLM_MODEL, default: deepseek-chat
|
||||||
|
OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL for other OpenAI-compatible APIs
|
||||||
|
|
||||||
Optional:
|
Optional:
|
||||||
ZOTERO_USER_ID If omitted, resolved from /keys/current
|
ZOTERO_USER_ID If omitted, resolved from /keys/current
|
||||||
"""
|
"""
|
||||||
|
|
@ -57,6 +60,14 @@ def load_dotenv(path: Path) -> None:
|
||||||
os.environ.setdefault(key, value)
|
os.environ.setdefault(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def first_env(*names: str) -> str:
|
||||||
|
for name in names:
|
||||||
|
value = os.environ.get(name)
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def zotero_profile_prefs() -> Path | None:
|
def zotero_profile_prefs() -> Path | None:
|
||||||
profiles_ini = Path.home() / "AppData/Roaming/Zotero/Zotero/profiles.ini"
|
profiles_ini = Path.home() / "AppData/Roaming/Zotero/Zotero/profiles.ini"
|
||||||
profiles_root = profiles_ini.parent
|
profiles_root = profiles_ini.parent
|
||||||
|
|
@ -444,11 +455,20 @@ def build_prompt(item: dict[str, Any], bibtex: str, fulltext: str, vault: Path)
|
||||||
|
|
||||||
|
|
||||||
def call_llm(prompt: str) -> str:
|
def call_llm(prompt: str) -> str:
|
||||||
api_key = os.environ.get("AWESOMEGPT_API_KEY")
|
api_key = first_env("QQZOT_LLM_API_KEY", "DEEPSEEK_API_KEY", "OPENAI_API_KEY", "AWESOMEGPT_API_KEY")
|
||||||
base_url = (os.environ.get("AWESOMEGPT_BASE_URL") or "").rstrip("/")
|
base_url = first_env("QQZOT_LLM_BASE_URL", "DEEPSEEK_BASE_URL", "OPENAI_BASE_URL", "AWESOMEGPT_BASE_URL")
|
||||||
model = os.environ.get("AWESOMEGPT_MODEL")
|
model = first_env("QQZOT_LLM_MODEL", "DEEPSEEK_MODEL", "OPENAI_MODEL", "AWESOMEGPT_MODEL")
|
||||||
|
if api_key and not base_url and os.environ.get("DEEPSEEK_API_KEY"):
|
||||||
|
base_url = "https://api.deepseek.com"
|
||||||
|
if api_key and not model and os.environ.get("DEEPSEEK_API_KEY"):
|
||||||
|
model = "deepseek-chat"
|
||||||
|
base_url = base_url.rstrip("/")
|
||||||
if not api_key or not base_url or not model:
|
if not api_key or not base_url or not model:
|
||||||
fail("AWESOMEGPT_API_KEY, AWESOMEGPT_BASE_URL, and AWESOMEGPT_MODEL are required")
|
fail(
|
||||||
|
"LLM config is required. Set DEEPSEEK_API_KEY plus optional "
|
||||||
|
"DEEPSEEK_BASE_URL/DEEPSEEK_MODEL, or set QQZOT_LLM_API_KEY, "
|
||||||
|
"QQZOT_LLM_BASE_URL, and QQZOT_LLM_MODEL."
|
||||||
|
)
|
||||||
if not base_url.endswith("/v1"):
|
if not base_url.endswith("/v1"):
|
||||||
base_url = base_url + "/v1"
|
base_url = base_url + "/v1"
|
||||||
payload = {
|
payload = {
|
||||||
|
|
@ -527,11 +547,17 @@ def main() -> None:
|
||||||
parser.add_argument("--dry-run", action="store_true", help="generate but do not write Zotero note")
|
parser.add_argument("--dry-run", action="store_true", help="generate but do not write Zotero note")
|
||||||
parser.add_argument("--vault", default=str(DEFAULT_VAULT), help="Obsidian vault containing 00 Templater")
|
parser.add_argument("--vault", default=str(DEFAULT_VAULT), help="Obsidian vault containing 00 Templater")
|
||||||
parser.add_argument("--env-file", help="Optional .env path; defaults to <vault>/.env")
|
parser.add_argument("--env-file", help="Optional .env path; defaults to <vault>/.env")
|
||||||
|
parser.add_argument(
|
||||||
|
"--use-awesomegpt-prefs",
|
||||||
|
action="store_true",
|
||||||
|
help="Opt in to reading Zotero AwesomeGPT/ZoteroGPT preferences as a legacy config source.",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
vault = Path(args.vault).expanduser().resolve()
|
vault = Path(args.vault).expanduser().resolve()
|
||||||
load_dotenv(Path(args.env_file).expanduser().resolve() if args.env_file else vault / ".env")
|
load_dotenv(Path(args.env_file).expanduser().resolve() if args.env_file else vault / ".env")
|
||||||
load_awesomegpt_prefs()
|
if args.use_awesomegpt_prefs:
|
||||||
|
load_awesomegpt_prefs()
|
||||||
|
|
||||||
keys = list(args.item_key)
|
keys = list(args.item_key)
|
||||||
if args.item_keys:
|
if args.item_keys:
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
Summarize Zotero items into a Markdown table using an OpenAI-compatible LLM.
|
Summarize Zotero items into a Markdown table using an OpenAI-compatible LLM.
|
||||||
|
|
||||||
Required environment variables:
|
Required environment variables:
|
||||||
AWESOMEGPT_API_KEY DeepSeek/OpenAI-compatible API key
|
DEEPSEEK_API_KEY or QQZOT_LLM_API_KEY
|
||||||
AWESOMEGPT_BASE_URL Example: https://api.deepseek.com
|
|
||||||
AWESOMEGPT_MODEL Example: deepseek-v4-pro
|
Optional LLM environment variables:
|
||||||
|
DEEPSEEK_BASE_URL or QQZOT_LLM_BASE_URL, default: https://api.deepseek.com
|
||||||
|
DEEPSEEK_MODEL or QQZOT_LLM_MODEL, default: deepseek-chat
|
||||||
|
OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL for other OpenAI-compatible APIs
|
||||||
|
|
||||||
Optional:
|
Optional:
|
||||||
Load from <vault>/.env or AwesomeGPT preferences in the default Zotero profile.
|
Load from <vault>/.env.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -63,6 +66,14 @@ def load_dotenv(path: Path) -> None:
|
||||||
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
|
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
|
||||||
|
|
||||||
|
|
||||||
|
def first_env(*names: str) -> str:
|
||||||
|
for name in names:
|
||||||
|
value = os.environ.get(name)
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def zotero_profile_prefs() -> Path | None:
|
def zotero_profile_prefs() -> Path | None:
|
||||||
profiles_ini = Path.home() / "AppData/Roaming/Zotero/Zotero/profiles.ini"
|
profiles_ini = Path.home() / "AppData/Roaming/Zotero/Zotero/profiles.ini"
|
||||||
profiles_root = profiles_ini.parent
|
profiles_root = profiles_ini.parent
|
||||||
|
|
@ -388,11 +399,20 @@ def build_prompt(records: list[dict[str, Any]], columns: list[str]) -> str:
|
||||||
|
|
||||||
|
|
||||||
def call_llm(prompt: str) -> str:
|
def call_llm(prompt: str) -> str:
|
||||||
api_key = os.environ.get("AWESOMEGPT_API_KEY")
|
api_key = first_env("QQZOT_LLM_API_KEY", "DEEPSEEK_API_KEY", "OPENAI_API_KEY", "AWESOMEGPT_API_KEY")
|
||||||
base_url = (os.environ.get("AWESOMEGPT_BASE_URL") or "").rstrip("/")
|
base_url = first_env("QQZOT_LLM_BASE_URL", "DEEPSEEK_BASE_URL", "OPENAI_BASE_URL", "AWESOMEGPT_BASE_URL")
|
||||||
model = os.environ.get("AWESOMEGPT_MODEL")
|
model = first_env("QQZOT_LLM_MODEL", "DEEPSEEK_MODEL", "OPENAI_MODEL", "AWESOMEGPT_MODEL")
|
||||||
|
if api_key and not base_url and os.environ.get("DEEPSEEK_API_KEY"):
|
||||||
|
base_url = "https://api.deepseek.com"
|
||||||
|
if api_key and not model and os.environ.get("DEEPSEEK_API_KEY"):
|
||||||
|
model = "deepseek-chat"
|
||||||
|
base_url = base_url.rstrip("/")
|
||||||
if not api_key or not base_url or not model:
|
if not api_key or not base_url or not model:
|
||||||
fail("AWESOMEGPT_API_KEY, AWESOMEGPT_BASE_URL, and AWESOMEGPT_MODEL are required")
|
fail(
|
||||||
|
"LLM config is required. Set DEEPSEEK_API_KEY plus optional "
|
||||||
|
"DEEPSEEK_BASE_URL/DEEPSEEK_MODEL, or set QQZOT_LLM_API_KEY, "
|
||||||
|
"QQZOT_LLM_BASE_URL, and QQZOT_LLM_MODEL."
|
||||||
|
)
|
||||||
if not base_url.endswith("/v1"):
|
if not base_url.endswith("/v1"):
|
||||||
base_url = base_url + "/v1"
|
base_url = base_url + "/v1"
|
||||||
payload = {
|
payload = {
|
||||||
|
|
@ -458,11 +478,17 @@ def main() -> None:
|
||||||
parser.add_argument("--vault", default=str(DEFAULT_VAULT), help="Obsidian vault containing optional .env")
|
parser.add_argument("--vault", default=str(DEFAULT_VAULT), help="Obsidian vault containing optional .env")
|
||||||
parser.add_argument("--env-file", help="Optional .env path; defaults to <vault>/.env")
|
parser.add_argument("--env-file", help="Optional .env path; defaults to <vault>/.env")
|
||||||
parser.add_argument("--out", help="Optional output Markdown file path")
|
parser.add_argument("--out", help="Optional output Markdown file path")
|
||||||
|
parser.add_argument(
|
||||||
|
"--use-awesomegpt-prefs",
|
||||||
|
action="store_true",
|
||||||
|
help="Opt in to reading Zotero AwesomeGPT/ZoteroGPT preferences as a legacy config source.",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
vault = Path(args.vault).expanduser().resolve()
|
vault = Path(args.vault).expanduser().resolve()
|
||||||
load_dotenv(Path(args.env_file).expanduser().resolve() if args.env_file else vault / ".env")
|
load_dotenv(Path(args.env_file).expanduser().resolve() if args.env_file else vault / ".env")
|
||||||
load_awesomegpt_prefs()
|
if args.use_awesomegpt_prefs:
|
||||||
|
load_awesomegpt_prefs()
|
||||||
|
|
||||||
keys = list(args.item_key)
|
keys = list(args.item_key)
|
||||||
if args.item_keys:
|
if args.item_keys:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue