├── .gitignore
├── LICENSE
├── README.md
├── assets
├── phidata-agent-serverless-basic-api.png
└── phidata-agent-serverless-bedrock-api.png
├── next-js-react-chat-template
└── phidata-ui
│ ├── .env-template
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── bun.lockb
│ ├── components.json
│ ├── next.config.mjs
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── postcss.config.mjs
│ ├── public
│ └── phi_logo.png
│ ├── src
│ ├── app
│ │ ├── data.tsx
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components
│ │ ├── chat
│ │ │ ├── chat-bottombar.tsx
│ │ │ ├── chat-layout.tsx
│ │ │ ├── chat-list.tsx
│ │ │ ├── chat.tsx
│ │ │ └── expandable-textarea.tsx
│ │ ├── emoji-picker.tsx
│ │ └── ui
│ │ │ ├── avatar.tsx
│ │ │ ├── button.tsx
│ │ │ ├── header.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── tooltip.tsx
│ │ │ └── user-avatar.tsx
│ ├── lib
│ │ └── utils.ts
│ └── utils
│ │ └── api.ts
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── phidata-agent-serverless-api-basic
├── .env-template
├── .gitignore
├── .npmignore
├── README.md
├── bin
│ └── phidata-agent-serverless-api-basic.ts
├── cdk.json
├── jest.config.js
├── lambdas
│ ├── agents
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── src
│ │ │ ├── main.py
│ │ │ └── utils.py
│ ├── initiator
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── src
│ │ │ └── main.py
│ └── poller
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── src
│ │ ├── main.py
│ │ └── utils.py
├── lib
│ └── phidata-agent-serverless-api-basic-stack.ts
├── package-lock.json
├── package.json
├── test
│ ├── .env-template
│ ├── phidata-agent-serverless-api-basic.test.ts
│ ├── requirements.txt
│ └── test.py
└── tsconfig.json
├── phidata-agent-serverless-api-bedrock-memory
├── .gitignore
├── .npmignore
├── README.md
├── bin
│ └── phidata-agent-serverless-api-bedrock-memory.ts
├── cdk.json
├── jest.config.js
├── lambdas
│ ├── agents
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── src
│ │ │ ├── main.py
│ │ │ └── utils.py
│ ├── initiator
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── src
│ │ │ └── main.py
│ └── poller
│ │ ├── Dockerfile
│ │ ├── requirements.txt
│ │ └── src
│ │ ├── main.py
│ │ └── utils.py
├── lib
│ └── phidata-agent-serverless-api-bedrock-memory-stack.ts
├── package-lock.json
├── package.json
├── test
│ ├── .env-template
│ ├── phidata-agent-serverless-api-bedrock-memory.test.ts
│ ├── requirements.txt
│ └── test.py
└── tsconfig.json
└── phidata-agent-serverless-api-bedrock
├── .gitignore
├── .npmignore
├── README.md
├── bin
└── phidata-agent-serverless-api-bedrock.ts
├── cdk.json
├── jest.config.js
├── lambdas
├── agents
│ ├── Dockerfile
│ ├── requirements.txt
│ └── src
│ │ ├── main.py
│ │ └── utils.py
├── initiator
│ ├── Dockerfile
│ ├── requirements.txt
│ └── src
│ │ └── main.py
└── poller
│ ├── Dockerfile
│ ├── requirements.txt
│ └── src
│ ├── main.py
│ └── utils.py
├── lib
└── phidata-agent-serverless-api-bedrock-stack.ts
├── package-lock.json
├── package.json
├── test
├── .env-template
├── phidata-agent-serverless-api-basic.test.ts
├── phidata-agent-serverless-api-bedrock.test.ts
├── requirements.txt
└── test.py
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | venv/
3 | .venv/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phidata AWS Templates
2 |
3 | A collection of AWS templates for deploying AI Agents
4 |
5 | ## Collection:
6 |
7 | - [Phidata OpenAI Agent Serverless API Basic](./phidata-agent-serverless-api-basic/README.md)
8 |
9 | - [Phidata Bedrock Claude 3.5 Sonnet Agent Serverless API](./phidata-agent-serverless-api-bedrock/README.md)
10 |
11 | - [Phidata Bedrock Claude 3.5 Sonnet Agent Serverless API with Memory](./phidata-agent-serverless-api-bedrock-memory/README.md)
12 |
13 | - [Phidata Next JS React Chat Template](./next-js-react-chat-template/phidata-ui/README.md)
14 |
--------------------------------------------------------------------------------
/assets/phidata-agent-serverless-basic-api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agno-agi/aws-templates/4a5bca34f54a6280fdca9eb901298e36eed51828/assets/phidata-agent-serverless-basic-api.png
--------------------------------------------------------------------------------
/assets/phidata-agent-serverless-bedrock-api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agno-agi/aws-templates/4a5bca34f54a6280fdca9eb901298e36eed51828/assets/phidata-agent-serverless-bedrock-api.png
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/.env-template:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_AWS_GATEWAY_API_URL=https://iasdfa.execute-api.us-east-1.amazonaws.com
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 | .env
3 |
4 | # dependencies
5 | /node_modules
6 | /.pnp
7 | .pnp.js
8 | .yarn/install-state.gz
9 |
10 | # testing
11 | /coverage
12 |
13 | # next.js
14 | /.next/
15 | /out/
16 |
17 | # production
18 | /build
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 |
24 | # debug
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
29 | # local env files
30 | .env*.local
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
39 | #amplify-do-not-edit-begin
40 | amplify/\#current-cloud-backend
41 | amplify/.config/local-*
42 | amplify/logs
43 | amplify/mock-data
44 | amplify/mock-api-resources
45 | amplify/backend/amplify-meta.json
46 | amplify/backend/.temp
47 | build/
48 | dist/
49 | node_modules/
50 | aws-exports.js
51 | awsconfiguration.json
52 | amplifyconfiguration.json
53 | amplifyconfiguration.dart
54 | amplify-build-config.json
55 | amplify-gradle-config.json
56 | amplifytools.xcconfig
57 | .secret-*
58 | **.sample
59 | #amplify-do-not-edit-end
60 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/README.md:
--------------------------------------------------------------------------------
1 | # Basic Frontend Framework for testing AWS backend
2 |
3 | A next js frontend framework for testing AWS backend
4 |
5 | ## How to run
6 |
7 | 1. Change directory to `phidata-ui`
8 |
9 | `cd phidata-ui`
10 |
11 | 2. Install dependencies
12 |
13 | `npm install`
14 |
15 | 3. Copy/Paste the URL of the backend API into the `.env` file
16 |
17 | `NEXT_PUBLIC_AWS_GATEWAY_API_URL=https://iasdfa.execute-api.us-east-1.amazonaws.com`
18 |
19 | 4. Run the development server
20 |
21 | `npm run dev`
22 |
23 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agno-agi/aws-templates/4a5bca34f54a6280fdca9eb901298e36eed51828/next-js-react-chat-template/phidata-ui/bun.lockb
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shadcn-chat",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@emoji-mart/data": "^1.1.2",
13 | "@emoji-mart/react": "^1.1.1",
14 | "@heroicons/react": "^1.0.6",
15 | "@radix-ui/react-avatar": "^1.0.4",
16 | "@radix-ui/react-icons": "^1.3.0",
17 | "@radix-ui/react-popover": "^1.0.7",
18 | "@radix-ui/react-scroll-area": "^1.0.5",
19 | "@radix-ui/react-separator": "^1.0.3",
20 | "@radix-ui/react-slot": "^1.0.2",
21 | "@radix-ui/react-tooltip": "^1.0.7",
22 | "class-variance-authority": "^0.7.0",
23 | "clsx": "^2.1.0",
24 | "emoji-mart": "^5.5.2",
25 | "framer-motion": "^10.18.0",
26 | "fuzzyset.js": "^1.0.7",
27 | "geist": "^1.2.1",
28 | "lucide-react": "^0.312.0",
29 | "next": "14.1.0",
30 | "react": "^18",
31 | "react-dom": "^18",
32 | "react-hook-form": "^7.51.5",
33 | "react-markdown": "^9.0.1",
34 | "react-resizable-panels": "^1.0.9",
35 | "tailwind-merge": "^2.2.0",
36 | "tailwindcss-animate": "^1.0.7",
37 | "uuid": "^10.0.0"
38 | },
39 | "devDependencies": {
40 | "@types/fuzzyset.js": "^0.0.5",
41 | "@types/node": "^20.12.7",
42 | "@types/react": "^18",
43 | "@types/react-dom": "^18",
44 | "autoprefixer": "^10.0.1",
45 | "eslint": "^8",
46 | "eslint-config-next": "14.1.0",
47 | "postcss": "^8",
48 | "tailwindcss": "^3.3.0",
49 | "typescript": "^5"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/public/phi_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agno-agi/aws-templates/4a5bca34f54a6280fdca9eb901298e36eed51828/next-js-react-chat-template/phidata-ui/public/phi_logo.png
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/app/data.tsx:
--------------------------------------------------------------------------------
1 | export const userData = [
2 | {
3 | id: 1,
4 | avatar: '/phi_logo.png',
5 | messages: [
6 | {
7 | id: 1,
8 | avatar: '/phi_logo.png',
9 | name: 'Jane Doe',
10 | message: '"Hi!"'
11 | }
12 | ],
13 | name: 'Jane Doe',
14 | }
15 | ];
16 | export type UserData = (typeof userData)[number];
17 |
18 | export const loggedInUserData = {
19 | id: 5,
20 | avatar: '/LoggedInUser.jpg',
21 | name: 'User',
22 | };
23 |
24 | export type LoggedInUserData = (typeof loggedInUserData);
25 |
26 | export interface Message {
27 | id: number;
28 | avatar: string;
29 | name: string;
30 | message: string;
31 | processId?: string; // Optional field to store the processId
32 | }
33 |
34 | export interface User {
35 | id: number;
36 | avatar: string;
37 | messages: Message[];
38 | name: string;
39 | signInDetails: {
40 | loginId: string;
41 | };
42 | }
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agno-agi/aws-templates/4a5bca34f54a6280fdca9eb901298e36eed51828/next-js-react-chat-template/phidata-ui/src/app/favicon.ico
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 240 10% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 240 10% 3.9%;
15 |
16 | --primary: 240 5.9% 10%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 240 4.8% 95.9%;
20 | --secondary-foreground: 240 5.9% 10%;
21 |
22 | --muted: 240 4.8% 95.9%;
23 | --muted-foreground: 240 3.8% 46.1%;
24 |
25 | --accent: 240 4.8% 95.9%;
26 | --accent-foreground: 240 5.9% 10%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border: 240 5.9% 90%;
32 | --input: 240 5.9% 90%;
33 | --ring: 240 10% 3.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 240 10% 3.9%;
40 | --foreground: 0 0% 98%;
41 |
42 | --card: 240 10% 3.9%;
43 | --card-foreground: 0 0% 98%;
44 |
45 | --popover: 240 10% 3.9%;
46 | --popover-foreground: 0 0% 98%;
47 |
48 | --primary: 0 0% 98%;
49 | --primary-foreground: 240 5.9% 10%;
50 |
51 | --secondary: 240 3.7% 15.9%;
52 | --secondary-foreground: 0 0% 98%;
53 |
54 | --muted: 240 3.7% 15.9%;
55 | --muted-foreground: 240 5% 64.9%;
56 |
57 | --accent: 240 3.7% 15.9%;
58 | --accent-foreground: 0 0% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 0 0% 98%;
62 |
63 | --border: 240 3.7% 15.9%;
64 | --input: 240 3.7% 15.9%;
65 | --ring: 240 4.9% 83.9%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border;
72 | }
73 | body {
74 | @apply bg-background text-foreground;
75 | }
76 | }
77 |
78 | .text-gradient {
79 | background: linear-gradient(90deg,#327fb9,rgba(108, 162, 243, 0.8));
80 | -webkit-background-clip: text;
81 | -webkit-text-fill-color: transparent;
82 | background-clip: text
83 | }
84 |
85 | .button:hover {
86 | background-color: theme('colors.blue.700'); /* Darken the button on hover */
87 | transition: background-color 0.3s ease-in-out;
88 | }
89 |
90 | .button:focus {
91 | outline: none;
92 | box-shadow: 0 0 0 3px theme('colors.blue.300'); /* Focus ring around the button */
93 | }
94 |
95 | /* Link hover effects */
96 | a:hover {
97 | text-decoration: underline;
98 | color: theme('colors.blue.600');
99 | }
100 |
101 | @layer base {
102 | :root {
103 | --sign-out-bg: var(--destructive); /* Using the destructive color for a sign-out action */
104 | --sign-out-bg-hover: var(--destructive, #C53030); /* Slightly darker for hover state */
105 | --sign-out-text: var(--destructive-foreground);
106 | --sign-out-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
107 | --sign-out-shadow-hover: 0 4px 6px rgba(0, 0, 0, 0.2);
108 | }
109 |
110 | .dark {
111 | --sign-out-bg: var(--destructive);
112 | --sign-out-bg-hover: var(--destructive, #C53030);
113 | --sign-out-text: var(--destructive-foreground);
114 | --sign-out-shadow: 0 2px 4px rgba(255, 255, 255, 0.1);
115 | --sign-out-shadow-hover: 0 4px 6px rgba(255, 255, 255, 0.2);
116 | }
117 | }
118 |
119 |
120 | .sign-out-btn {
121 | @apply py-2 px-4 font-bold rounded transition duration-300 ease-in-out;
122 | background-color: var(--sign-out-bg);
123 | color: var(--sign-out-text);
124 | box-shadow: var(--sign-out-shadow);
125 | }
126 |
127 | .sign-out-btn:hover, .sign-out-btn:focus {
128 | background-color: var(--sign-out-bg-hover);
129 | box-shadow: var(--sign-out-shadow-hover);
130 | @apply ring-2 ring-red-500 ring-opacity-50;
131 | }
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { GeistSans } from 'geist/font/sans';
3 | import "./globals.css";
4 |
5 | export const metadata: Metadata = {
6 | title: "Phidata UI template",
7 | description: "UI template",
8 | };
9 |
10 | export const viewport = {
11 | width: 'device-width',
12 | initialScale: 1,
13 | maximumScale: 1,
14 | userScalable: 1,
15 | }
16 |
17 | export default function RootLayout({
18 | children,
19 | }: Readonly<{
20 | children: React.ReactNode;
21 | }>) {
22 | return (
23 |
24 |
{children}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React from 'react';
4 | import { ChatLayout } from "@/components/chat/chat-layout";
5 |
6 | function App() {
7 | return (
8 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/chat/chat-bottombar.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Mic,
3 | } from "lucide-react";
4 | import Link from "next/link";
5 | import React, { useRef, useState, useEffect } from "react";
6 | import { buttonVariants } from "../ui/button";
7 | import { cn } from "@/lib/utils";
8 | import { AnimatePresence, motion } from "framer-motion";
9 | import { Message, loggedInUserData, userData } from "@/app/data";
10 | import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
11 | import { testInitiate, testStatus } from "@/utils/api";
12 | import ExpandableTextarea from "./expandable-textarea";
13 | import { v4 as uuidv4 } from 'uuid';
14 |
15 | interface ChatBottombarProps {
16 | sendMessage: (newMessage: Message) => void;
17 | isMobile: boolean;
18 | }
19 |
20 | const useSessionId = () => {
21 | const [sessionId, setSessionId] = useState('');
22 |
23 | useEffect(() => {
24 | setSessionId(uuidv4());
25 | }, []);
26 |
27 | return sessionId;
28 | };
29 |
30 | export default function ChatBottombar({
31 | sendMessage, isMobile,
32 | }: ChatBottombarProps) {
33 | const [message, setMessage] = useState("");
34 | const [isLoading, setIsLoading] = useState(false);
35 | const inputRef = useRef(null);
36 | const sessionId = useSessionId();
37 |
38 | const handleInputChange = (event: React.ChangeEvent) => {
39 | setMessage(event.target.value);
40 | };
41 |
42 | const handleSend = async () => {
43 | if (message.trim()) {
44 | setIsLoading(true);
45 |
46 | const trimmedMessage = message.trim();
47 | const newMessage: Message = {
48 | id: trimmedMessage.length + 1,
49 | name: loggedInUserData.name,
50 | avatar: loggedInUserData.avatar,
51 | message: trimmedMessage,
52 | };
53 |
54 | sendMessage(newMessage);
55 | setMessage("");
56 |
57 | if (inputRef.current) {
58 | inputRef.current.focus();
59 | }
60 |
61 | try {
62 | await handleLongRunningProcess(trimmedMessage, sessionId);
63 | } catch (error) {
64 | console.error("Error during API calls:", error);
65 | } finally {
66 | setIsLoading(false);
67 | }
68 | }
69 | };
70 |
71 | async function handleLongRunningProcess(question: string, sessionId: string): Promise {
72 | const processId = await testInitiate(question, sessionId);
73 | if (processId) {
74 | const result = await testStatus(processId);
75 | if (result) {
76 | sendResponseMessage(result, processId);
77 | }
78 | }
79 | }
80 |
81 | function sendResponseMessage(response: string, processId?: string) {
82 | const responseMessage: Message = {
83 | id: userData[0].messages.length + 1,
84 | name: userData[0].name,
85 | avatar: userData[0].avatar,
86 | message: response,
87 | processId,
88 | };
89 | userData[0].messages.push(responseMessage);
90 | sendMessage(responseMessage);
91 | }
92 |
93 | const handleKeyPress = (event: React.KeyboardEvent) => {
94 | if (event.key === "Enter" && !event.shiftKey) {
95 | event.preventDefault();
96 | handleSend();
97 | }
98 |
99 | if (event.key === "Enter" && event.shiftKey) {
100 | event.preventDefault();
101 | setMessage((prev) => prev + "\n");
102 | }
103 | };
104 |
105 | return (
106 |
107 |
108 |
109 |
110 |
118 |
119 |
120 |
123 | {message.trim() || isMobile ? (
124 |
125 |
133 |
134 |
135 |
136 | ) : (
137 |
145 |
146 |
147 | )}
148 |
149 |
150 |
151 |
152 |
153 |
168 |
175 |
176 |
177 |
178 | );
179 | }
180 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/chat/chat-layout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { userData } from "@/app/data";
4 | import React, { useEffect, useState } from "react";
5 | import { Chat } from "./chat";
6 |
7 | interface ChatLayoutProps {
8 | navCollapsedSize: number;
9 | className?: string;
10 | defaultCollapsed?: boolean;
11 | }
12 |
13 | export function ChatLayout({}: ChatLayoutProps) {
14 | const [selectedUser] = React.useState(userData[0]);
15 | const [isMobile, setIsMobile] = useState(false);
16 |
17 | useEffect(() => {
18 | const checkScreenWidth = () => {
19 | setIsMobile(window.innerWidth <= 768);
20 | };
21 |
22 | // Initial check
23 | checkScreenWidth();
24 |
25 | // Event listener for screen width changes
26 | window.addEventListener("resize", checkScreenWidth);
27 |
28 | // Cleanup the event listener on component unmount
29 | return () => {
30 | window.removeEventListener("resize", checkScreenWidth);
31 | };
32 | }, []);
33 |
34 | return (
35 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/chat/chat-list.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { Message } from "@/app/data";
3 | import { Avatar, AvatarImage } from "../ui/avatar";
4 | import ChatBottombar from "./chat-bottombar";
5 | import { AnimatePresence, motion } from "framer-motion";
6 | import ReactMarkdown from 'react-markdown';
7 |
8 | interface ChatListProps {
9 | messages?: Message[];
10 | sendMessage: (newMessage: Message) => void;
11 | isMobile: boolean;
12 | }
13 |
14 | function parseMessage(message: string): string {
15 | try {
16 | const parsed = JSON.parse(message);
17 | return typeof parsed === 'string' ? parsed : JSON.stringify(parsed);
18 | } catch (e) {
19 | if (message.startsWith('"') && message.endsWith('"')) {
20 | return message.slice(1, -1).replace(/\\n/g, '\n').replace(/\\/g, '');
21 | }
22 | return message;
23 | }
24 | }
25 |
26 | export function ChatList({
27 | messages,
28 | sendMessage,
29 | isMobile,
30 | }: ChatListProps) {
31 | const messagesContainerRef = useRef(null);
32 | useEffect(() => {
33 | if (messagesContainerRef.current) {
34 | messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
35 | }
36 | }, [messages]);
37 |
38 | return (
39 |
40 |
41 |
42 | {messages?.map((message, index) => (
43 |
59 |
60 |
61 |
62 |
63 |
66 | ,
69 | h2: ({node, ...props}) => ,
70 | p: ({node, ...props}) =>
,
71 | ul: ({node, ...props}) => ,
72 | ol: ({node, ...props}) => ,
73 | li: ({node, ...props}) => ,
74 | }}
75 | >
76 | {parseMessage(message.message)}
77 |
78 |
79 |
80 |
81 | ))}
82 |
83 |
84 |
85 |
86 | );
87 | }
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/chat/chat.tsx:
--------------------------------------------------------------------------------
1 | import { Message } from "@/app/data";
2 | import { ChatList } from "./chat-list";
3 | import React from "react";
4 |
5 | interface ChatProps {
6 | messages?: Message[];
7 | isMobile: boolean;
8 | }
9 |
10 | export function Chat({
11 | messages,
12 | isMobile,
13 | }: ChatProps) {
14 | const [messagesState, setMessages] = React.useState(
15 | messages ?? []
16 | );
17 |
18 | const sendMessage = (newMessage: Message) => {
19 | setMessages(prevMessages => [...prevMessages, newMessage]);
20 | };
21 |
22 | return (
23 |
24 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/chat/expandable-textarea.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect, ChangeEvent, KeyboardEvent } from "react";
2 | import { Textarea } from "@/components/ui/textarea";
3 | import { SendHorizontal } from "lucide-react";
4 |
5 | interface ExpandableTextareaProps {
6 | value: string;
7 | onChange: (event: ChangeEvent) => void;
8 | onSend: () => void;
9 | placeholder?: string;
10 | isLoading?: boolean;
11 | }
12 |
13 | const ExpandableTextarea: React.FC = ({
14 | value,
15 | onChange,
16 | onSend,
17 | placeholder = "Type a message...",
18 | isLoading = false
19 | }) => {
20 | const textareaRef = useRef(null);
21 | const [textareaHeight, setTextareaHeight] = useState("auto");
22 |
23 | useEffect(() => {
24 | if (textareaRef.current) {
25 | textareaRef.current.style.height = "auto";
26 | textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
27 | }
28 | }, [value]);
29 |
30 | const handleChange = (event: ChangeEvent) => {
31 | setTextareaHeight("auto");
32 | onChange(event);
33 | };
34 |
35 | const handleKeyDown = (event: KeyboardEvent) => {
36 | if (event.key === "Enter" && !event.shiftKey) {
37 | event.preventDefault();
38 | onSend();
39 | }
40 | };
41 |
42 | return (
43 |
44 |
45 |
61 |
65 | {isLoading ? (
66 |
67 | ) : (
68 |
69 | )}
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | export default ExpandableTextarea;
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/emoji-picker.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import {
4 | Popover,
5 | PopoverContent,
6 | PopoverTrigger,
7 | } from "@/components/ui/popover"
8 | import { SmileIcon } from "lucide-react";
9 | import Picker from '@emoji-mart/react';
10 | import data from "@emoji-mart/data"
11 |
12 | interface EmojiPickerProps {
13 | onChange: (value: string) => void;
14 | }
15 |
16 |
17 | export const EmojiPicker = ({
18 | onChange
19 | }: EmojiPickerProps) => {
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
28 | onChange(emoji.native)}
34 | />
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent/30 hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | grey: "bg-accent/30 text-accent-foreground shadow-sm hover:bg-accent/80",
23 | },
24 | size: {
25 | default: "h-9 px-4 py-2",
26 | sm: "h-8 rounded-md px-3 text-xs",
27 | lg: "h-10 rounded-md px-8",
28 | icon: "h-9 w-9",
29 | xl: "h-16 rounded-md px-5 "
30 | },
31 | },
32 | defaultVariants: {
33 | variant: "default",
34 | size: "default",
35 | },
36 | }
37 | )
38 |
39 | export interface ButtonProps
40 | extends React.ButtonHTMLAttributes,
41 | VariantProps {
42 | asChild?: boolean
43 | }
44 |
45 | const Button = React.forwardRef(
46 | ({ className, variant, size, asChild = false, ...props }, ref) => {
47 | const Comp = asChild ? Slot : "button"
48 | return (
49 |
54 | )
55 | }
56 | )
57 | Button.displayName = "Button"
58 |
59 | export { Button, buttonVariants }
60 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import { Bell } from 'lucide-react';
4 |
5 | const Header: React.FC = () => {
6 | return (
7 |
25 | );
26 | };
27 |
28 | export default Header;
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverAnchor = PopoverPrimitive.Anchor
13 |
14 | const PopoverContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
18 |
19 |
29 |
30 | ))
31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
32 |
33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
34 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/components/ui/user-avatar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface UserAvatarProps {
4 | initials: string;
5 | className?: string;
6 | }
7 |
8 | const UserAvatar: React.FC = ({ initials, className = '' }) => {
9 | return (
10 |
11 |
12 | {initials.toUpperCase()}
13 |
14 |
15 | );
16 | };
17 |
18 | export default UserAvatar;
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/src/utils/api.ts:
--------------------------------------------------------------------------------
1 | // utils/api.ts
2 |
3 | const apiUrl = process.env.NEXT_PUBLIC_AWS_GATEWAY_API_URL;
4 |
5 | interface InitiateResponse {
6 | processId: string;
7 | }
8 |
9 | interface StatusResponse {
10 | status: 'COMPLETED' | 'FAILED' | 'PENDING';
11 | processId: string;
12 | result?: string;
13 | }
14 |
15 | // Utility function to make API requests using ID token for authentication
16 | async function makeApiRequest(url: string, method: 'GET' | 'POST', body?: any): Promise {
17 | try {
18 |
19 | const headers = {
20 | 'Content-Type': 'application/json',
21 | };
22 |
23 | const response = await fetch(url, {
24 | method: method,
25 | headers: headers,
26 | body: body ? JSON.stringify(body) : undefined
27 | });
28 |
29 | if (response.ok) {
30 | return response.json();
31 | } else {
32 | return null;
33 | }
34 | } catch (error) {
35 | console.error(`Error during API call`);
36 | return null;
37 | }
38 | }
39 |
40 | // Function to initiate a test of various types
41 | export const testInitiate = async (question: string, sessionId: string): Promise => {
42 | const initiateUrl = `${apiUrl}/initiate/agent`;
43 | const payload = { question, session_id: sessionId };
44 | const data: InitiateResponse | null = await makeApiRequest(initiateUrl, 'POST', payload);
45 | return data ? data.processId : null;
46 | };
47 |
48 | // Function to check the status of a process
49 | export const testStatus = async (processId: string): Promise => {
50 | const statusUrl = `${apiUrl}/status/${processId}`;
51 | return new Promise((resolve) => {
52 | const checkStatus = async () => {
53 | const result: StatusResponse | null = await makeApiRequest(statusUrl, 'GET');
54 | if (result && result.status === 'COMPLETED') {
55 | resolve(result.result || null);
56 | } else if (result && result.status === 'FAILED') {
57 | resolve(null);
58 | } else {
59 | setTimeout(checkStatus, 5000);
60 | }
61 | };
62 | checkStatus();
63 | });
64 | };
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss"
2 |
3 | const config = {
4 | darkMode: ["class"],
5 | content: [
6 | './pages/**/*.{ts,tsx}',
7 | './components/**/*.{ts,tsx}',
8 | './app/**/*.{ts,tsx}',
9 | './src/**/*.{ts,tsx}',
10 | ],
11 | prefix: "",
12 | theme: {
13 | container: {
14 | center: true,
15 | padding: "2rem",
16 | screens: {
17 | "2xl": "1400px",
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: "hsl(var(--border))",
23 | input: "hsl(var(--input))",
24 | ring: "hsl(var(--ring))",
25 | background: "hsl(var(--background))",
26 | foreground: "hsl(var(--foreground))",
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | destructive: {
36 | DEFAULT: "hsl(var(--destructive))",
37 | foreground: "hsl(var(--destructive-foreground))",
38 | },
39 | muted: {
40 | DEFAULT: "hsl(var(--muted))",
41 | foreground: "hsl(var(--muted-foreground))",
42 | },
43 | accent: {
44 | DEFAULT: "hsl(var(--accent))",
45 | foreground: "hsl(var(--accent-foreground))",
46 | },
47 | popover: {
48 | DEFAULT: "hsl(var(--popover))",
49 | foreground: "hsl(var(--popover-foreground))",
50 | },
51 | card: {
52 | DEFAULT: "hsl(var(--card))",
53 | foreground: "hsl(var(--card-foreground))",
54 | },
55 | },
56 | borderRadius: {
57 | lg: "var(--radius)",
58 | md: "calc(var(--radius) - 2px)",
59 | sm: "calc(var(--radius) - 4px)",
60 | },
61 | keyframes: {
62 | "accordion-down": {
63 | from: { height: "0" },
64 | to: { height: "var(--radix-accordion-content-height)" },
65 | },
66 | "accordion-up": {
67 | from: { height: "var(--radix-accordion-content-height)" },
68 | to: { height: "0" },
69 | },
70 | },
71 | animation: {
72 | "accordion-down": "accordion-down 0.2s ease-out",
73 | "accordion-up": "accordion-up 0.2s ease-out",
74 | },
75 | },
76 | },
77 | plugins: [require("tailwindcss-animate")],
78 | } satisfies Config
79 |
80 | export default config
--------------------------------------------------------------------------------
/next-js-react-chat-template/phidata-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "noEmit": true,
12 | "esModuleInterop": true,
13 | "module": "esnext",
14 | "moduleResolution": "bundler",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "jsx": "preserve",
18 | "incremental": true,
19 | "plugins": [
20 | {
21 | "name": "next"
22 | }
23 | ],
24 | "paths": {
25 | "@/*": [
26 | "./src/*"
27 | ]
28 | }
29 | },
30 | "include": [
31 | "next-env.d.ts",
32 | "**/*.ts",
33 | "**/*.tsx",
34 | ".next/types/**/*.ts"
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/.env-template:
--------------------------------------------------------------------------------
1 | OPENAI_API_KEY=""
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | !jest.config.js
3 | *.d.ts
4 | node_modules
5 |
6 | # CDK asset staging directory
7 | .cdk.staging
8 | cdk.out
9 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/.npmignore:
--------------------------------------------------------------------------------
1 | *.ts
2 | !*.d.ts
3 |
4 | # CDK asset staging directory
5 | .cdk.staging
6 | cdk.out
7 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/README.md:
--------------------------------------------------------------------------------
1 | # Phidata Agent Serverless API Basic
2 |
3 | * A basic serverless API for interacting with the Phidata Agent
4 | * The conversation gets stored in a DynamoDB table
5 | * AWS Secrets Manager to store the OpenAI API Key
6 | * AWS Lambda to run the Phidata Agent
7 | * AWS API Gateway to expose the API
8 |
9 |
13 |
14 | ## Prerequisites
15 |
16 | * [AWS Account](https://aws.amazon.com/free/)
17 | * [Install NodeJS 18+](https://nodejs.org/en/download/)
18 | * [Install Python 3.10+](https://www.python.org/downloads/)
19 | * [Install AWS CLI](https://aws.amazon.com/cli/)
20 | * [Authenticate AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
21 | * [OpenAI API Key](https://platform.openai.com/api-keys)
22 | * [Install CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html)
23 |
24 | ## Steps
25 |
26 | 1. Change directory to phidata-agent-serverless-api-basic
27 | ```
28 | cd phidata-agent-serverless-api-basic
29 | ```
30 |
31 | 2. Add .env file from .env-template and add your OpenAI API Key
32 |
33 | ```
34 | cp .env-template .env
35 | ```
36 |
37 | 3. Install dependencies
38 |
39 | ```
40 | npm install
41 | ```
42 |
43 | 4. Bootstrap the stack
44 |
45 | ```
46 | cdk bootstrap
47 | ```
48 |
49 |
50 | 5. Deploy the stack
51 |
52 | ```
53 | cdk deploy
54 | ```
55 |
56 | Once you've completed testing, you can remove the deployed resources by destroying the stack
57 |
58 | ```
59 | cdk destroy
60 | ```
61 |
62 | ## Test the API
63 |
64 | 1. Get the API URL from the output of the cdk deploy command
65 | ```
66 | PhidataAgentServerlessApiBasicStack.HttpAPIUrl = https://1djpu9.execute-api.us-east-1.amazonaws.com/
67 | ```
68 |
69 | 2. Change directory to phidata-agent-serverless-api-basic/test
70 | ```
71 | cd test
72 | ```
73 |
74 | 3. Create a test/.env file from test/.env-template
75 | ```
76 | cp .env-template .env
77 | ```
78 |
79 | 4. Paste the URL into the test/.env file
80 | ```
81 | AWS_API_GATEWAY_URL=https://1djpu9.execute-api.us-east-1.amazonaws.com
82 | ```
83 |
84 | 5. Create a virtual environment and install dependencies
85 | ```
86 | python3 -m venv venv
87 | source venv/bin/activate
88 | pip install -r requirements.txt
89 | ```
90 |
91 | 6. Run the test
92 | ```
93 | python test.py
94 | ```
95 |
96 |
97 | ## Useful CDK commands
98 | The `cdk.json` file tells the CDK Toolkit how to execute your app.
99 |
100 | * `npm run build` compile typescript to js
101 | * `npm run watch` watch for changes and compile
102 | * `npm run test` perform the jest unit tests
103 | * `npx cdk deploy` deploy this stack to your default AWS account/region
104 | * `npx cdk diff` compare deployed stack with current state
105 | * `npx cdk synth` emits the synthesized CloudFormation template
106 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/bin/phidata-agent-serverless-api-basic.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import 'source-map-support/register';
3 | import * as cdk from 'aws-cdk-lib';
4 | import { PhidataAgentServerlessApiBasicStack } from '../lib/phidata-agent-serverless-api-basic-stack';
5 |
6 | const app = new cdk.App();
7 | new PhidataAgentServerlessApiBasicStack(app, 'PhidataAgentServerlessApiBasicStack', {
8 | /* If you don't specify 'env', this stack will be environment-agnostic.
9 | * Account/Region-dependent features and context lookups will not work,
10 | * but a single synthesized template can be deployed anywhere. */
11 |
12 | /* Uncomment the next line to specialize this stack for the AWS Account
13 | * and Region that are implied by the current CLI configuration. */
14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
15 |
16 | /* Uncomment the next line if you know exactly what Account and Region you
17 | * want to deploy the stack to. */
18 | // env: { account: '123456789012', region: 'us-east-1' },
19 |
20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
21 | });
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node --prefer-ts-exts bin/phidata-agent-serverless-api-basic.ts",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "**/*.d.ts",
11 | "**/*.js",
12 | "tsconfig.json",
13 | "package*.json",
14 | "yarn.lock",
15 | "node_modules",
16 | "test"
17 | ]
18 | },
19 | "context": {
20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21 | "@aws-cdk/core:checkSecretUsage": true,
22 | "@aws-cdk/core:target-partitions": [
23 | "aws",
24 | "aws-cn"
25 | ],
26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29 | "@aws-cdk/aws-iam:minimizePolicies": true,
30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35 | "@aws-cdk/core:enablePartitionLiterals": true,
36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40 | "@aws-cdk/aws-route53-patters:useCertificate": true,
41 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47 | "@aws-cdk/aws-redshift:columnId": true,
48 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51 | "@aws-cdk/aws-kms:aliasNameRef": true,
52 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
55 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62 | "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63 | "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64 | "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65 | "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66 | "@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67 | "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68 | "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69 | "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70 | "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71 | "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
72 | "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | roots: ['/test'],
4 | testMatch: ['**/*.test.ts'],
5 | transform: {
6 | '^.+\\.tsx?$': 'ts-jest'
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/agents/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/agents/requirements.txt:
--------------------------------------------------------------------------------
1 | openai
2 | boto3
3 | s3fs
4 | httpx
5 | phidata
6 | pinecone
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/agents/src/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import logging
4 |
5 | import boto3
6 |
7 | from utils import get_secret, update_dynamodb
8 |
9 | from phi.agent import Agent, RunResponse # noqa
10 | from phi.model.openai import OpenAIChat
11 |
12 | logger = logging.getLogger()
13 | logger.setLevel(logging.DEBUG)
14 |
15 | secret_name = "PhidataServerlessAgentSecrets"
16 | secret_storage = os.environ["SECRET_STORAGE"]
17 | region_name = "us-east-1"
18 |
19 | secret = get_secret(secret_name, region_name)
20 | api_key = secret["OPENAI_API_KEY"]
21 |
22 | def handler(event, context):
23 | process_id = event["processId"]
24 | question = event["question"]
25 |
26 | dynamodb = boto3.resource("dynamodb")
27 | process_table = dynamodb.Table(os.environ["PROCESS_TABLE"])
28 |
29 | # Update the process status to 'PROCESSING' in DynamoDB
30 | update_dynamodb(process_table, process_id, "PROCESSING")
31 |
32 | try:
33 |
34 | agent = Agent(model=OpenAIChat(id="gpt-4o", api_key=api_key), markdown=True)
35 |
36 | # Get the response in a variable
37 | run: RunResponse = agent.run(question)
38 | res = run.content
39 |
40 | # Update the process status to 'COMPLETED' and store the result in DynamoDB
41 | update_dynamodb(process_table, process_id, "COMPLETED", {"result": json.dumps(res)})
42 |
43 | return {
44 | "statusCode": 200,
45 | "body": json.dumps({"message": "Process completed successfully"}),
46 | "headers": {"Content-Type": "application/json"},
47 | }
48 | except Exception as e:
49 | # Update the process status to 'FAILED' in DynamoDB if an error occurs
50 | update_dynamodb(process_table, process_id, "FAILED", {"error": str(e)})
51 | return {
52 | "statusCode": 500,
53 | "body": json.dumps({"error": "An error occurred during processing"}),
54 | "headers": {"Content-Type": "application/json"},
55 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/agents/src/utils.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import json
3 |
4 | from datetime import datetime
5 |
6 | def get_secret(secret_name, region_name):
7 | # Create a session using the specified profile
8 | session = boto3.Session()
9 |
10 | # Create a Secrets Manager client using the session
11 | client = session.client('secretsmanager', region_name=region_name)
12 |
13 | try:
14 | get_secret_value_response = client.get_secret_value(SecretId=secret_name)
15 | except Exception as e:
16 | raise Exception(f"Error retrieving secret {secret_name}: {e}")
17 |
18 | # Decrypts secret using the associated KMS key.
19 | secret = get_secret_value_response['SecretString']
20 |
21 | return json.loads(secret)
22 |
23 | def update_dynamodb(table, process_id, status, additional_data=None):
24 | current_time = datetime.now().isoformat()
25 | update_expression = "SET #status = :status, #timestamp = :timestamp"
26 | expression_attribute_names = {
27 | "#status": "status",
28 | "#timestamp": "timestamp",
29 | }
30 | expression_attribute_values = {
31 | ":status": status,
32 | ":timestamp": current_time,
33 | }
34 |
35 | if additional_data:
36 | for key, value in additional_data.items():
37 | update_expression += f", #{key} = :{key}"
38 | expression_attribute_names[f"#{key}"] = key
39 | expression_attribute_values[f":{key}"] = value
40 |
41 | table.update_item(
42 | Key={"processId": process_id},
43 | UpdateExpression=update_expression,
44 | ExpressionAttributeNames=expression_attribute_names,
45 | ExpressionAttributeValues=expression_attribute_values,
46 | )
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/initiator/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/initiator/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/initiator/src/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import uuid
3 | import boto3
4 | import os
5 | import logging
6 |
7 | logger = logging.getLogger()
8 | logger.setLevel(logging.INFO)
9 | LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
10 | logging.basicConfig(format=LOG_FORMAT)
11 |
12 |
13 | dynamodb = boto3.resource('dynamodb')
14 | lambda_client = boto3.client('lambda')
15 |
16 | def handler(event, context):
17 |
18 | function_type = event['pathParameters']['type']
19 | if function_type == 'agent':
20 | lambda_function_name = os.environ['AGENT_FUNCTION_NAME']
21 | else:
22 | return {'statusCode': 400, 'body': 'Invalid function type'}
23 |
24 | request_body = json.loads(event['body'])
25 |
26 | logger.info(f"Request body: {request_body}")
27 | process_id = str(uuid.uuid4())
28 | process_table = dynamodb.Table(os.environ['PROCESS_TABLE'])
29 | process_table.put_item(Item={
30 | 'processId': process_id,
31 | 'status': 'PENDING',
32 | 'agent': function_type,
33 | 'input': request_body.get('question', ''),
34 | 'username': request_body.get('username', '')
35 | })
36 |
37 | lambda_client.invoke(
38 | FunctionName=lambda_function_name,
39 | InvocationType='Event',
40 | Payload=json.dumps({
41 | 'processId': process_id,
42 | 'question': request_body.get('question', '')
43 | })
44 | )
45 |
46 | return {
47 | 'statusCode': 200,
48 | 'body': json.dumps({'processId': process_id})
49 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/poller/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/poller/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/poller/src/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import boto3
3 | import os
4 |
5 | dynamodb = boto3.resource('dynamodb')
6 |
7 | def handler(event, context):
8 | # Get the process ID from the path parameters
9 | process_id = event['pathParameters']['processId']
10 |
11 | # Retrieve the process status from DynamoDB
12 | process_table = dynamodb.Table(os.environ['PROCESS_TABLE'])
13 | response = process_table.get_item(Key={'processId': process_id})
14 |
15 | # Check if the process exists
16 | if 'Item' not in response:
17 | return {
18 | 'statusCode': 404,
19 | 'body': json.dumps({'error': 'Process not found'})
20 | }
21 |
22 | # Get the process status and result
23 | process_item = response['Item']
24 | process_id = process_item['processId']
25 | process_status = process_item.get('status', 'PENDING')
26 | process_result = process_item.get('result', None)
27 |
28 | # Return the process status and result
29 | return {
30 | 'statusCode': 200,
31 | 'body': json.dumps({
32 | 'processId': process_id,
33 | 'status': process_status,
34 | 'result': process_result,
35 | })
36 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lambdas/poller/src/utils.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import json
3 |
4 | def get_secret(secret_name, region_name):
5 | # Create a session using the specified profile
6 | session = boto3.Session()
7 |
8 | # Create a Secrets Manager client using the session
9 | client = session.client('secretsmanager', region_name=region_name)
10 |
11 | try:
12 | get_secret_value_response = client.get_secret_value(SecretId=secret_name)
13 | except Exception as e:
14 | raise Exception(f"Error retrieving secret {secret_name}: {e}")
15 |
16 | # Decrypts secret using the associated KMS key.
17 | secret = get_secret_value_response['SecretString']
18 |
19 | return json.loads(secret)
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/lib/phidata-agent-serverless-api-basic-stack.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "aws-cdk-lib";
2 | import { Construct } from "constructs";
3 | import * as lambda from "aws-cdk-lib/aws-lambda";
4 | import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
5 | import * as apigatewayv2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
6 | import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
7 | import * as iam from 'aws-cdk-lib/aws-iam';
8 | import { HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
9 | import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
10 | import * as dotenv from 'dotenv';
11 |
12 | dotenv.config();
13 |
14 | export class PhidataAgentServerlessApiBasicStack extends cdk.Stack {
15 | constructor(scope: Construct, id: string, props?: cdk.StackProps) {
16 | super(scope, id, props);
17 |
18 | const requiredEnvVars = [
19 | 'OPENAI_API_KEY',
20 | ];
21 |
22 | for (const envVar of requiredEnvVars) {
23 | if (typeof process.env[envVar] !== 'string') {
24 | throw new Error(`${envVar} environment variable is not set`);
25 | }
26 | }
27 |
28 | const secret = new secretsmanager.Secret(this, 'EnvVarsSecret', {
29 | secretName: 'PhidataServerlessAgentSecrets',
30 | generateSecretString: {
31 | secretStringTemplate: JSON.stringify({
32 | OPENAI_API_KEY: process.env.OPENAI_API_KEY,
33 | }),
34 | generateStringKey: 'ignore_this',
35 | }
36 | });
37 |
38 | const processStatusTable = new dynamodb.Table(this, "PhidataServerlessAgentLogs", {
39 | partitionKey: { name: "processId", type: dynamodb.AttributeType.STRING },
40 | billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
41 | });
42 |
43 |
44 | const httpApi = new apigatewayv2.HttpApi(this, "PhidataServerlessAgentHttpApi", {
45 | corsPreflight: {
46 | allowHeaders: ['Content-Type', 'Authorization'],
47 | allowMethods: [apigatewayv2.CorsHttpMethod.POST, apigatewayv2.CorsHttpMethod.GET, apigatewayv2.CorsHttpMethod.OPTIONS],
48 | allowOrigins: ['*'],
49 | },
50 | });
51 |
52 | const agentLambda = new lambda.DockerImageFunction(this, "PhidataServerlessAgent", {
53 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/agents"),
54 | memorySize: 1024 * 2,
55 | timeout: cdk.Duration.seconds(90),
56 | architecture: lambda.Architecture.X86_64,
57 | environment: {
58 | PROCESS_TABLE: processStatusTable.tableName,
59 | SECRET_STORAGE: secret.secretName,
60 | },
61 | });
62 |
63 |
64 | const initiatorLambda = new lambda.DockerImageFunction(this, "PhidataAgentInitiatorFunction", {
65 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/initiator"),
66 | memorySize: 1024,
67 | timeout: cdk.Duration.seconds(30),
68 | architecture: lambda.Architecture.X86_64,
69 | environment: {
70 | PROCESS_TABLE: processStatusTable.tableName,
71 | AGENT_FUNCTION_NAME: agentLambda.functionName,
72 | },
73 | });
74 |
75 | const statusCheckLambda = new lambda.DockerImageFunction(this, "PhidataAgentStatusCheckFunction", {
76 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/poller"),
77 | memorySize: 1024,
78 | timeout: cdk.Duration.seconds(30),
79 | architecture: lambda.Architecture.X86_64,
80 | environment: {
81 | PROCESS_TABLE: processStatusTable.tableName
82 | },
83 | });
84 |
85 | const lambdaInvokePolicyStatement = new iam.PolicyStatement({
86 | actions: ['lambda:InvokeFunction'],
87 | resources: [
88 | initiatorLambda.functionArn,
89 | statusCheckLambda.functionArn,
90 | agentLambda.functionArn,
91 | ],
92 | effect: iam.Effect.ALLOW,
93 | });
94 |
95 | initiatorLambda.role?.attachInlinePolicy(new iam.Policy(this, 'InvokeLambdaPolicy', {
96 | statements: [lambdaInvokePolicyStatement],
97 | }));
98 |
99 | secret.grantRead(initiatorLambda);
100 | secret.grantRead(statusCheckLambda);
101 | secret.grantRead(agentLambda);
102 |
103 | processStatusTable.grantReadWriteData(statusCheckLambda);
104 | processStatusTable.grantReadWriteData(initiatorLambda);
105 | processStatusTable.grantReadWriteData(agentLambda);
106 |
107 |
108 | statusCheckLambda.grantInvoke(initiatorLambda);
109 | agentLambda.grantInvoke(initiatorLambda);
110 |
111 | httpApi.addRoutes({
112 | path: "/initiate/{type}",
113 | methods: [apigatewayv2.HttpMethod.POST],
114 | integration: new apigatewayv2Integrations.HttpLambdaIntegration("InitiatorIntegration", initiatorLambda),
115 | });
116 |
117 | const statusCheckIntegration = new apigatewayv2Integrations.HttpLambdaIntegration("StatusCheckIntegration", statusCheckLambda);
118 | httpApi.addRoutes({
119 | path: "/status/{processId}",
120 | methods: [apigatewayv2.HttpMethod.GET],
121 | integration: statusCheckIntegration,
122 | });
123 |
124 | const AgentIntegration = new apigatewayv2Integrations.HttpLambdaIntegration("AgentIntegration", agentLambda);
125 | httpApi.addRoutes({
126 | path: "/agent",
127 | methods: [HttpMethod.POST],
128 | integration: AgentIntegration,
129 | });
130 |
131 | new cdk.CfnOutput(this, "SecretName", {
132 | value: secret.secretName,
133 | });
134 |
135 | new cdk.CfnOutput(this, "ProcessStatusTableName", {
136 | value: processStatusTable.tableName,
137 | });
138 |
139 | new cdk.CfnOutput(this, "HttpAPIUrl", {
140 | value: httpApi.url!,
141 | });
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phidata-agent-serverless-api-basic",
3 | "version": "0.1.0",
4 | "bin": {
5 | "phidata-agent-serverless-api-basic": "bin/phidata-agent-serverless-api-basic.js"
6 | },
7 | "scripts": {
8 | "build": "tsc",
9 | "watch": "tsc -w",
10 | "test": "jest",
11 | "cdk": "cdk"
12 | },
13 | "devDependencies": {
14 | "@types/jest": "^29.5.12",
15 | "@types/node": "22.5.4",
16 | "aws-cdk": "2.160.0",
17 | "jest": "^29.7.0",
18 | "ts-jest": "^29.2.5",
19 | "ts-node": "^10.9.2",
20 | "typescript": "~5.6.2"
21 | },
22 | "dependencies": {
23 | "aws-cdk-lib": "2.160.0",
24 | "constructs": "^10.0.0",
25 | "dotenv": "^16.4.5",
26 | "source-map-support": "^0.5.21"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/test/.env-template:
--------------------------------------------------------------------------------
1 | AWS_API_GATEWAY_URL=""
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/test/phidata-agent-serverless-api-basic.test.ts:
--------------------------------------------------------------------------------
1 | // import * as cdk from 'aws-cdk-lib';
2 | // import { Template } from 'aws-cdk-lib/assertions';
3 | // import * as PhidataAgentServerlessApiBasic from '../lib/phidata-agent-serverless-api-basic-stack';
4 |
5 | // example test. To run these tests, uncomment this file along with the
6 | // example resource in lib/phidata-agent-serverless-api-basic-stack.ts
7 | test('SQS Queue Created', () => {
8 | // const app = new cdk.App();
9 | // // WHEN
10 | // const stack = new PhidataAgentServerlessApiBasic.PhidataAgentServerlessApiBasicStack(app, 'MyTestStack');
11 | // // THEN
12 | // const template = Template.fromStack(stack);
13 |
14 | // template.hasResourceProperties('AWS::SQS::Queue', {
15 | // VisibilityTimeout: 300
16 | // });
17 | });
18 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/test/requirements.txt:
--------------------------------------------------------------------------------
1 | httpx
2 | python-dotenv
3 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/test/test.py:
--------------------------------------------------------------------------------
1 | import os
2 | import httpx
3 | import time
4 | import json
5 | import logging
6 |
7 | from dotenv import load_dotenv
8 |
9 | load_dotenv()
10 |
11 | logger = logging.getLogger()
12 | logger.setLevel(logging.INFO)
13 |
14 | api_url = os.getenv("AWS_API_GATEWAY_URL")
15 |
16 | def make_api_request(url, method, body=None):
17 | headers = {'Content-Type': 'application/json'}
18 | try:
19 | if method == 'POST':
20 | response = httpx.post(url, headers=headers, data=json.dumps(body))
21 | print(f"POST request to {url} response:", response)
22 | elif method == 'GET':
23 | response = httpx.get(url, headers=headers)
24 | else:
25 | return None
26 |
27 | print(f"{method} request to {url} response:", response)
28 | if response:
29 | return response.json()
30 | else:
31 | print(f"{method} request to {url} failed with status: {response.status_code}")
32 | return None
33 | except Exception as e:
34 | print(f"Error during {method} request to {url}:", e)
35 | return None
36 |
37 | def test_initiate(test_type, payload):
38 | initiate_url = f"{api_url}/initiate/{test_type}"
39 | data = make_api_request(initiate_url, 'POST', payload)
40 | print(f"Initiate response: {data}")
41 | logger.info(f"Initiate response: {data}")
42 | return data["processId"] if data else None
43 |
44 | def test_status(process_id):
45 | status_url = f"{api_url}/status/{process_id}"
46 | while True:
47 | result = make_api_request(status_url, 'GET')
48 | if result and result['status'] == 'COMPLETED':
49 | print(f"Status test passed. Result: {result.get('result')}")
50 | return result.get('result', None)
51 | elif result and result['status'] == 'FAILED':
52 | print('Status test failed. Process failed.')
53 | return None
54 | else:
55 | print(f"Process status: {result.get('status') if result else 'UNKNOWN'}. Waiting...")
56 | time.sleep(5)
57 |
58 | # Example of usage
59 | if __name__ == "__main__":
60 | data = {"question": "Write a paragraph about the benefits of using Phidata"}
61 | print("Question data type:", type(data))
62 |
63 | logger.info(f"Initiating test with payload: {data}")
64 | process_id = test_initiate('agent', data)
65 | if process_id:
66 | result = test_status(process_id)
67 | print("Final Result:", result)
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-basic/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "lib": [
6 | "es2020",
7 | "dom"
8 | ],
9 | "declaration": true,
10 | "strict": true,
11 | "noImplicitAny": true,
12 | "strictNullChecks": true,
13 | "noImplicitThis": true,
14 | "alwaysStrict": true,
15 | "noUnusedLocals": false,
16 | "noUnusedParameters": false,
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": false,
19 | "inlineSourceMap": true,
20 | "inlineSources": true,
21 | "experimentalDecorators": true,
22 | "strictPropertyInitialization": false,
23 | "typeRoots": [
24 | "./node_modules/@types"
25 | ]
26 | },
27 | "exclude": [
28 | "node_modules",
29 | "cdk.out"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | !jest.config.js
3 | *.d.ts
4 | node_modules
5 |
6 | # CDK asset staging directory
7 | .cdk.staging
8 | cdk.out
9 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/.npmignore:
--------------------------------------------------------------------------------
1 | *.ts
2 | !*.d.ts
3 |
4 | # CDK asset staging directory
5 | .cdk.staging
6 | cdk.out
7 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/README.md:
--------------------------------------------------------------------------------
1 | # Phidata Bedrock Serverless Agent with Memory
2 |
3 | - A basic serverless API for interacting with the Phidata Agent using AWS Bedrock
4 | - Question and answer gets stored in a DynamoDB table
5 | - Add an additional DynamoDB table to maintain session history for each user
6 | - Session history providers personalized memory and chat history for each user
7 | - AWS Bedrock as the LLM Provider
8 | - AWS Lambda to run the Phidata Agent
9 | - AWS API Gateway to expose the API
10 |
11 |
15 |
16 | ## Prerequisites
17 |
18 | * [AWS Account](https://aws.amazon.com/free/)
19 | * [AWS Bedrock](https://aws.amazon.com/bedrock/)
20 | - You will need to request access to AWS Bedrock, if you haven't already.
21 | - Specifically request access for Claude 3.5 Sonnet.
22 | * [Install NodeJS 18+](https://nodejs.org/en/download/)
23 | * [Install Python 3.10+](https://www.python.org/downloads/)
24 | * [Install AWS CLI](https://aws.amazon.com/cli/)
25 | * [Authenticate AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
26 | * [Install CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html)
27 |
28 | ## Steps
29 |
30 | 1. Change directory to phidata-agent-serverless-api-bedrock
31 | ```
32 | cd phidata-agent-serverless-api-bedrock
33 | ```
34 |
35 | 2. Add .env file from .env-template and add your OpenAI API Key
36 |
37 | ```
38 | cp .env-template .env
39 | ```
40 |
41 | 3. Install dependencies
42 |
43 | ```
44 | npm install
45 | ```
46 |
47 | 4. Bootstrap the stack
48 |
49 | ```
50 | cdk bootstrap
51 | ```
52 |
53 |
54 | 5. Deploy the stack
55 |
56 | ```
57 | cdk deploy
58 | ```
59 |
60 | Once you've completed testing, you can remove the deployed resources by destroying the stack
61 |
62 | ```
63 | cdk destroy
64 | ```
65 |
66 | ## Test the API
67 |
68 | 1. Get the API URL from the output of the cdk deploy command
69 | ```
70 | PhidataBedrockAgentServerlessApiStack.HttpAPIUrl = https://1djpu9.execute-api.us-east-1.amazonaws.com/
71 | ```
72 |
73 | 2. Change directory to phidata-agent-serverless-api-bedrock/test
74 | ```
75 | cd test
76 | ```
77 |
78 | 3. Create a test/.env file from test/.env-template
79 | ```
80 | cp .env-template .env
81 | ```
82 |
83 | 4. Paste the URL into the test/.env file
84 | ```
85 | AWS_API_GATEWAY_URL=https://1djpu9.execute-api.us-east-1.amazonaws.com
86 | ```
87 |
88 | 5. Create a virtual environment and install dependencies
89 | ```
90 | python3 -m venv venv
91 | source venv/bin/activate
92 | pip install -r requirements.txt
93 | ```
94 |
95 | 6. Run the test
96 | ```
97 | python test.py
98 | ```
99 |
100 |
101 | ## Useful CDK commands
102 | The `cdk.json` file tells the CDK Toolkit how to execute your app.
103 |
104 | * `npm run build` compile typescript to js
105 | * `npm run watch` watch for changes and compile
106 | * `npm run test` perform the jest unit tests
107 | * `npx cdk deploy` deploy this stack to your default AWS account/region
108 | * `npx cdk diff` compare deployed stack with current state
109 | * `npx cdk synth` emits the synthesized CloudFormation template
110 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/bin/phidata-agent-serverless-api-bedrock-memory.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import 'source-map-support/register';
3 | import * as cdk from 'aws-cdk-lib';
4 | import { PhidataAgentServerlessApiBedrockMemoryStack } from '../lib/phidata-agent-serverless-api-bedrock-memory-stack';
5 |
6 | const app = new cdk.App();
7 | new PhidataAgentServerlessApiBedrockMemoryStack(app, 'PhidataAgentServerlessApiBedrockMemoryStack', {
8 | /* If you don't specify 'env', this stack will be environment-agnostic.
9 | * Account/Region-dependent features and context lookups will not work,
10 | * but a single synthesized template can be deployed anywhere. */
11 |
12 | /* Uncomment the next line to specialize this stack for the AWS Account
13 | * and Region that are implied by the current CLI configuration. */
14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
15 |
16 | /* Uncomment the next line if you know exactly what Account and Region you
17 | * want to deploy the stack to. */
18 | // env: { account: '123456789012', region: 'us-east-1' },
19 |
20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
21 | });
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node --prefer-ts-exts bin/phidata-agent-serverless-api-bedrock-memory.ts",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "**/*.d.ts",
11 | "**/*.js",
12 | "tsconfig.json",
13 | "package*.json",
14 | "yarn.lock",
15 | "node_modules",
16 | "test"
17 | ]
18 | },
19 | "context": {
20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21 | "@aws-cdk/core:checkSecretUsage": true,
22 | "@aws-cdk/core:target-partitions": [
23 | "aws",
24 | "aws-cn"
25 | ],
26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29 | "@aws-cdk/aws-iam:minimizePolicies": true,
30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35 | "@aws-cdk/core:enablePartitionLiterals": true,
36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40 | "@aws-cdk/aws-route53-patters:useCertificate": true,
41 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47 | "@aws-cdk/aws-redshift:columnId": true,
48 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51 | "@aws-cdk/aws-kms:aliasNameRef": true,
52 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
55 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62 | "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63 | "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64 | "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65 | "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66 | "@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67 | "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68 | "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69 | "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70 | "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71 | "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
72 | "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | roots: ['/test'],
4 | testMatch: ['**/*.test.ts'],
5 | transform: {
6 | '^.+\\.tsx?$': 'ts-jest'
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/agents/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/agents/requirements.txt:
--------------------------------------------------------------------------------
1 | openai
2 | boto3
3 | s3fs
4 | httpx
5 | phidata
6 | pinecone
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/agents/src/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import logging
4 |
5 | import boto3
6 |
7 | from utils import update_dynamodb
8 |
9 | from phi.agent import Agent, RunResponse # noqa
10 | from phi.model.aws.claude import Claude
11 | from phi.storage.agent.dynamodb import DynamoDbAgentStorage
12 |
13 | logger = logging.getLogger()
14 | logger.setLevel(logging.DEBUG)
15 |
16 |
17 | def handler(event, context):
18 | process_id = event["processId"]
19 | question = event["question"]
20 | username = event["username"]
21 | session_id = event["session_id"]
22 |
23 | dynamodb = boto3.resource("dynamodb")
24 | process_table = dynamodb.Table(os.environ["PROCESS_TABLE"])
25 | session_table = os.environ["SESSION_TABLE"]
26 |
27 | storage = DynamoDbAgentStorage(table_name=session_table, region_name="us-east-1")
28 |
29 | # Update the process status to 'PROCESSING' in DynamoDB
30 | update_dynamodb(process_table, process_id, "PROCESSING")
31 |
32 | try:
33 |
34 | agent = Agent(
35 | session_id=session_id,
36 | user_id=username,
37 | model=Claude(id="anthropic.claude-3-5-sonnet-20240620-v1:0"),
38 | storage=storage,
39 | read_chat_history=True,
40 | add_history_to_messages=True,
41 | add_datetime_to_instructions=True,
42 | )
43 |
44 | # Get the response in a variable
45 | run: RunResponse = agent.run(question)
46 | res = run.content
47 |
48 | # Update the process status to 'COMPLETED' and store the result in DynamoDB
49 | update_dynamodb(process_table, process_id, "COMPLETED", {"result": json.dumps(res)})
50 |
51 | return {
52 | "statusCode": 200,
53 | "body": json.dumps({"message": "Process completed successfully"}),
54 | "headers": {"Content-Type": "application/json"},
55 | }
56 |
57 | except Exception as e:
58 | # Update the process status to 'FAILED' in DynamoDB if an error occurs
59 | update_dynamodb(process_table, process_id, "FAILED", {"error": str(e)})
60 | return {
61 | "statusCode": 500,
62 | "body": json.dumps({"error": "An error occurred during processing"}),
63 | "headers": {"Content-Type": "application/json"},
64 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/agents/src/utils.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import json
3 |
4 | from datetime import datetime
5 |
6 | def update_dynamodb(table, process_id, status, additional_data=None):
7 | current_time = datetime.now().isoformat()
8 | update_expression = "SET #status = :status, #timestamp = :timestamp"
9 | expression_attribute_names = {
10 | "#status": "status",
11 | "#timestamp": "timestamp",
12 | }
13 | expression_attribute_values = {
14 | ":status": status,
15 | ":timestamp": current_time,
16 | }
17 |
18 | if additional_data:
19 | for key, value in additional_data.items():
20 | update_expression += f", #{key} = :{key}"
21 | expression_attribute_names[f"#{key}"] = key
22 | expression_attribute_values[f":{key}"] = value
23 |
24 | table.update_item(
25 | Key={"processId": process_id},
26 | UpdateExpression=update_expression,
27 | ExpressionAttributeNames=expression_attribute_names,
28 | ExpressionAttributeValues=expression_attribute_values,
29 | )
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/initiator/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/initiator/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/initiator/src/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import uuid
3 | import boto3
4 | import os
5 | import logging
6 |
7 | logger = logging.getLogger()
8 | logger.setLevel(logging.INFO)
9 | LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
10 | logging.basicConfig(format=LOG_FORMAT)
11 |
12 |
13 | dynamodb = boto3.resource('dynamodb')
14 | lambda_client = boto3.client('lambda')
15 |
16 | def handler(event, context):
17 |
18 | function_type = event['pathParameters']['type']
19 | if function_type == 'agent':
20 | lambda_function_name = os.environ['AGENT_FUNCTION_NAME']
21 | else:
22 | return {'statusCode': 400, 'body': 'Invalid function type'}
23 |
24 | request_body = json.loads(event['body'])
25 |
26 | logger.info(f"Request body: {request_body}")
27 | process_id = str(uuid.uuid4())
28 | process_table = dynamodb.Table(os.environ['PROCESS_TABLE'])
29 | process_table.put_item(Item={
30 | 'processId': process_id,
31 | 'status': 'PENDING',
32 | 'agent': function_type,
33 | 'input': request_body.get('question', ''),
34 | 'username': request_body.get('username', '')
35 | })
36 |
37 | lambda_client.invoke(
38 | FunctionName=lambda_function_name,
39 | InvocationType='Event',
40 | Payload=json.dumps({
41 | 'processId': process_id,
42 | 'question': request_body.get('question', ''),
43 | 'username': request_body.get('username', ''),
44 | 'session_id': request_body.get('session_id', ''),
45 | })
46 | )
47 |
48 | return {
49 | 'statusCode': 200,
50 | 'body': json.dumps({'processId': process_id})
51 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/poller/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/poller/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/poller/src/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import boto3
3 | import os
4 |
5 | dynamodb = boto3.resource('dynamodb')
6 |
7 | def handler(event, context):
8 | # Get the process ID from the path parameters
9 | process_id = event['pathParameters']['processId']
10 |
11 | # Retrieve the process status from DynamoDB
12 | process_table = dynamodb.Table(os.environ['PROCESS_TABLE'])
13 | response = process_table.get_item(Key={'processId': process_id})
14 |
15 | # Check if the process exists
16 | if 'Item' not in response:
17 | return {
18 | 'statusCode': 404,
19 | 'body': json.dumps({'error': 'Process not found'})
20 | }
21 |
22 | # Get the process status and result
23 | process_item = response['Item']
24 | process_id = process_item['processId']
25 | process_status = process_item.get('status', 'PENDING')
26 | process_result = process_item.get('result', None)
27 |
28 | # Return the process status and result
29 | return {
30 | 'statusCode': 200,
31 | 'body': json.dumps({
32 | 'processId': process_id,
33 | 'status': process_status,
34 | 'result': process_result,
35 | })
36 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lambdas/poller/src/utils.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import json
3 |
4 | def get_secret(secret_name, region_name):
5 | # Create a session using the specified profile
6 | session = boto3.Session()
7 |
8 | # Create a Secrets Manager client using the session
9 | client = session.client('secretsmanager', region_name=region_name)
10 |
11 | try:
12 | get_secret_value_response = client.get_secret_value(SecretId=secret_name)
13 | except Exception as e:
14 | raise Exception(f"Error retrieving secret {secret_name}: {e}")
15 |
16 | # Decrypts secret using the associated KMS key.
17 | secret = get_secret_value_response['SecretString']
18 |
19 | return json.loads(secret)
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/lib/phidata-agent-serverless-api-bedrock-memory-stack.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "aws-cdk-lib";
2 | import { Construct } from "constructs";
3 | import * as lambda from "aws-cdk-lib/aws-lambda";
4 | import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
5 | import * as apigatewayv2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
6 | import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
7 | import * as iam from 'aws-cdk-lib/aws-iam';
8 | import { HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
9 | import * as dotenv from 'dotenv';
10 |
11 | dotenv.config();
12 |
13 |
14 | export class PhidataAgentServerlessApiBedrockMemoryStack extends cdk.Stack {
15 | constructor(scope: Construct, id: string, props?: cdk.StackProps) {
16 | super(scope, id, props);
17 |
18 | const processStatusTable = new dynamodb.Table(this, "PhidataServerlessBedrockAgentMemoryLogs", {
19 | partitionKey: { name: "processId", type: dynamodb.AttributeType.STRING },
20 | billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
21 | });
22 |
23 | const sessionStatusTable = new dynamodb.Table(this, "PhidataServerlessBedrockAgentMemorySessionLogs", {
24 | partitionKey: { name: "session_id", type: dynamodb.AttributeType.STRING },
25 | billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
26 | });
27 |
28 | const httpApi = new apigatewayv2.HttpApi(this, "PhidataServerlessBedrockAgentMemoryHttpApi", {
29 | corsPreflight: {
30 | allowHeaders: ['Content-Type', 'Authorization'],
31 | allowMethods: [apigatewayv2.CorsHttpMethod.POST, apigatewayv2.CorsHttpMethod.GET, apigatewayv2.CorsHttpMethod.OPTIONS],
32 | allowOrigins: ['*'],
33 | },
34 | });
35 |
36 | const agentLambda = new lambda.DockerImageFunction(this, "PhidataServerlessBedrockAgentMemory", {
37 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/agents"),
38 | memorySize: 1024 * 2,
39 | timeout: cdk.Duration.seconds(90),
40 | architecture: lambda.Architecture.X86_64,
41 | environment: {
42 | PROCESS_TABLE: processStatusTable.tableName,
43 | SESSION_TABLE: sessionStatusTable.tableName,
44 | },
45 | });
46 |
47 | // AmazonBedrockFullAccess Policy
48 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
49 | effect: iam.Effect.ALLOW,
50 | actions: ["bedrock:*"],
51 | resources: ["*"],
52 | }));
53 |
54 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
55 | effect: iam.Effect.ALLOW,
56 | actions: ["kms:DescribeKey"],
57 | resources: ["arn:*:kms:*:::*"],
58 | }));
59 |
60 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
61 | effect: iam.Effect.ALLOW,
62 | actions: [
63 | "iam:ListRoles",
64 | "ec2:DescribeVpcs",
65 | "ec2:DescribeSubnets",
66 | "ec2:DescribeSecurityGroups",
67 | ],
68 | resources: ["*"],
69 | }));
70 |
71 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
72 | effect: iam.Effect.ALLOW,
73 | actions: ["iam:PassRole"],
74 | resources: ["arn:aws:iam::*:role/*AmazonBedrock*"],
75 | conditions: {
76 | StringEquals: {
77 | "iam:PassedToService": ["bedrock.amazonaws.com"],
78 | },
79 | },
80 | }));
81 |
82 | const initiatorLambda = new lambda.DockerImageFunction(this, "PhidataServerlessBedrockAgentMemoryInitiatorFunction", {
83 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/initiator"),
84 | memorySize: 1024,
85 | timeout: cdk.Duration.seconds(30),
86 | architecture: lambda.Architecture.X86_64,
87 | environment: {
88 | PROCESS_TABLE: processStatusTable.tableName,
89 | AGENT_FUNCTION_NAME: agentLambda.functionName,
90 | },
91 | });
92 |
93 | const statusCheckLambda = new lambda.DockerImageFunction(this, "PhidataServerlessBedrockAgentMemoryStatusCheckFunction", {
94 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/poller"),
95 | memorySize: 1024,
96 | timeout: cdk.Duration.seconds(30),
97 | architecture: lambda.Architecture.X86_64,
98 | environment: {
99 | PROCESS_TABLE: processStatusTable.tableName
100 | },
101 | });
102 |
103 | const lambdaInvokePolicyStatement = new iam.PolicyStatement({
104 | actions: ['lambda:InvokeFunction'],
105 | resources: [
106 | initiatorLambda.functionArn,
107 | statusCheckLambda.functionArn,
108 | agentLambda.functionArn,
109 | ],
110 | effect: iam.Effect.ALLOW,
111 | });
112 |
113 | initiatorLambda.role?.attachInlinePolicy(new iam.Policy(this, 'InvokeLambdaPolicy', {
114 | statements: [lambdaInvokePolicyStatement],
115 | }));
116 |
117 | processStatusTable.grantReadWriteData(statusCheckLambda);
118 | processStatusTable.grantReadWriteData(initiatorLambda);
119 | processStatusTable.grantReadWriteData(agentLambda);
120 | sessionStatusTable.grantReadWriteData(agentLambda);
121 |
122 | statusCheckLambda.grantInvoke(initiatorLambda);
123 | agentLambda.grantInvoke(initiatorLambda);
124 |
125 | httpApi.addRoutes({
126 | path: "/initiate/{type}",
127 | methods: [apigatewayv2.HttpMethod.POST],
128 | integration: new apigatewayv2Integrations.HttpLambdaIntegration("InitiatorIntegration", initiatorLambda),
129 | });
130 |
131 | const statusCheckIntegration = new apigatewayv2Integrations.HttpLambdaIntegration("StatusCheckIntegration", statusCheckLambda);
132 | httpApi.addRoutes({
133 | path: "/status/{processId}",
134 | methods: [apigatewayv2.HttpMethod.GET],
135 | integration: statusCheckIntegration,
136 | });
137 |
138 | const AgentIntegration = new apigatewayv2Integrations.HttpLambdaIntegration("AgentIntegration", agentLambda);
139 | httpApi.addRoutes({
140 | path: "/agent",
141 | methods: [HttpMethod.POST],
142 | integration: AgentIntegration,
143 | });
144 |
145 | new cdk.CfnOutput(this, "ProcessStatusTableName", {
146 | value: processStatusTable.tableName,
147 | });
148 |
149 | new cdk.CfnOutput(this, "SessionStatusTableName", {
150 | value: sessionStatusTable.tableName,
151 | });
152 |
153 | new cdk.CfnOutput(this, "HttpAPIUrl", {
154 | value: httpApi.url!,
155 | });
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phidata-agent-serverless-api-bedrock-memory",
3 | "version": "0.1.0",
4 | "bin": {
5 | "phidata-agent-serverless-api-bedrock-memory": "bin/phidata-agent-serverless-api-bedrock-memory.js"
6 | },
7 | "scripts": {
8 | "build": "tsc",
9 | "watch": "tsc -w",
10 | "test": "jest",
11 | "cdk": "cdk"
12 | },
13 | "devDependencies": {
14 | "@types/jest": "^29.5.12",
15 | "@types/node": "22.5.4",
16 | "aws-cdk": "2.160.0",
17 | "jest": "^29.7.0",
18 | "ts-jest": "^29.2.5",
19 | "ts-node": "^10.9.2",
20 | "typescript": "~5.6.2"
21 | },
22 | "dependencies": {
23 | "aws-cdk-lib": "2.160.0",
24 | "constructs": "^10.0.0",
25 | "dotenv": "^16.4.5",
26 | "source-map-support": "^0.5.21"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/test/.env-template:
--------------------------------------------------------------------------------
1 | AWS_API_GATEWAY_URL=""
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/test/phidata-agent-serverless-api-bedrock-memory.test.ts:
--------------------------------------------------------------------------------
1 | // import * as cdk from 'aws-cdk-lib';
2 | // import { Template } from 'aws-cdk-lib/assertions';
3 | // import * as PhidataAgentServerlessApiBedrockMemory from '../lib/phidata-agent-serverless-api-bedrock-memory-stack';
4 |
5 | // example test. To run these tests, uncomment this file along with the
6 | // example resource in lib/phidata-agent-serverless-api-bedrock-memory-stack.ts
7 | test('SQS Queue Created', () => {
8 | // const app = new cdk.App();
9 | // // WHEN
10 | // const stack = new PhidataAgentServerlessApiBedrockMemory.PhidataAgentServerlessApiBedrockMemoryStack(app, 'MyTestStack');
11 | // // THEN
12 | // const template = Template.fromStack(stack);
13 |
14 | // template.hasResourceProperties('AWS::SQS::Queue', {
15 | // VisibilityTimeout: 300
16 | // });
17 | });
18 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/test/requirements.txt:
--------------------------------------------------------------------------------
1 | httpx
2 | python-dotenv
3 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/test/test.py:
--------------------------------------------------------------------------------
1 | import os
2 | import uuid
3 | import httpx
4 | import time
5 | import json
6 | import logging
7 |
8 | from dotenv import load_dotenv
9 |
10 | load_dotenv()
11 |
12 | logger = logging.getLogger()
13 | logger.setLevel(logging.INFO)
14 |
15 | api_url = os.getenv("AWS_API_GATEWAY_URL")
16 |
17 | def make_api_request(url, method, body=None):
18 | headers = {'Content-Type': 'application/json'}
19 | try:
20 | if method == 'POST':
21 | response = httpx.post(url, headers=headers, data=json.dumps(body))
22 | print(f"POST request to {url} response:", response)
23 | elif method == 'GET':
24 | response = httpx.get(url, headers=headers)
25 | else:
26 | return None
27 |
28 | print(f"{method} request to {url} response:", response)
29 | if response:
30 | return response.json()
31 | else:
32 | print(f"{method} request to {url} failed with status: {response.status_code}")
33 | return None
34 | except Exception as e:
35 | print(f"Error during {method} request to {url}:", e)
36 | return None
37 |
38 | def test_initiate(test_type, payload):
39 | initiate_url = f"{api_url}/initiate/{test_type}"
40 | data = make_api_request(initiate_url, 'POST', payload)
41 | print(f"Initiate response: {data}")
42 | logger.info(f"Initiate response: {data}")
43 | return data["processId"] if data else None
44 |
45 | def test_status(process_id):
46 | status_url = f"{api_url}/status/{process_id}"
47 | while True:
48 | result = make_api_request(status_url, 'GET')
49 | if result and result['status'] == 'COMPLETED':
50 | print(f"Status test passed. Result: {result.get('result')}")
51 | return result.get('result', None)
52 | elif result and result['status'] == 'FAILED':
53 | print('Status test failed. Process failed.')
54 | return None
55 | else:
56 | print(f"Process status: {result.get('status') if result else 'UNKNOWN'}. Waiting...")
57 | time.sleep(5)
58 |
59 | # Example of usage
60 | if __name__ == "__main__":
61 | session_id = str(uuid.uuid4())
62 | data = {
63 | "question": "What color is the sky?",
64 | "username": "John Doe",
65 | "session_id": session_id,
66 | }
67 |
68 | process_id = test_initiate('agent', data)
69 | if process_id:
70 | result = test_status(process_id)
71 | print("Final Result:", result)
72 |
73 | data = {
74 | "question": "What was my first question?",
75 | "username": "John Doe",
76 | "session_id": session_id,
77 | }
78 |
79 | process_id = test_initiate('agent', data)
80 | if process_id:
81 | result = test_status(process_id)
82 | print("Final Result:", result)
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock-memory/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "lib": [
6 | "es2020",
7 | "dom"
8 | ],
9 | "declaration": true,
10 | "strict": true,
11 | "noImplicitAny": true,
12 | "strictNullChecks": true,
13 | "noImplicitThis": true,
14 | "alwaysStrict": true,
15 | "noUnusedLocals": false,
16 | "noUnusedParameters": false,
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": false,
19 | "inlineSourceMap": true,
20 | "inlineSources": true,
21 | "experimentalDecorators": true,
22 | "strictPropertyInitialization": false,
23 | "typeRoots": [
24 | "./node_modules/@types"
25 | ]
26 | },
27 | "exclude": [
28 | "node_modules",
29 | "cdk.out"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | !jest.config.js
3 | *.d.ts
4 | node_modules
5 |
6 | # CDK asset staging directory
7 | .cdk.staging
8 | cdk.out
9 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/.npmignore:
--------------------------------------------------------------------------------
1 | *.ts
2 | !*.d.ts
3 |
4 | # CDK asset staging directory
5 | .cdk.staging
6 | cdk.out
7 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/README.md:
--------------------------------------------------------------------------------
1 | # Phidata Bedrock Agent Serverless API
2 |
3 | - A basic serverless API for interacting with the Phidata Agent using AWS Bedrock
4 | - The conversation gets stored in a DynamoDB table
5 | - AWS Bedrock as the LLM Provider
6 | - AWS Lambda to run the Phidata Agent
7 | - AWS API Gateway to expose the API
8 |
9 |
13 |
14 | ## Prerequisites
15 |
16 | * [AWS Account](https://aws.amazon.com/free/)
17 | * [AWS Bedrock](https://aws.amazon.com/bedrock/)
18 | - You will need to request access to AWS Bedrock, if you haven't already.
19 | - Specifically request access for Claude 3.5 Sonnet.
20 | * [Install NodeJS 18+](https://nodejs.org/en/download/)
21 | * [Install Python 3.10+](https://www.python.org/downloads/)
22 | * [Install AWS CLI](https://aws.amazon.com/cli/)
23 | * [Authenticate AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
24 | * [Install CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html)
25 |
26 | ## Steps
27 |
28 | 1. Change directory to phidata-agent-serverless-api-bedrock
29 | ```
30 | cd phidata-agent-serverless-api-bedrock
31 | ```
32 |
33 | 2. Add .env file from .env-template and add your OpenAI API Key
34 |
35 | ```
36 | cp .env-template .env
37 | ```
38 |
39 | 3. Install dependencies
40 |
41 | ```
42 | npm install
43 | ```
44 |
45 | 4. Bootstrap the stack
46 |
47 | ```
48 | cdk bootstrap
49 | ```
50 |
51 |
52 | 5. Deploy the stack
53 |
54 | ```
55 | cdk deploy
56 | ```
57 |
58 | Once you've completed testing, you can remove the deployed resources by destroying the stack
59 |
60 | ```
61 | cdk destroy
62 | ```
63 |
64 | ## Test the API
65 |
66 | 1. Get the API URL from the output of the cdk deploy command
67 | ```
68 | PhidataBedrockAgentServerlessApiStack.HttpAPIUrl = https://1djpu9.execute-api.us-east-1.amazonaws.com/
69 | ```
70 |
71 | 2. Change directory to phidata-agent-serverless-api-bedrock/test
72 | ```
73 | cd test
74 | ```
75 |
76 | 3. Create a test/.env file from test/.env-template
77 | ```
78 | cp .env-template .env
79 | ```
80 |
81 | 4. Paste the URL into the test/.env file
82 | ```
83 | AWS_API_GATEWAY_URL=https://1djpu9.execute-api.us-east-1.amazonaws.com
84 | ```
85 |
86 | 5. Create a virtual environment and install dependencies
87 | ```
88 | python3 -m venv venv
89 | source venv/bin/activate
90 | pip install -r requirements.txt
91 | ```
92 |
93 | 6. Run the test
94 | ```
95 | python test.py
96 | ```
97 |
98 |
99 | ## Useful CDK commands
100 | The `cdk.json` file tells the CDK Toolkit how to execute your app.
101 |
102 | * `npm run build` compile typescript to js
103 | * `npm run watch` watch for changes and compile
104 | * `npm run test` perform the jest unit tests
105 | * `npx cdk deploy` deploy this stack to your default AWS account/region
106 | * `npx cdk diff` compare deployed stack with current state
107 | * `npx cdk synth` emits the synthesized CloudFormation template
108 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/bin/phidata-agent-serverless-api-bedrock.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import 'source-map-support/register';
3 | import * as cdk from 'aws-cdk-lib';
4 | import { PhidataAgentServerlessApiBedrockStack } from '../lib/phidata-agent-serverless-api-bedrock-stack';
5 |
6 | const app = new cdk.App();
7 | new PhidataAgentServerlessApiBedrockStack(app, 'PhidataAgentServerlessApiBedrockStack', {
8 | /* If you don't specify 'env', this stack will be environment-agnostic.
9 | * Account/Region-dependent features and context lookups will not work,
10 | * but a single synthesized template can be deployed anywhere. */
11 |
12 | /* Uncomment the next line to specialize this stack for the AWS Account
13 | * and Region that are implied by the current CLI configuration. */
14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
15 |
16 | /* Uncomment the next line if you know exactly what Account and Region you
17 | * want to deploy the stack to. */
18 | // env: { account: '123456789012', region: 'us-east-1' },
19 |
20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
21 | });
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/cdk.json:
--------------------------------------------------------------------------------
1 | {
2 | "app": "npx ts-node --prefer-ts-exts bin/phidata-agent-serverless-api-bedrock.ts",
3 | "watch": {
4 | "include": [
5 | "**"
6 | ],
7 | "exclude": [
8 | "README.md",
9 | "cdk*.json",
10 | "**/*.d.ts",
11 | "**/*.js",
12 | "tsconfig.json",
13 | "package*.json",
14 | "yarn.lock",
15 | "node_modules",
16 | "test"
17 | ]
18 | },
19 | "context": {
20 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21 | "@aws-cdk/core:checkSecretUsage": true,
22 | "@aws-cdk/core:target-partitions": [
23 | "aws",
24 | "aws-cn"
25 | ],
26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29 | "@aws-cdk/aws-iam:minimizePolicies": true,
30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35 | "@aws-cdk/core:enablePartitionLiterals": true,
36 | "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37 | "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38 | "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39 | "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40 | "@aws-cdk/aws-route53-patters:useCertificate": true,
41 | "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42 | "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43 | "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44 | "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45 | "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46 | "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47 | "@aws-cdk/aws-redshift:columnId": true,
48 | "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49 | "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50 | "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51 | "@aws-cdk/aws-kms:aliasNameRef": true,
52 | "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53 | "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54 | "@aws-cdk/aws-efs:denyAnonymousAccess": true,
55 | "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56 | "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57 | "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58 | "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59 | "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60 | "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61 | "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62 | "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63 | "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64 | "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65 | "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66 | "@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67 | "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68 | "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69 | "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70 | "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71 | "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
72 | "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | roots: ['/test'],
4 | testMatch: ['**/*.test.ts'],
5 | transform: {
6 | '^.+\\.tsx?$': 'ts-jest'
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/agents/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/agents/requirements.txt:
--------------------------------------------------------------------------------
1 | openai
2 | boto3
3 | s3fs
4 | httpx
5 | phidata
6 | pinecone
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/agents/src/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import logging
4 |
5 | import boto3
6 |
7 | from utils import update_dynamodb
8 |
9 | from phi.agent import Agent, RunResponse # noqa
10 | from phi.model.aws.claude import Claude
11 |
12 | logger = logging.getLogger()
13 | logger.setLevel(logging.DEBUG)
14 |
15 |
16 | def handler(event, context):
17 | process_id = event["processId"]
18 | question = event["question"]
19 |
20 | dynamodb = boto3.resource("dynamodb")
21 | process_table = dynamodb.Table(os.environ["PROCESS_TABLE"])
22 |
23 | # Update the process status to 'PROCESSING' in DynamoDB
24 | update_dynamodb(process_table, process_id, "PROCESSING")
25 |
26 | try:
27 |
28 | agent = Agent(model=Claude(id="anthropic.claude-3-5-sonnet-20240620-v1:0"), markdown=True)
29 |
30 | # Get the response in a variable
31 | run: RunResponse = agent.run(question)
32 | res = run.content
33 |
34 | # Update the process status to 'COMPLETED' and store the result in DynamoDB
35 | update_dynamodb(process_table, process_id, "COMPLETED", {"result": json.dumps(res)})
36 |
37 | return {
38 | "statusCode": 200,
39 | "body": json.dumps({"message": "Process completed successfully"}),
40 | "headers": {"Content-Type": "application/json"},
41 | }
42 | except Exception as e:
43 | # Update the process status to 'FAILED' in DynamoDB if an error occurs
44 | update_dynamodb(process_table, process_id, "FAILED", {"error": str(e)})
45 | return {
46 | "statusCode": 500,
47 | "body": json.dumps({"error": "An error occurred during processing"}),
48 | "headers": {"Content-Type": "application/json"},
49 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/agents/src/utils.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import json
3 |
4 | from datetime import datetime
5 |
6 | def update_dynamodb(table, process_id, status, additional_data=None):
7 | current_time = datetime.now().isoformat()
8 | update_expression = "SET #status = :status, #timestamp = :timestamp"
9 | expression_attribute_names = {
10 | "#status": "status",
11 | "#timestamp": "timestamp",
12 | }
13 | expression_attribute_values = {
14 | ":status": status,
15 | ":timestamp": current_time,
16 | }
17 |
18 | if additional_data:
19 | for key, value in additional_data.items():
20 | update_expression += f", #{key} = :{key}"
21 | expression_attribute_names[f"#{key}"] = key
22 | expression_attribute_values[f":{key}"] = value
23 |
24 | table.update_item(
25 | Key={"processId": process_id},
26 | UpdateExpression=update_expression,
27 | ExpressionAttributeNames=expression_attribute_names,
28 | ExpressionAttributeValues=expression_attribute_values,
29 | )
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/initiator/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/initiator/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/initiator/src/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import uuid
3 | import boto3
4 | import os
5 | import logging
6 |
7 | logger = logging.getLogger()
8 | logger.setLevel(logging.INFO)
9 | LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
10 | logging.basicConfig(format=LOG_FORMAT)
11 |
12 |
13 | dynamodb = boto3.resource('dynamodb')
14 | lambda_client = boto3.client('lambda')
15 |
16 | def handler(event, context):
17 |
18 | function_type = event['pathParameters']['type']
19 | if function_type == 'agent':
20 | lambda_function_name = os.environ['AGENT_FUNCTION_NAME']
21 | else:
22 | return {'statusCode': 400, 'body': 'Invalid function type'}
23 |
24 | request_body = json.loads(event['body'])
25 |
26 | logger.info(f"Request body: {request_body}")
27 | process_id = str(uuid.uuid4())
28 | process_table = dynamodb.Table(os.environ['PROCESS_TABLE'])
29 | process_table.put_item(Item={
30 | 'processId': process_id,
31 | 'status': 'PENDING',
32 | 'agent': function_type,
33 | 'input': request_body.get('question', ''),
34 | 'username': request_body.get('username', '')
35 | })
36 |
37 | lambda_client.invoke(
38 | FunctionName=lambda_function_name,
39 | InvocationType='Event',
40 | Payload=json.dumps({
41 | 'processId': process_id,
42 | 'question': request_body.get('question', '')
43 | })
44 | )
45 |
46 | return {
47 | 'statusCode': 200,
48 | 'body': json.dumps({'processId': process_id})
49 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/poller/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM public.ecr.aws/lambda/python:3.11
2 |
3 | # Copy requirements.txt
4 | COPY requirements.txt ${LAMBDA_TASK_ROOT}
5 |
6 | # Install the specified packages
7 | RUN pip install -r requirements.txt
8 |
9 | # Copy all files in ./src
10 | COPY src/* ${LAMBDA_TASK_ROOT}
11 |
12 | # Set the CMD to your handler.
13 | CMD [ "main.handler" ]
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/poller/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/poller/src/main.py:
--------------------------------------------------------------------------------
1 | import json
2 | import boto3
3 | import os
4 |
5 | dynamodb = boto3.resource('dynamodb')
6 |
7 | def handler(event, context):
8 | # Get the process ID from the path parameters
9 | process_id = event['pathParameters']['processId']
10 |
11 | # Retrieve the process status from DynamoDB
12 | process_table = dynamodb.Table(os.environ['PROCESS_TABLE'])
13 | response = process_table.get_item(Key={'processId': process_id})
14 |
15 | # Check if the process exists
16 | if 'Item' not in response:
17 | return {
18 | 'statusCode': 404,
19 | 'body': json.dumps({'error': 'Process not found'})
20 | }
21 |
22 | # Get the process status and result
23 | process_item = response['Item']
24 | process_id = process_item['processId']
25 | process_status = process_item.get('status', 'PENDING')
26 | process_result = process_item.get('result', None)
27 |
28 | # Return the process status and result
29 | return {
30 | 'statusCode': 200,
31 | 'body': json.dumps({
32 | 'processId': process_id,
33 | 'status': process_status,
34 | 'result': process_result,
35 | })
36 | }
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lambdas/poller/src/utils.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | import json
3 |
4 | def get_secret(secret_name, region_name):
5 | # Create a session using the specified profile
6 | session = boto3.Session()
7 |
8 | # Create a Secrets Manager client using the session
9 | client = session.client('secretsmanager', region_name=region_name)
10 |
11 | try:
12 | get_secret_value_response = client.get_secret_value(SecretId=secret_name)
13 | except Exception as e:
14 | raise Exception(f"Error retrieving secret {secret_name}: {e}")
15 |
16 | # Decrypts secret using the associated KMS key.
17 | secret = get_secret_value_response['SecretString']
18 |
19 | return json.loads(secret)
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/lib/phidata-agent-serverless-api-bedrock-stack.ts:
--------------------------------------------------------------------------------
1 | import * as cdk from "aws-cdk-lib";
2 | import { Construct } from "constructs";
3 | import * as lambda from "aws-cdk-lib/aws-lambda";
4 | import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
5 | import * as apigatewayv2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
6 | import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
7 | import * as iam from 'aws-cdk-lib/aws-iam';
8 | import { HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
9 | import * as bedrock from 'aws-cdk-lib/aws-bedrock';
10 | import * as dotenv from 'dotenv';
11 |
12 | dotenv.config();
13 |
14 |
15 | export class PhidataAgentServerlessApiBedrockStack extends cdk.Stack {
16 | constructor(scope: Construct, id: string, props?: cdk.StackProps) {
17 | super(scope, id, props);
18 |
19 | const processStatusTable = new dynamodb.Table(this, "PhidataServerlessBedrockAgentLogs", {
20 | partitionKey: { name: "processId", type: dynamodb.AttributeType.STRING },
21 | billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
22 | });
23 |
24 |
25 | const httpApi = new apigatewayv2.HttpApi(this, "PhidataServerlessBedrockAgentHttpApi", {
26 | corsPreflight: {
27 | allowHeaders: ['Content-Type', 'Authorization'],
28 | allowMethods: [apigatewayv2.CorsHttpMethod.POST, apigatewayv2.CorsHttpMethod.GET, apigatewayv2.CorsHttpMethod.OPTIONS],
29 | allowOrigins: ['*'],
30 | },
31 | });
32 |
33 | const agentLambda = new lambda.DockerImageFunction(this, "PhidataServerlessBedrockAgent", {
34 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/agents"),
35 | memorySize: 1024 * 2,
36 | timeout: cdk.Duration.seconds(90),
37 | architecture: lambda.Architecture.X86_64,
38 | environment: {
39 | PROCESS_TABLE: processStatusTable.tableName,
40 | },
41 | });
42 |
43 | // AmazonBedrockFullAccess Policy
44 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
45 | effect: iam.Effect.ALLOW,
46 | actions: ["bedrock:*"],
47 | resources: ["*"],
48 | }));
49 |
50 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
51 | effect: iam.Effect.ALLOW,
52 | actions: ["kms:DescribeKey"],
53 | resources: ["arn:*:kms:*:::*"],
54 | }));
55 |
56 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
57 | effect: iam.Effect.ALLOW,
58 | actions: [
59 | "iam:ListRoles",
60 | "ec2:DescribeVpcs",
61 | "ec2:DescribeSubnets",
62 | "ec2:DescribeSecurityGroups",
63 | ],
64 | resources: ["*"],
65 | }));
66 |
67 | agentLambda.addToRolePolicy(new iam.PolicyStatement({
68 | effect: iam.Effect.ALLOW,
69 | actions: ["iam:PassRole"],
70 | resources: ["arn:aws:iam::*:role/*AmazonBedrock*"],
71 | conditions: {
72 | StringEquals: {
73 | "iam:PassedToService": ["bedrock.amazonaws.com"],
74 | },
75 | },
76 | }));
77 |
78 | const initiatorLambda = new lambda.DockerImageFunction(this, "PhidataBedrockAgentInitiatorFunction", {
79 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/initiator"),
80 | memorySize: 1024,
81 | timeout: cdk.Duration.seconds(30),
82 | architecture: lambda.Architecture.X86_64,
83 | environment: {
84 | PROCESS_TABLE: processStatusTable.tableName,
85 | AGENT_FUNCTION_NAME: agentLambda.functionName,
86 | },
87 | });
88 |
89 | const statusCheckLambda = new lambda.DockerImageFunction(this, "PhidataBedrockAgentStatusCheckFunction", {
90 | code: lambda.DockerImageCode.fromImageAsset("./lambdas/poller"),
91 | memorySize: 1024,
92 | timeout: cdk.Duration.seconds(30),
93 | architecture: lambda.Architecture.X86_64,
94 | environment: {
95 | PROCESS_TABLE: processStatusTable.tableName
96 | },
97 | });
98 |
99 | const lambdaInvokePolicyStatement = new iam.PolicyStatement({
100 | actions: ['lambda:InvokeFunction'],
101 | resources: [
102 | initiatorLambda.functionArn,
103 | statusCheckLambda.functionArn,
104 | agentLambda.functionArn,
105 | ],
106 | effect: iam.Effect.ALLOW,
107 | });
108 |
109 | initiatorLambda.role?.attachInlinePolicy(new iam.Policy(this, 'InvokeLambdaPolicy', {
110 | statements: [lambdaInvokePolicyStatement],
111 | }));
112 |
113 | processStatusTable.grantReadWriteData(statusCheckLambda);
114 | processStatusTable.grantReadWriteData(initiatorLambda);
115 | processStatusTable.grantReadWriteData(agentLambda);
116 |
117 |
118 | statusCheckLambda.grantInvoke(initiatorLambda);
119 | agentLambda.grantInvoke(initiatorLambda);
120 |
121 | httpApi.addRoutes({
122 | path: "/initiate/{type}",
123 | methods: [apigatewayv2.HttpMethod.POST],
124 | integration: new apigatewayv2Integrations.HttpLambdaIntegration("InitiatorIntegration", initiatorLambda),
125 | });
126 |
127 | const statusCheckIntegration = new apigatewayv2Integrations.HttpLambdaIntegration("StatusCheckIntegration", statusCheckLambda);
128 | httpApi.addRoutes({
129 | path: "/status/{processId}",
130 | methods: [apigatewayv2.HttpMethod.GET],
131 | integration: statusCheckIntegration,
132 | });
133 |
134 | const AgentIntegration = new apigatewayv2Integrations.HttpLambdaIntegration("AgentIntegration", agentLambda);
135 | httpApi.addRoutes({
136 | path: "/agent",
137 | methods: [HttpMethod.POST],
138 | integration: AgentIntegration,
139 | });
140 |
141 | new cdk.CfnOutput(this, "ProcessStatusTableName", {
142 | value: processStatusTable.tableName,
143 | });
144 |
145 | new cdk.CfnOutput(this, "HttpAPIUrl", {
146 | value: httpApi.url!,
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phidata-agent-serverless-api-bedrock",
3 | "version": "0.1.0",
4 | "bin": {
5 | "phidata-agent-serverless-api-bedrock": "bin/phidata-agent-serverless-api-bedrock.js"
6 | },
7 | "scripts": {
8 | "build": "tsc",
9 | "watch": "tsc -w",
10 | "test": "jest",
11 | "cdk": "cdk"
12 | },
13 | "devDependencies": {
14 | "@types/jest": "^29.5.12",
15 | "@types/node": "22.5.4",
16 | "aws-cdk": "2.160.0",
17 | "jest": "^29.7.0",
18 | "ts-jest": "^29.2.5",
19 | "ts-node": "^10.9.2",
20 | "typescript": "~5.6.2"
21 | },
22 | "dependencies": {
23 | "aws-cdk-lib": "2.160.0",
24 | "constructs": "^10.0.0",
25 | "dotenv": "^16.4.5",
26 | "source-map-support": "^0.5.21"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/test/.env-template:
--------------------------------------------------------------------------------
1 | AWS_API_GATEWAY_URL=""
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/test/phidata-agent-serverless-api-basic.test.ts:
--------------------------------------------------------------------------------
1 | // import * as cdk from 'aws-cdk-lib';
2 | // import { Template } from 'aws-cdk-lib/assertions';
3 | // import * as PhidataAgentServerlessApiBasic from '../lib/phidata-agent-serverless-api-basic-stack';
4 |
5 | // example test. To run these tests, uncomment this file along with the
6 | // example resource in lib/phidata-agent-serverless-api-basic-stack.ts
7 | test('SQS Queue Created', () => {
8 | // const app = new cdk.App();
9 | // // WHEN
10 | // const stack = new PhidataAgentServerlessApiBasic.PhidataAgentServerlessApiBasicStack(app, 'MyTestStack');
11 | // // THEN
12 | // const template = Template.fromStack(stack);
13 |
14 | // template.hasResourceProperties('AWS::SQS::Queue', {
15 | // VisibilityTimeout: 300
16 | // });
17 | });
18 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/test/phidata-agent-serverless-api-bedrock.test.ts:
--------------------------------------------------------------------------------
1 | // import * as cdk from 'aws-cdk-lib';
2 | // import { Template } from 'aws-cdk-lib/assertions';
3 | // import * as PhidataAgentServerlessApiBedrock from '../lib/phidata-agent-serverless-api-bedrock-stack';
4 |
5 | // example test. To run these tests, uncomment this file along with the
6 | // example resource in lib/phidata-agent-serverless-api-bedrock-stack.ts
7 | test('SQS Queue Created', () => {
8 | // const app = new cdk.App();
9 | // // WHEN
10 | // const stack = new PhidataAgentServerlessApiBedrock.PhidataAgentServerlessApiBedrockStack(app, 'MyTestStack');
11 | // // THEN
12 | // const template = Template.fromStack(stack);
13 |
14 | // template.hasResourceProperties('AWS::SQS::Queue', {
15 | // VisibilityTimeout: 300
16 | // });
17 | });
18 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/test/requirements.txt:
--------------------------------------------------------------------------------
1 | httpx
2 | python-dotenv
3 |
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/test/test.py:
--------------------------------------------------------------------------------
1 | import os
2 | import httpx
3 | import time
4 | import json
5 | import logging
6 |
7 | from dotenv import load_dotenv
8 |
9 | load_dotenv()
10 |
11 | logger = logging.getLogger()
12 | logger.setLevel(logging.INFO)
13 |
14 | api_url = os.getenv("AWS_API_GATEWAY_URL")
15 |
16 | def make_api_request(url, method, body=None):
17 | headers = {'Content-Type': 'application/json'}
18 | try:
19 | if method == 'POST':
20 | response = httpx.post(url, headers=headers, data=json.dumps(body))
21 | print(f"POST request to {url} response:", response)
22 | elif method == 'GET':
23 | response = httpx.get(url, headers=headers)
24 | else:
25 | return None
26 |
27 | print(f"{method} request to {url} response:", response)
28 | if response:
29 | return response.json()
30 | else:
31 | print(f"{method} request to {url} failed with status: {response.status_code}")
32 | return None
33 | except Exception as e:
34 | print(f"Error during {method} request to {url}:", e)
35 | return None
36 |
37 | def test_initiate(test_type, payload):
38 | initiate_url = f"{api_url}/initiate/{test_type}"
39 | data = make_api_request(initiate_url, 'POST', payload)
40 | print(f"Initiate response: {data}")
41 | logger.info(f"Initiate response: {data}")
42 | return data["processId"] if data else None
43 |
44 | def test_status(process_id):
45 | status_url = f"{api_url}/status/{process_id}"
46 | while True:
47 | result = make_api_request(status_url, 'GET')
48 | if result and result['status'] == 'COMPLETED':
49 | print(f"Status test passed. Result: {result.get('result')}")
50 | return result.get('result', None)
51 | elif result and result['status'] == 'FAILED':
52 | print('Status test failed. Process failed.')
53 | return None
54 | else:
55 | print(f"Process status: {result.get('status') if result else 'UNKNOWN'}. Waiting...")
56 | time.sleep(5)
57 |
58 | # Example of usage
59 | if __name__ == "__main__":
60 | data = {"question": "Write a paragraph about the benefits of using Phidata"}
61 | print("Question data type:", type(data))
62 |
63 | logger.info(f"Initiating test with payload: {data}")
64 | process_id = test_initiate('agent', data)
65 | if process_id:
66 | result = test_status(process_id)
67 | print("Final Result:", result)
--------------------------------------------------------------------------------
/phidata-agent-serverless-api-bedrock/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "lib": [
6 | "es2020",
7 | "dom"
8 | ],
9 | "declaration": true,
10 | "strict": true,
11 | "noImplicitAny": true,
12 | "strictNullChecks": true,
13 | "noImplicitThis": true,
14 | "alwaysStrict": true,
15 | "noUnusedLocals": false,
16 | "noUnusedParameters": false,
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": false,
19 | "inlineSourceMap": true,
20 | "inlineSources": true,
21 | "experimentalDecorators": true,
22 | "strictPropertyInitialization": false,
23 | "typeRoots": [
24 | "./node_modules/@types"
25 | ]
26 | },
27 | "exclude": [
28 | "node_modules",
29 | "cdk.out"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------