Skip to content

Commit a9638d4

Browse files
authored
Add tavily to search agent (#1166)
1 parent b4fa31d commit a9638d4

File tree

10 files changed

+121
-28
lines changed

10 files changed

+121
-28
lines changed

docs/en/DEPLOY_OPTION.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,15 @@ const envs: Record<string, Partial<StackInput>> = {
405405

406406
Creates an Agent that connects to APIs to reference the latest information for responses. You can customize the Agent to add other actions and create multiple Agents to switch between.
407407

408-
The default search agent uses [Brave Search API's Data for AI](https://brave.com/search/api/) due to its large free tier, request limit considerations, and cost factors, but you can customize it to use other APIs. Getting an API key requires credit card registration even for the free plan.
408+
The default search agents available are [Data for AI in Brave Search API] (https://brave.com/search/api/) or [Tavily's Tavily Search API] (https://docs.tavily.com/documentation/api-reference/endpoint/search). It is also possible to customise the API so that it can be used with other APIs. Please note that Brave Search API requires credit card setup, even for free plans.
409409

410410
> [!NOTE]
411-
> When you enable the Agent Chat use case, it only sends data to external APIs in the Agent Chat use case. (By default, Brave Search API) Other use cases can continue to be used entirely within AWS. Please check your internal policies and API terms of service before enabling.
411+
> When you enable the Agent Chat use case, it only sends data to external APIs in the Agent Chat use case. (By default, Brave Search API or Tavily Search) Other use cases can continue to be used entirely within AWS. Please check your internal policies and API terms of service before enabling.
412412
413-
Set `agentEnabled` and `searchAgentEnabled` to `true` (default is `false`), and specify the search engine API key in `searchApiKey`.
413+
Set `agentEnabled` and `searchAgentEnabled` to `true` (default is `false`), and then set the required fields.
414+
415+
- `searchEngine` : Specify the search engine to use. You can use `Brave` or `Tavily`.
416+
- `searchApiKey` : Specify the API key of the search engine.
414417

415418
**Edit [parameter.ts](/packages/cdk/parameter.ts)**
416419

@@ -420,6 +423,7 @@ const envs: Record<string, Partial<StackInput>> = {
420423
dev: {
421424
agentEnabled: true,
422425
searchAgentEnabled: true,
426+
searchEngine: 'Brave' or 'Tavily',
423427
searchApiKey: '<Search Engine API Key>',
424428
},
425429
};
@@ -433,6 +437,7 @@ const envs: Record<string, Partial<StackInput>> = {
433437
"context": {
434438
"agentEnabled": true,
435439
"searchAgentEnabled": true,
440+
"searchEngine": "Brave" or "Tavily",
436441
"searchApiKey": "<Search Engine API Key>"
437442
}
438443
}

docs/ja/DEPLOY_OPTION.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,12 +420,15 @@ const envs: Record<string, Partial<StackInput>> = {
420420

421421
API と連携し最新情報を参照して回答する Agent を作成します。Agent のカスタマイズを行い他のアクションを追加できるほか、複数の Agent を作成し切り替えることが可能です。
422422

423-
デフォルトで使用できる検索エージェントでは、無料利用枠の大きさ・リクエスト数の制限・コストの観点から [Brave Search API の Data for AI](https://brave.com/search/api/) を使用していますが、他の API にカスタマイズすることも可能です。API キーの取得はフリープランでもクレジットカードの登録が必要になります
423+
デフォルトで使用できる検索エージェントでは、 [Brave Search API の Data for AI](https://brave.com/search/api/) [Tavily の Tavily Search API](https://docs.tavily.com/documentation/api-reference/endpoint/search) を利用します。他の API を利用するようにカスタマイズすることも可能です。Brave Search API は、無料プランでもクレジットカードの設定が必要なのでご注意ください
424424

425425
> [!NOTE]
426-
> Agent チャットユースケースを有効化すると Agent チャットユースケースでのみ外部 API にデータを送信します。(デフォルトでは Brave Search API)他のユースケースは引き続き AWS 内のみに閉じて利用することが可能です。社内ポリシー、API の利用規約などを確認してから有効化してください。
426+
> Agent チャットユースケースを有効化すると Agent チャットユースケースでのみ外部 API にデータを送信します。(デフォルトでは Brave Search API か Tavily Search API)他のユースケースは引き続き AWS 内のみに閉じて利用することが可能です。社内ポリシー、API の利用規約などを確認してから有効化してください。
427427
428-
`agentEnabled``searchAgentEnabled``true` を指定し(デフォルトは `false`)、`searchApiKey` に検索エンジンの API キーを指定します。
428+
`agentEnabled``searchAgentEnabled``true` (デフォルトは `false`) を指定した上で、必要な項目を設定してください。
429+
430+
- `searchEngine` : 利用する検索エンジンを指定してください。`Brave``Tavily` が利用できます。
431+
- `searchApiKey` : 検索エンジンの API キーを指定します。
429432

430433
**[parameter.ts](/packages/cdk/parameter.ts) を編集**
431434

@@ -435,6 +438,7 @@ const envs: Record<string, Partial<StackInput>> = {
435438
dev: {
436439
agentEnabled: true,
437440
searchAgentEnabled: true,
441+
searchEngine: 'Brave' or 'Tavily',
438442
searchApiKey: '<検索エンジンの API キー>',
439443
},
440444
};
@@ -448,6 +452,7 @@ const envs: Record<string, Partial<StackInput>> = {
448452
"context": {
449453
"agentEnabled": true,
450454
"searchAgentEnabled": true,
455+
"searchEngine": "Brave" or "Tavily",
451456
"searchApiKey": "<検索エンジンの API キー>"
452457
}
453458
}

packages/cdk/cdk.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"endpointNames": [],
5757
"agentEnabled": false,
5858
"searchAgentEnabled": false,
59+
"searchEngine": "Brave",
5960
"searchApiKey": "",
6061
"agents": [],
6162
"inlineAgents": false,

packages/cdk/lambda/agent.ts

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,69 @@ import {
22
AgentInput,
33
AgentOutput,
44
BraveSearchResult,
5+
TavilySearchResult,
56
} from 'generative-ai-use-cases';
7+
import { StackInput } from '../lib/stack-input';
8+
9+
type SearchResult = {
10+
title: string;
11+
content: string;
12+
url: string;
13+
extraSnippets?: string[];
14+
};
15+
16+
const searchUsingBrave = async (keyword: string): Promise<SearchResult[]> => {
17+
// https://api-dashboard.search.brave.com/app/documentation/web-search/get-started
18+
const searchUrl = `https://api.search.brave.com/res/v1/web/search?q=${keyword}&count=3&text_decorations=0`;
19+
const searchApiKey = process.env.SEARCH_API_KEY || '';
20+
const response = await fetch(searchUrl, {
21+
headers: {
22+
'X-Subscription-Token': searchApiKey,
23+
},
24+
});
25+
const data = await response.json();
26+
console.log(JSON.stringify(data));
27+
28+
return data.web.results.map(
29+
(result: BraveSearchResult): SearchResult => ({
30+
title: result.title,
31+
content: result.description,
32+
url: result.url,
33+
extraSnippets: result.extra_snippets,
34+
})
35+
);
36+
};
37+
38+
const searchUsingTavily = async (keyword: string): Promise<SearchResult[]> => {
39+
const searchUrl = 'https://api.tavily.com/search';
40+
const searchApiKey = process.env.SEARCH_API_KEY || '';
41+
42+
// https://docs.tavily.com/documentation/api-reference/endpoint/search
43+
const response = await fetch(searchUrl, {
44+
method: 'POST',
45+
headers: {
46+
'Content-Type': 'application/json',
47+
Authorization: `Bearer ${searchApiKey}`,
48+
},
49+
body: JSON.stringify({
50+
query: keyword,
51+
search_depth: 'basic',
52+
include_answer: false,
53+
include_images: false,
54+
include_raw_content: true,
55+
max_results: 3,
56+
}),
57+
});
58+
59+
const data = await response.json();
60+
console.log(JSON.stringify(data));
61+
62+
return data.results.map((result: TavilySearchResult) => ({
63+
title: result.title,
64+
content: result.raw_content ?? result.content,
65+
url: result.url,
66+
}));
67+
};
668

769
export const handler = async (event: AgentInput): Promise<AgentOutput> => {
870
try {
@@ -15,23 +77,13 @@ export const handler = async (event: AgentInput): Promise<AgentOutput> => {
1577
}
1678
}
1779

18-
// Search
19-
const searchUrl = `https://api.search.brave.com/res/v1/web/search?q=${keyword}&count=3&text_decorations=0`;
20-
const searchApiKey = process.env.SEARCH_API_KEY || '';
21-
const response = await fetch(searchUrl, {
22-
headers: {
23-
'X-Subscription-Token': searchApiKey,
24-
},
25-
});
26-
const data = await response.json();
27-
console.log(JSON.stringify(data));
80+
const searchEngine = process.env
81+
.SEARCH_ENGINE as StackInput['searchEngine'];
2882

29-
const results = data.web.results.map((result: BraveSearchResult) => ({
30-
title: result.title,
31-
description: result.description,
32-
extra_snippets: result.extra_snippets,
33-
url: result.url,
34-
}));
83+
const results =
84+
searchEngine === 'Brave'
85+
? await searchUsingBrave(keyword)
86+
: await searchUsingTavily(keyword);
3587

3688
// Create Response Object
3789
const response_body = {

packages/cdk/lib/agent-stack.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ export class AgentStack extends Stack {
1414
constructor(scope: Construct, id: string, props: AgentStackProps) {
1515
super(scope, id, props);
1616

17-
const { searchAgentEnabled, searchApiKey } = props.params;
17+
const { searchAgentEnabled, searchApiKey, searchEngine } = props.params;
1818

1919
const agent = new Agent(this, 'Agent', {
2020
searchAgentEnabled,
2121
searchApiKey,
22+
searchEngine,
2223
});
2324

2425
this.agents = agent.agents;

packages/cdk/lib/construct/agent.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment';
1717
import { CfnAgent, CfnAgentAlias } from 'aws-cdk-lib/aws-bedrock';
1818
import { Agent as AgentType } from 'generative-ai-use-cases';
1919
import { LAMBDA_RUNTIME_NODEJS } from '../../consts';
20+
import { StackInput } from '../stack-input';
2021

2122
interface AgentProps {
2223
// Context Params
2324
readonly searchAgentEnabled: boolean;
2425
readonly searchApiKey?: string | null;
26+
readonly searchEngine?: StackInput['searchEngine'];
2527
}
2628

2729
export class Agent extends Construct {
@@ -32,7 +34,7 @@ export class Agent extends Construct {
3234

3335
const suffix = Lazy.string({ produce: () => Names.uniqueId(this) });
3436

35-
const { searchAgentEnabled, searchApiKey } = props;
37+
const { searchAgentEnabled, searchApiKey, searchEngine } = props;
3638

3739
// Bucket to store schema and data for agents for bedrock
3840
const s3Bucket = new Bucket(this, 'Bucket', {
@@ -76,7 +78,7 @@ export class Agent extends Construct {
7678
});
7779

7880
// Search Agent
79-
if (searchAgentEnabled && searchApiKey) {
81+
if (searchAgentEnabled && searchApiKey && searchEngine) {
8082
const bedrockAgentLambda = new NodejsFunction(
8183
this,
8284
'BedrockAgentLambda',
@@ -86,6 +88,7 @@ export class Agent extends Construct {
8688
timeout: Duration.seconds(300),
8789
environment: {
8890
SEARCH_API_KEY: searchApiKey ?? '',
91+
SEARCH_ENGINE: searchEngine,
8992
},
9093
}
9194
);

packages/cdk/lib/stack-input.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { z } from 'zod';
22

3-
// Common Validator
4-
export const stackInputSchema = z.object({
3+
// Base schema without refine
4+
const baseStackInputSchema = z.object({
55
account: z.string().default(process.env.CDK_DEFAULT_ACCOUNT ?? ''),
66
region: z.string().default(process.env.CDK_DEFAULT_REGION ?? 'us-east-1'),
77
env: z.string().default(''),
@@ -125,6 +125,7 @@ export const stackInputSchema = z.object({
125125
agentEnabled: z.boolean().default(false),
126126
searchAgentEnabled: z.boolean().default(false),
127127
searchApiKey: z.string().nullish(),
128+
searchEngine: z.enum(['Brave', 'Tavily']).default('Brave'),
128129
agents: z
129130
.array(
130131
z.object({
@@ -164,8 +165,23 @@ export const stackInputSchema = z.object({
164165
dashboard: z.boolean().default(false),
165166
});
166167

168+
// Common Validator with refine
169+
export const stackInputSchema = baseStackInputSchema.refine(
170+
(data) => {
171+
// If searchApiKey is provided, searchEngine must also be provided
172+
if (data.searchApiKey && !data.searchEngine) {
173+
return false;
174+
}
175+
return true;
176+
},
177+
{
178+
message: 'searchEngine is required when searchApiKey is provided',
179+
path: ['searchEngine'],
180+
}
181+
);
182+
167183
// schema after conversion
168-
export const processedStackInputSchema = stackInputSchema.extend({
184+
export const processedStackInputSchema = baseStackInputSchema.extend({
169185
modelIds: z.array(
170186
z.object({
171187
modelId: z.string(),

packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,7 @@ exports[`GenerativeAiUseCases matches the snapshot 3`] = `
11091109
"Environment": {
11101110
"Variables": {
11111111
"SEARCH_API_KEY": "XXXXXX",
1112+
"SEARCH_ENGINE": "Brave",
11121113
},
11131114
},
11141115
"Handler": "index.handler",

packages/cdk/test/generative-ai-use-cases.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe('GenerativeAiUseCases', () => {
4747
endpointNames: [],
4848
agentEnabled: true,
4949
searchAgentEnabled: true,
50+
searchEngine: 'Brave',
5051
searchApiKey: 'XXXXXX',
5152
agents: [],
5253
flows: [],

packages/types/src/agent.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,11 @@ export type BraveSearchResult = {
3636
description: string;
3737
extra_snippets?: string[];
3838
};
39+
40+
export type TavilySearchResult = {
41+
title: string;
42+
url: string;
43+
content: string;
44+
score: number;
45+
raw_content?: string;
46+
};

0 commit comments

Comments
 (0)