Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,9 @@ exposes these tools to the AI:
toggle specific groups (e.g. enable only `economic-calendars` for
fundamentals) or the whole-internet `web-search` platform to tailor research
scope.
- **`run_web_search`** — searches the public Internet directly from chat for
current rates, news, fiat/forex pairs, or anything outside the tracked crypto
symbols, then uses those results to answer the user.
- **`run_ratings_adjust`** — modifies a source's credibility rating. Parameters:
`source` (title or ID) and `grade` (integer, e.g. `-1` = −1%). Triggered by
user feedback like *"Хабр -1"* or *"понизь рейтинг Хабру"*.
Expand Down
18 changes: 18 additions & 0 deletions src/cli/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,24 @@ export function App({ app, version, apiUrl, promptOperatingMode }: AppProps): Re
openCurrencySelector();
return 'Currency selector opened — pick a currency.';
}
case 'run_web_search': {
const query = ((toolArgs.query as string) || '').trim();
const requestedLimit = typeof toolArgs.limit === 'number'
? toolArgs.limit
: Number(toolArgs.limit);
const limit = Number.isFinite(requestedLimit) && requestedLimit > 0
? Math.min(10, Math.floor(requestedLimit))
: 5;
if (!query) return 'Error: query is required for web search.';
const results = await app.search(query, limit);
return JSON.stringify(results.map((result) => ({
title: result.title,
url: result.url,
snippet: result.snippet,
source: result.source,
score: result.score,
})));
}
case 'run_help': {
const text = COMMANDS.map(c => ` ${c.name.padEnd(12)} ${c.summary}`).join('\n');
return `Available commands:\n${text}`;
Expand Down
18 changes: 18 additions & 0 deletions src/services/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,21 @@ const TOOL_DEFINITIONS: { type: string; function: Record<string, unknown> }[] =
},
},
},
{
type: 'function',
function: {
name: 'run_web_search',
description: 'Search the public Internet for current information, exchange rates, news, or topics outside the tracked crypto symbols.',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query, e.g. "RUB/USD exchange rate today"' },
limit: { type: 'integer', description: 'Maximum number of results to return (1-10). Defaults to 5.' },
},
required: ['query'],
},
},
},
{
type: 'function',
function: {
Expand Down Expand Up @@ -253,12 +268,15 @@ const SYSTEM_PROMPT = `Ты — AI-ассистент торговой сист
- get_timeframe — узнать текущий таймфрейм
- get_mode — узнать текущий режим торговли
- get_currency — узнать список отслеживаемых символов
- run_web_search — найти актуальную информацию в интернете, включая фиатные/форекс-курсы, новости и темы вне отслеживаемых криптосимволов
- run_serching_level — установить глубину поиска (normal / high / max)
- run_serching_platforms — выбрать группы источников для краулинга
- run_ratings_adjust — изменить рейтинг источника (grade: -1 = -1%, +5 = +5%). Нужен source (название или ID) и grade (целое число).

ВАЖНО ПРО РЕЙТИНГИ: Когда пользователь говорит что-то вроде "Хабр -1", "понизь Хабр", "Хабр плохой источник", "уменьши рейтинг Хабру" или аналогично — используй run_ratings_adjust. Параметр source — это название источника (например "Хабр", "Investing.com", "Reddit CryptoCurrency"), а grade — это целое число со знаком: -1, -2, +2, +5 и т.д. Если пользователь не указал конкретное число, спроси или используй -1 по умолчанию для понижения и +1 для повышения.

Если пользователь просит актуальный курс, свежие новости, данные по фиатным/форекс-парам или другой информации, которой нет среди отслеживаемых символов — сначала используй run_web_search, а затем отвечай по найденным результатам. Не отказывайся только из-за того, что пара не входит в криптовалютные символы Tradefast.

Если пользователь просит сделать анализ рынка, показать статус или что-то подобное — используй соответствующий инструмент. Прежде чем спрашивать пользователя о текущих настройках — сначала сам проверь их через get_* инструменты.

Когда ты выполняешь команду — результат возвращается тебе, ты сам его анализируешь и объясняешь пользователю. Дополнительный запрос на валидацию не нужен.
Expand Down
73 changes: 73 additions & 0 deletions tests/cli.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,79 @@ describe('command autocomplete', () => {
expect(lastFrame()).toContain('type a command');
unmount();
});

it('lets AI chat search the internet directly for current non-tracked rates', async () => {
const previousKey = process.env.TRADEFAST_AI_API_KEY;
const previousUrl = process.env.TRADEFAST_AI_API_URL;
process.env.TRADEFAST_AI_API_KEY = 'test-key';
process.env.TRADEFAST_AI_API_URL = 'http://127.0.0.1/v1/chat/completions';

const search = vi.fn().mockResolvedValue([
{
query: 'RUB/USD exchange rate',
source: 'web-search',
title: 'USD/RUB Exchange Rate',
url: 'https://example.com/rub-usd',
snippet: '1 USD equals 90 RUB',
score: 1,
},
]);
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(JSON.stringify({
choices: [
{
message: {
content: null,
tool_calls: [
{
id: 'call_1',
type: 'function',
function: {
name: 'run_web_search',
arguments: JSON.stringify({ query: 'RUB/USD exchange rate', limit: 3 }),
},
},
],
},
},
],
})))
.mockResolvedValueOnce(new Response(JSON.stringify({
choices: [{ message: { content: 'По веб-поиску: 1 USD примерно равен 90 RUB.' } }],
})));
vi.stubGlobal('fetch', fetchMock);

const tallStdout = { rows: 80, columns: 120, write: () => {}, on: () => {}, removeListener: () => {} } as any;
const app = { ...fakeApp, search } as unknown as Tradefast;
const { lastFrame, stdin, unmount } = render(
<App app={app} version="0.0.0-test" apiUrl="http://127.0.0.1:8787/graphql" />,
{ stdout: tallStdout },
);

try {
await new Promise((resolve) => setTimeout(resolve, 10));
for (const ch of 'rub/usd rate') {
stdin.write(ch);
await new Promise((resolve) => setTimeout(resolve, 1));
}
stdin.write('\r');
await new Promise((resolve) => setTimeout(resolve, 50));

const firstBody = JSON.parse((fetchMock.mock.calls[0]?.[1] as RequestInit).body as string);
const toolNames = firstBody.tools.map((tool: { function: { name: string } }) => tool.function.name);
expect(toolNames).toContain('run_web_search');
expect(search).toHaveBeenCalledWith('RUB/USD exchange rate', 3);
expect(lastFrame()).toContain('По веб-поиску');
} finally {
unmount();
vi.unstubAllGlobals();
if (previousKey === undefined) delete process.env.TRADEFAST_AI_API_KEY;
else process.env.TRADEFAST_AI_API_KEY = previousKey;
if (previousUrl === undefined) delete process.env.TRADEFAST_AI_API_URL;
else process.env.TRADEFAST_AI_API_URL = previousUrl;
}
});
});

describe('cli themes', () => {
Expand Down
Loading