|
22 | 22 | from .evidence import build_daily_evidence, build_stock_evidence |
23 | 23 | from .llm import LLMClient, LLMError |
24 | 24 | from .local_store import load_store, now_label, save_store, young_home |
| 25 | +from .methodology import sync_stock_analysis_methodology |
25 | 26 | from .profile import ( |
26 | 27 | add_group, |
27 | 28 | add_group_item, |
@@ -125,7 +126,9 @@ def update(pre: bool, user_install: bool) -> None: |
125 | 126 | "update failed with exit code " |
126 | 127 | f"{result.returncode}. young-stock-cli requires Python 3.9+. " |
127 | 128 | "Check `python3 --version`, then retry with " |
128 | | - "`python3 -m pip install --upgrade young-stock-cli`." |
| 129 | + "`python3 -m pip install --upgrade young-stock-cli`;" |
| 130 | + "如果 `which young` 指向 uv tool 环境,请运行 " |
| 131 | + "`uv tool install --upgrade young-stock-cli`。" |
129 | 132 | ) |
130 | 133 |
|
131 | 134 |
|
@@ -278,10 +281,17 @@ def _run_llm_replay(date_str: str, kind: str = "replay", symbol: str | None = No |
278 | 281 | artifacts = ReportArtifacts(date_str) |
279 | 282 | artifacts.write_json("evidence" if not symbol else f"{symbol}-evidence", evidence.to_dict()) |
280 | 283 | config = load_config(strict=False).get("llm", {}) |
| 284 | + methodology = sync_stock_analysis_methodology() |
281 | 285 | try: |
282 | | - markdown, metadata = generate_llm_daily_report(evidence.to_dict(), LLMClient(config)) |
| 286 | + markdown, metadata = generate_llm_daily_report( |
| 287 | + evidence.to_dict(), |
| 288 | + LLMClient(config), |
| 289 | + methodology=methodology.text, |
| 290 | + ) |
283 | 291 | except LLMError as exc: |
284 | 292 | raise click.ClickException(str(exc)) from exc |
| 293 | + metadata["stock_analysis_version"] = methodology.version |
| 294 | + metadata["stock_analysis_updated"] = methodology.updated |
285 | 295 | name = f"analyze-{symbol}" if symbol else kind |
286 | 296 | path = artifacts.write_markdown(name, markdown) |
287 | 297 | artifacts.write_metadata({"kind": name, **metadata}) |
@@ -334,7 +344,11 @@ def config_show() -> None: |
334 | 344 |
|
335 | 345 |
|
336 | 346 | @config.command("llm", help="Configure the LLM provider and model.") |
337 | | -@click.option("--provider", required=True, type=click.Choice(["openai", "deepseek", "qwen", "ollama", "anthropic"])) |
| 347 | +@click.option( |
| 348 | + "--provider", |
| 349 | + required=True, |
| 350 | + type=click.Choice(["openai", "ark", "kimi", "moonshot", "deepseek", "qwen", "ollama", "anthropic"]), |
| 351 | +) |
338 | 352 | @click.option("--model", required=True) |
339 | 353 | @click.option("--api-key", default=None, hide_input=True) |
340 | 354 | @click.option("--api-key-env", default=None, help="Environment variable containing the API key.") |
@@ -362,6 +376,43 @@ def config_llm( |
362 | 376 | click.echo(f"LLM configured: provider={provider}; model={model}; config={config_path()}") |
363 | 377 |
|
364 | 378 |
|
| 379 | +@config.command("models", help="List model IDs exposed by an OpenAI-compatible, Anthropic, or Ollama endpoint.") |
| 380 | +@click.option( |
| 381 | + "--provider", |
| 382 | + default=None, |
| 383 | + type=click.Choice(["openai", "ark", "kimi", "moonshot", "deepseek", "qwen", "ollama", "anthropic"]), |
| 384 | +) |
| 385 | +@click.option("--api-key", default=None, hide_input=True) |
| 386 | +@click.option("--api-key-env", default=None, help="Environment variable containing the API key.") |
| 387 | +@click.option("--api-base", default=None, help="Provider API base, for example https://api.moonshot.cn/v1.") |
| 388 | +@click.option("--timeout", default=30, show_default=True, type=float) |
| 389 | +def config_models( |
| 390 | + provider: str | None, |
| 391 | + api_key: str | None, |
| 392 | + api_key_env: str | None, |
| 393 | + api_base: str | None, |
| 394 | + timeout: float, |
| 395 | +) -> None: |
| 396 | + saved = dict(load_config(strict=False).get("llm") or {}) |
| 397 | + query = { |
| 398 | + **saved, |
| 399 | + "provider": provider or saved.get("provider"), |
| 400 | + "api_key": api_key if api_key is not None else saved.get("api_key"), |
| 401 | + "api_key_env": api_key_env if api_key_env is not None else saved.get("api_key_env"), |
| 402 | + "api_base": api_base or saved.get("api_base"), |
| 403 | + "timeout": timeout, |
| 404 | + } |
| 405 | + try: |
| 406 | + models = LLMClient(query).list_models() |
| 407 | + except LLMError as exc: |
| 408 | + raise click.ClickException(str(exc)) from exc |
| 409 | + if not models: |
| 410 | + click.echo("该服务当前未返回可用模型 ID。") |
| 411 | + return |
| 412 | + for model_id in models: |
| 413 | + click.echo(model_id) |
| 414 | + |
| 415 | + |
365 | 416 | @config.group("channel", help="Manage notification channels.") |
366 | 417 | def config_channel() -> None: |
367 | 418 | pass |
|
0 commit comments