mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-18 09:54:35 +08:00
feat(agent): make the assistant agentic - visible tools, LLM voice, full toolset
The agent felt like an artificial idiot because the LLM almost never spoke
for itself: 14+ Go paths injected fmt.Sprintf canned replies, the frontend
filtered out tool-progress events so users saw three dots for 10-20s, the
main prompt told the LLM "be a trading partner" AND "answer only what's
asked", and the planner sliced the toolset by inferred domain so a "BTC
dropped, how much am I losing?" question couldn't see positions and market
at the same time.
- agent/central_brain.go: shouldTrustDeterministicSkillReply now always
returns false. Successful mutations (trader/strategy/model/exchange
create/update/start/stop/delete) flow through reviewTaskCompletion so the
LLM sees the real outcome JSON and writes the user-facing prose. The
trade-confirmation regex path (handleTradeConfirmation) was already
outside this code path and is unaffected.
- agent/agent.go: rewrite the Behavior section of the main system prompt.
Replace the contradictory "answer only what's asked / don't upsell" with
"lead with the direct answer, then optionally one relevant follow-up
only when (a) open risk, (b) missing config, or (c) the next step is
obvious — e.g. created, want me to start it?". Explicitly authorize
chaining ("if the user says create and start, do both this turn") and
ban "please wait / I'll get back to you" language because there is no
background job to come back from.
- agent/tools.go: plannerToolsForText always returns the full 22-tool set
(new __all__ domain). The old per-domain trimming hid manage_trader from
market questions and execute_trade from anything that didn't look like
an explicit trade — cross-domain reasoning was structurally blocked. The
compact-vs-full strategy schema switch is preserved so mutation intents
still see the full config schema.
- web/src/components/agent/{AgentStepPanel,ChatMessages}.tsx: stop
filtering tool: steps. Map raw tool names to friendly labels with emoji
("get_positions" → "📊 检查持仓") in zh/en/id. Users now see what the
agent is doing in real time instead of silence. central_brain routing
chatter still gets dropped.
- agent/planner_tools_test.go: tests updated to assert the new
full-toolset behavior and the compact-vs-full strategy schema switch.
This commit is contained in:
@@ -7,34 +7,53 @@ import (
|
||||
"nofx/mcp"
|
||||
)
|
||||
|
||||
func TestPlannerToolsForMarketIntentAreTrimmed(t *testing.T) {
|
||||
// plannerToolsForText now always returns the FULL toolset (no per-domain
|
||||
// trimming) so the LLM can cross-domain reason. The old "if market intent,
|
||||
// hide manage_trader" filter was making cross-domain questions like "BTC
|
||||
// dropped, how much am I losing?" impossible to answer because the agent
|
||||
// couldn't see both market AND position tools in the same turn.
|
||||
//
|
||||
// We still trim the giant strategy schema for non-mutation intents because
|
||||
// that one is genuinely huge and uninformative for read-only use.
|
||||
|
||||
func TestPlannerToolsExposeFullSetForMarketIntent(t *testing.T) {
|
||||
tools := plannerToolsForText("看一下 BTCUSDT 行情和 K线")
|
||||
names := toolNamesForTest(tools)
|
||||
|
||||
// Market tools must be present.
|
||||
for _, expected := range []string{"get_market_snapshot", "get_market_price", "get_kline"} {
|
||||
if !containsString(names, expected) {
|
||||
t.Fatalf("expected market tool %q in %v", expected, names)
|
||||
}
|
||||
}
|
||||
for _, unexpected := range []string{"manage_strategy", "manage_trader", "manage_exchange_config", "manage_model_config"} {
|
||||
if containsString(names, unexpected) {
|
||||
t.Fatalf("did not expect management tool %q in market tools %v", unexpected, names)
|
||||
// Cross-domain tools (positions, balance, trader management) must ALSO be
|
||||
// present so the agent can answer "how much am I losing" follow-ups
|
||||
// without losing the market context.
|
||||
for _, expected := range []string{"get_positions", "get_balance", "manage_trader"} {
|
||||
if !containsString(names, expected) {
|
||||
t.Fatalf("expected cross-domain tool %q in market context %v", expected, names)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlannerToolsForExchangeIntentAreTrimmed(t *testing.T) {
|
||||
func TestPlannerToolsExposeFullSetForExchangeIntent(t *testing.T) {
|
||||
tools := plannerToolsForText("帮我添加 okx 交易所 API key")
|
||||
names := toolNamesForTest(tools)
|
||||
|
||||
if len(names) != 2 {
|
||||
t.Fatalf("expected two exchange tools, got %v", names)
|
||||
}
|
||||
// At least the exchange management tools must show up.
|
||||
for _, expected := range []string{"get_exchange_configs", "manage_exchange_config"} {
|
||||
if !containsString(names, expected) {
|
||||
t.Fatalf("expected exchange tool %q in %v", expected, names)
|
||||
}
|
||||
}
|
||||
// And the agent still has the broader surface available — adding an
|
||||
// exchange often leads to "now create a trader" so trader/strategy tools
|
||||
// must be reachable in the same turn.
|
||||
for _, expected := range []string{"manage_trader", "get_strategies"} {
|
||||
if !containsString(names, expected) {
|
||||
t.Fatalf("expected adjacent tool %q in exchange context %v", expected, names)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlannerToolsUseCompactManageStrategyForReadIntent(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user