diff --git a/docs/CONFIGURATION_GUIDE.md b/docs/CONFIGURATION_GUIDE.md index b00593fd0..321d943d8 100644 --- a/docs/CONFIGURATION_GUIDE.md +++ b/docs/CONFIGURATION_GUIDE.md @@ -269,7 +269,7 @@ models: # Fallback models for different providers provider_models: - siliconflow: "qwen/qwen-max" + siliconflow: "qwen/qwen3-max" google: "gemini-2.5-flash" # Model-specific parameters (override provider defaults) diff --git a/python/configs/providers/openrouter.yaml b/python/configs/providers/openrouter.yaml index 30a3188af..2f89555ec 100644 --- a/python/configs/providers/openrouter.yaml +++ b/python/configs/providers/openrouter.yaml @@ -12,7 +12,7 @@ connection: api_key_env: "OPENROUTER_API_KEY" # Default model if none specified -default_model: "qwen/qwen-max" +default_model: "qwen/qwen3-max" # Model Parameters Defaults defaults: @@ -29,8 +29,8 @@ models: name: "Claude Haiku 4.5" - id: "x-ai/grok-4" name: "Grok 4" - - id: "qwen/qwen-max" - name: "Qwen Max" + - id: "qwen/qwen3-max" + name: "Qwen3 Max" - id: "openai/gpt-5" name: "GPT-5" - id: "google/gemini-2.5-flash" diff --git a/python/valuecell/agents/common/trading/_internal/coordinator.py b/python/valuecell/agents/common/trading/_internal/coordinator.py index fba7aeef2..08d43f4ea 100644 --- a/python/valuecell/agents/common/trading/_internal/coordinator.py +++ b/python/valuecell/agents/common/trading/_internal/coordinator.py @@ -5,6 +5,7 @@ from loguru import logger +from valuecell.utils.ts import get_current_timestamp_ms from valuecell.utils.uuid import generate_uuid from ..decision import BaseComposer @@ -37,7 +38,6 @@ from ..utils import ( extract_market_snapshot_features, fetch_free_cash_from_gateway, - get_current_timestamp_ms, ) # Core interfaces for orchestration and portfolio service. diff --git a/python/valuecell/agents/common/trading/_internal/stream_controller.py b/python/valuecell/agents/common/trading/_internal/stream_controller.py index 00c5f3dde..8a364b5c5 100644 --- a/python/valuecell/agents/common/trading/_internal/stream_controller.py +++ b/python/valuecell/agents/common/trading/_internal/stream_controller.py @@ -15,9 +15,9 @@ from loguru import logger from valuecell.agents.common.trading import models as agent_models -from valuecell.agents.common.trading.utils import get_current_timestamp_ms from valuecell.server.db.repositories.strategy_repository import get_strategy_repository from valuecell.server.services import strategy_persistence +from valuecell.utils.ts import get_current_timestamp_ms if TYPE_CHECKING: from valuecell.agents.common.trading._internal.coordinator import ( diff --git a/python/valuecell/agents/common/trading/decision/prompt_based/composer.py b/python/valuecell/agents/common/trading/decision/prompt_based/composer.py index a7698c6a5..bb087b12e 100644 --- a/python/valuecell/agents/common/trading/decision/prompt_based/composer.py +++ b/python/valuecell/agents/common/trading/decision/prompt_based/composer.py @@ -1,7 +1,6 @@ from __future__ import annotations import json -from datetime import datetime, timezone from typing import Dict from agno.agent import Agent as AgnoAgent @@ -87,7 +86,9 @@ async def compose(self, context: ComposeContext) -> ComposeResult: return ComposeResult(instructions=[], rationale=plan.rationale) except Exception as exc: # noqa: BLE001 logger.error("LLM invocation failed: {}", exc) - return ComposeResult(instructions=[], rationale="LLM invocation failed") + return ComposeResult( + instructions=[], rationale=f"LLM invocation failed: {exc}" + ) # Optionally forward non-NOOP plan rationale to Discord webhook (env-driven) try: @@ -210,9 +211,13 @@ async def _call_llm(self, prompt: str) -> TradePlanProposal: logger.error("LLM output failed validation: {}", content) return TradePlanProposal( - ts=int(datetime.now(timezone.utc).timestamp() * 1000), items=[], - rationale="LLM output failed validation", + rationale=( + "LLM output failed validation. The model you chose " + f"`{model_utils.describe_model(model)}` " + "may be incompatible or returned unexpected output. " + f"Raw output: {content}" + ), ) async def _send_plan_to_discord(self, plan: TradePlanProposal) -> None: diff --git a/python/valuecell/agents/common/trading/features/market_snapshot.py b/python/valuecell/agents/common/trading/features/market_snapshot.py index 12fbc9e95..203ca3576 100644 --- a/python/valuecell/agents/common/trading/features/market_snapshot.py +++ b/python/valuecell/agents/common/trading/features/market_snapshot.py @@ -11,7 +11,7 @@ InstrumentRef, MarketSnapShotType, ) -from valuecell.agents.common.trading.utils import get_current_timestamp_ms +from valuecell.utils.ts import get_current_timestamp_ms class MarketSnapshotFeatureComputer: diff --git a/python/valuecell/agents/common/trading/models.py b/python/valuecell/agents/common/trading/models.py index 636da20b8..1d378c0ee 100644 --- a/python/valuecell/agents/common/trading/models.py +++ b/python/valuecell/agents/common/trading/models.py @@ -4,6 +4,8 @@ from pydantic import BaseModel, Field, field_validator, model_validator +from valuecell.utils.ts import get_current_timestamp_ms + from .constants import ( DEFAULT_AGENT_MODEL, DEFAULT_CAP_FACTOR, @@ -588,7 +590,10 @@ def _normalize_instrument(cls, data): class TradePlanProposal(BaseModel): """Structured output before rule normalization.""" - ts: int + ts: Optional[int] = Field( + default_factory=get_current_timestamp_ms, + description="Proposal timestamp in ms (if available)", + ) items: List[TradeDecisionItem] = Field(default_factory=list) rationale: Optional[str] = Field( default=None, description="Optional natural language rationale" diff --git a/python/valuecell/agents/common/trading/utils.py b/python/valuecell/agents/common/trading/utils.py index 82e56b4f2..6742cead2 100644 --- a/python/valuecell/agents/common/trading/utils.py +++ b/python/valuecell/agents/common/trading/utils.py @@ -1,5 +1,4 @@ import os -from datetime import datetime, timezone from typing import Dict, List, Optional, Tuple import ccxt.pro as ccxtpro @@ -13,11 +12,6 @@ from valuecell.agents.common.trading.models import FeatureVector -def get_current_timestamp_ms() -> int: - """Get current timestamp in milliseconds.""" - return int(datetime.now(timezone.utc).timestamp() * 1000) - - async def fetch_free_cash_from_gateway( execution_gateway, symbols: list[str] ) -> Tuple[float, float]: diff --git a/python/valuecell/utils/model.py b/python/valuecell/utils/model.py index 54105be36..be452c30d 100644 --- a/python/valuecell/utils/model.py +++ b/python/valuecell/utils/model.py @@ -9,7 +9,6 @@ - Backward compatible: Environment variables still work for model_id override """ -import logging import os from typing import Optional @@ -19,6 +18,7 @@ from agno.models.openai import OpenAILike as AgnoOpenAILikeModel from agno.models.openrouter import OpenRouter as AgnoOpenRouterModel from agno.models.siliconflow import Siliconflow as AgnoSiliconflowModel +from loguru import logger from valuecell.adapters.models.factory import ( create_embedder, @@ -27,7 +27,14 @@ create_model_for_agent, ) -logger = logging.getLogger(__name__) + +def describe_model(model: AgnoModel) -> str: + try: + model_description = f"{model.id} (via {model.provider})" + except Exception: + model_description = "unknown model/provider" + + return model_description def model_should_use_json_mode(model: AgnoModel) -> bool: diff --git a/python/valuecell/utils/ts.py b/python/valuecell/utils/ts.py new file mode 100644 index 000000000..aeda31373 --- /dev/null +++ b/python/valuecell/utils/ts.py @@ -0,0 +1,6 @@ +from datetime import datetime, timezone + + +def get_current_timestamp_ms() -> int: + """Get current timestamp in milliseconds.""" + return int(datetime.now(timezone.utc).timestamp() * 1000)