mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 04:50:57 +08:00
fix: enforce tool-based data only — Agent can no longer fabricate prices
- System prompt now has absolute rule: no tool result = no price - Pre-market overview must call search_stock for each major stock - Index futures explicitly marked as unsupported - Training data prices explicitly declared outdated and forbidden - Applied to both CN and EN prompts
This commit is contained in:
@@ -285,14 +285,20 @@ func (a *Agent) buildSystemPrompt(lang string) string {
|
||||
%s
|
||||
%s
|
||||
|
||||
## 数据说明
|
||||
## 数据说明(极其重要,违反即失职!)
|
||||
- 加密货币(BTC/ETH等):交易所实时数据,标注 [Real-time]
|
||||
- A股/港股/美股:通过 search_stock 工具获取**实时行情**(新浪财经数据源)
|
||||
- 美股支持**盘前盘后数据**:search_stock 返回的 quote 中 ext_price/ext_change_pct/ext_time 为盘前盘后价格
|
||||
- 当返回数据包含 is_ext_hours=true 时,**必须**向用户展示盘前/盘后价格
|
||||
- 外汇:没有实时数据,需要说明
|
||||
- 对于没有实时数据的标的,严禁编造具体价格
|
||||
- **有数据就给数据,没有才说没有。诚实但不过度声明。**
|
||||
- A股/港股/美股:**必须调用 search_stock 工具**获取实时行情。不调工具就没有数据。
|
||||
- 美股盘前盘后:search_stock 返回的 quote 中 ext_price/ext_change_pct/ext_time
|
||||
- 外汇/指数期货:当前没有数据源,如实告知
|
||||
|
||||
### 铁律:禁止编造任何价格!
|
||||
- **你的训练数据中的价格全部过时,不可使用**
|
||||
- **没有通过工具获取的价格 = 你不知道 = 不能说**
|
||||
- 用户问多只股票的盘前数据?→ 对每只股票调用 search_stock 工具
|
||||
- 用户问"盘前概览"?→ 调用 search_stock 查主要股票(AAPL、TSLA、NVDA、MSFT、GOOGL、AMZN、META等),用真实数据回答
|
||||
- **绝对不允许**不调工具就给出具体价格数字(如 $421.85)
|
||||
- 如果某只股票 search_stock 查不到数据,就说"暂时无法获取该股票数据"
|
||||
- 指数期货(纳指、标普、道琼斯期货)我们目前没有数据源,直接说"暂不支持指数期货数据"
|
||||
|
||||
## 工具使用
|
||||
你可以调用以下工具来执行操作:
|
||||
@@ -340,14 +346,20 @@ func (a *Agent) buildSystemPrompt(lang string) string {
|
||||
%s
|
||||
%s
|
||||
|
||||
## Data Notice
|
||||
## Data Notice (CRITICAL — violating this is unacceptable!)
|
||||
- Crypto (BTC/ETH): Exchange real-time data, marked [Real-time]
|
||||
- A-shares/HK/US stocks: Real-time quotes via search_stock tool (Sina Finance data)
|
||||
- US stocks include **pre-market/after-hours data**: ext_price/ext_change_pct/ext_time in quote
|
||||
- When is_ext_hours=true, you MUST show pre-market/after-hours prices to the user
|
||||
- Forex: No real-time data — must inform user
|
||||
- NEVER fabricate prices for assets without data
|
||||
- Show data when you have it. Only disclaim when you don't.
|
||||
- Stocks: You MUST call search_stock tool to get real-time quotes. No tool call = no data.
|
||||
- US stocks pre/after-hours: ext_price/ext_change_pct/ext_time in search_stock results
|
||||
- Forex/Index futures: No data source currently — tell user honestly
|
||||
|
||||
### ABSOLUTE RULE: NEVER fabricate any price!
|
||||
- Your training data prices are ALL outdated and MUST NOT be used
|
||||
- No tool result = you don't know = you cannot state a price
|
||||
- User asks multiple stocks? → Call search_stock for EACH one
|
||||
- User asks "pre-market overview"? → Call search_stock for major stocks (AAPL, TSLA, NVDA, MSFT, GOOGL, AMZN, META etc.) and use real data
|
||||
- NEVER output a specific price number (like $421.85) without a tool having returned it
|
||||
- If search_stock fails for a stock, say "unable to fetch data for this stock"
|
||||
- Index futures (NDX, SPX, DJI futures) — we have no data source, say "index futures not supported yet"
|
||||
|
||||
## Tools
|
||||
You can call these tools to take action:
|
||||
|
||||
@@ -24,6 +24,7 @@ func (s *Scheduler) Start(ctx context.Context) {
|
||||
defer ticker.Stop()
|
||||
lastReport := time.Time{}
|
||||
lastCheck := time.Time{}
|
||||
lastCleanup := time.Time{}
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -40,6 +41,13 @@ func (s *Scheduler) Start(ctx context.Context) {
|
||||
s.riskCheck()
|
||||
lastCheck = now
|
||||
}
|
||||
// Clean stale chat history every hour (sessions idle > 24h)
|
||||
if now.Sub(lastCleanup) > 1*time.Hour {
|
||||
if s.agent.history != nil {
|
||||
s.agent.history.CleanOld(24 * time.Hour)
|
||||
}
|
||||
lastCleanup = now
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
24
agent/web.go
24
agent/web.go
@@ -7,9 +7,16 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// validSymbolRe matches only alphanumeric trading symbols (e.g. BTCUSDT, ETH-USD).
|
||||
var validSymbolRe = regexp.MustCompile(`^[A-Za-z0-9\-_]{1,20}$`)
|
||||
|
||||
// validIntervalRe matches only valid kline intervals (e.g. 1m, 5m, 1h, 4h, 1d, 1w).
|
||||
var validIntervalRe = regexp.MustCompile(`^[0-9]{1,2}[mhHdDwWM]$`)
|
||||
|
||||
// WebHandler provides HTTP endpoints for the NOFXi agent.
|
||||
type WebHandler struct {
|
||||
agent *Agent
|
||||
@@ -36,7 +43,8 @@ func (w *WebHandler) HandleChat(rw http.ResponseWriter, r *http.Request) {
|
||||
UserID int64 `json:"user_id"`
|
||||
Lang string `json:"lang"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
// Limit request body to 64KB to prevent abuse
|
||||
if err := json.NewDecoder(io.LimitReader(r.Body, 64*1024)).Decode(&req); err != nil {
|
||||
writeJSON(rw, 400, map[string]string{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
@@ -70,6 +78,15 @@ func (w *WebHandler) HandleKlines(rw http.ResponseWriter, r *http.Request) {
|
||||
interval := r.URL.Query().Get("interval")
|
||||
if interval == "" { interval = "1h" }
|
||||
|
||||
if !validSymbolRe.MatchString(symbol) {
|
||||
writeJSON(rw, 400, map[string]string{"error": "invalid symbol"})
|
||||
return
|
||||
}
|
||||
if !validIntervalRe.MatchString(interval) {
|
||||
writeJSON(rw, 400, map[string]string{"error": "invalid interval"})
|
||||
return
|
||||
}
|
||||
|
||||
proxyBinance(rw, fmt.Sprintf("https://fapi.binance.com/fapi/v1/klines?symbol=%s&interval=%s&limit=300", symbol, interval))
|
||||
}
|
||||
|
||||
@@ -78,6 +95,11 @@ func (w *WebHandler) HandleTicker(rw http.ResponseWriter, r *http.Request) {
|
||||
symbol := r.URL.Query().Get("symbol")
|
||||
if symbol == "" { symbol = "BTCUSDT" }
|
||||
|
||||
if !validSymbolRe.MatchString(symbol) {
|
||||
writeJSON(rw, 400, map[string]string{"error": "invalid symbol"})
|
||||
return
|
||||
}
|
||||
|
||||
proxyBinance(rw, fmt.Sprintf("https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=%s", symbol))
|
||||
}
|
||||
|
||||
|
||||
8
main.go
8
main.go
@@ -30,10 +30,10 @@ func main() {
|
||||
// Initialize logger
|
||||
logger.Init(nil)
|
||||
|
||||
logger.Info("╔════════════════════════════════════════════════════════════╗")
|
||||
logger.Info("║ 🚀 NOFXi - AI Trading Agent ║")
|
||||
logger.Info("║ Powered by NOFX Engine ║")
|
||||
logger.Info("╚════════════════════════════════════════════════════════════╝")
|
||||
logger.Info("╔══════════════════════════════════════════════════╗")
|
||||
logger.Info("║ 🚀 NOFXi - AI Trading Agent ║")
|
||||
logger.Info("║ Powered by NOFX Engine ║")
|
||||
logger.Info("╚══════════════════════════════════════════════════╝")
|
||||
|
||||
// Initialize global configuration (loaded from .env)
|
||||
config.Init()
|
||||
|
||||
Reference in New Issue
Block a user