Repository: awslabs/agent-squad Branch: main Commit: 8048bcf308fc Files: 347 Total size: 1.7 MB Directory structure: gitextract_g9hwz37y/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── feature_request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── npm-publish.yml │ ├── on-docs-update.yml │ ├── on-issue-opened.yml │ ├── on-push.yml │ ├── pr-issue-link-checker.yml │ ├── py-run-tests.yml │ ├── pypi-publish.yml │ ├── ts-run-lint.yml │ ├── ts-run-security-checks.yml │ └── ts-run-tests.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs/ │ ├── .gitignore │ ├── README.md │ ├── astro.config.mjs │ ├── package.json │ ├── src/ │ │ ├── components/ │ │ │ └── code.astro │ │ ├── content/ │ │ │ ├── config.ts │ │ │ └── docs/ │ │ │ ├── agents/ │ │ │ │ ├── built-in/ │ │ │ │ │ ├── amazon-bedrock-agent.mdx │ │ │ │ │ ├── anthropic-agent.mdx │ │ │ │ │ ├── bedrock-flows-agent.mdx │ │ │ │ │ ├── bedrock-inline-agent.mdx │ │ │ │ │ ├── bedrock-llm-agent.mdx │ │ │ │ │ ├── bedrock-translator-agent.mdx │ │ │ │ │ ├── chain-agent.mdx │ │ │ │ │ ├── comprehend-filter-agent.mdx │ │ │ │ │ ├── lambda-agent.mdx │ │ │ │ │ ├── lex-bot-agent.mdx │ │ │ │ │ ├── openai-agent.mdx │ │ │ │ │ └── supervisor-agent.mdx │ │ │ │ ├── custom-agents.mdx │ │ │ │ ├── overview.mdx │ │ │ │ └── tools.mdx │ │ │ ├── classifiers/ │ │ │ │ ├── built-in/ │ │ │ │ │ ├── anthropic-classifier.mdx │ │ │ │ │ ├── bedrock-classifier.mdx │ │ │ │ │ └── openai-classifier.mdx │ │ │ │ ├── custom-classifier.mdx │ │ │ │ └── overview.mdx │ │ │ ├── cookbook/ │ │ │ │ ├── examples/ │ │ │ │ │ ├── api-agent.mdx │ │ │ │ │ ├── chat-chainlit-app.md │ │ │ │ │ ├── chat-demo-app.md │ │ │ │ │ ├── ecommerce-support-simulator.md │ │ │ │ │ ├── fast-api-streaming.md │ │ │ │ │ ├── ollama-agent.mdx │ │ │ │ │ ├── ollama-classifier.mdx │ │ │ │ │ ├── python-local-demo.md │ │ │ │ │ └── typescript-local-demo.md │ │ │ │ ├── lambda/ │ │ │ │ │ ├── aws-lambda-nodejs.md │ │ │ │ │ └── aws-lambda-python.md │ │ │ │ ├── monitoring/ │ │ │ │ │ ├── agent-overlap.md │ │ │ │ │ ├── logging.mdx │ │ │ │ │ └── observability.mdx │ │ │ │ ├── patterns/ │ │ │ │ │ ├── cost-efficient.md │ │ │ │ │ └── multi-lingual.md │ │ │ │ └── tools/ │ │ │ │ ├── math-operations.md │ │ │ │ └── weather-api.mdx │ │ │ ├── general/ │ │ │ │ ├── faq.md │ │ │ │ ├── how-it-works.md │ │ │ │ ├── introduction.md │ │ │ │ └── quickstart.mdx │ │ │ ├── index.mdx │ │ │ ├── orchestrator/ │ │ │ │ └── overview.mdx │ │ │ ├── retrievers/ │ │ │ │ ├── built-in/ │ │ │ │ │ └── bedrock-kb-retriever.mdx │ │ │ │ ├── custom-retriever.mdx │ │ │ │ └── overview.md │ │ │ └── storage/ │ │ │ ├── custom.mdx │ │ │ ├── dynamodb.mdx │ │ │ ├── in-memory.mdx │ │ │ ├── overview.md │ │ │ └── sql.mdx │ │ ├── env.d.ts │ │ └── styles/ │ │ ├── custom.css │ │ ├── font.css │ │ ├── landing.css │ │ └── terminal.css │ └── tsconfig.json ├── examples/ │ ├── bedrock-flows/ │ │ ├── python/ │ │ │ └── main.py │ │ ├── readme.md │ │ └── typescript/ │ │ └── main.ts │ ├── bedrock-inline-agents/ │ │ ├── python/ │ │ │ └── main.py │ │ └── typescript/ │ │ └── main.ts │ ├── bedrock-prompt-routing/ │ │ ├── main.py │ │ └── readme.md │ ├── chat-chainlit-app/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── agents.py │ │ ├── app.py │ │ ├── chainlit.md │ │ ├── ollamaAgent.py │ │ └── requirements.txt │ ├── chat-demo-app/ │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── bin/ │ │ │ └── chat-demo-app.ts │ │ ├── cdk.json │ │ ├── jest.config.js │ │ ├── lambda/ │ │ │ ├── auth/ │ │ │ │ ├── index.mjs │ │ │ │ └── package.json │ │ │ ├── find-my-name/ │ │ │ │ └── lambda.py │ │ │ ├── multi-agent/ │ │ │ │ ├── index.ts │ │ │ │ ├── math_tool.ts │ │ │ │ ├── prompts.ts │ │ │ │ └── weather_tool.ts │ │ │ └── sync_bedrock_knowledgebase/ │ │ │ └── lambda.py │ │ ├── lib/ │ │ │ ├── CustomResourcesLambda/ │ │ │ │ ├── aoss-index-create.ts │ │ │ │ ├── data-source-sync.ts │ │ │ │ └── permission-validation.ts │ │ │ ├── airlines.yaml │ │ │ ├── bedrock-agent-construct.ts │ │ │ ├── chat-demo-app-stack.ts │ │ │ ├── constants.ts │ │ │ ├── knowledge-base-construct.ts │ │ │ ├── lex-agent-construct.ts │ │ │ ├── user-interface-stack.ts │ │ │ └── utils/ │ │ │ ├── OpensearchServerlessHelper.ts │ │ │ └── utils.ts │ │ ├── package.json │ │ ├── scripts/ │ │ │ └── download.js │ │ ├── test/ │ │ │ └── chat-demo-app.ts │ │ ├── tsconfig.json │ │ └── ui/ │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── .vscode/ │ │ │ ├── extensions.json │ │ │ └── launch.json │ │ ├── README.md │ │ ├── astro.config.mjs │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── ChatWindow.tsx │ │ │ │ ├── emojiHelper.ts │ │ │ │ └── loadingScreen.tsx │ │ │ ├── pages/ │ │ │ │ └── index.astro │ │ │ └── utils/ │ │ │ ├── ApiClient.ts │ │ │ └── amplifyConfig.ts │ │ ├── tailwind.config.cjs │ │ └── tsconfig.json │ ├── ecommerce-support-simulator/ │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── bin/ │ │ │ └── ai-ecommerce-support-simulator.ts │ │ ├── cdk.json │ │ ├── graphql/ │ │ │ ├── Query.sendMessage.js │ │ │ ├── schema.graphql │ │ │ ├── sendResponse.js │ │ │ └── sendResponsePipeline.js │ │ ├── jest.config.js │ │ ├── lambda/ │ │ │ ├── customerMessage/ │ │ │ │ ├── agents.ts │ │ │ │ ├── index.ts │ │ │ │ └── sqsLogger.ts │ │ │ ├── sendResponse/ │ │ │ │ └── index.ts │ │ │ └── supportMessage/ │ │ │ └── index.ts │ │ ├── lib/ │ │ │ ├── ai-ecommerce-support-simulator-stack.ts │ │ │ └── utils/ │ │ │ └── utils.ts │ │ ├── package.json │ │ ├── resources/ │ │ │ └── ui/ │ │ │ ├── .gitignore │ │ │ ├── .vscode/ │ │ │ │ ├── extensions.json │ │ │ │ └── launch.json │ │ │ ├── README.md │ │ │ ├── astro.config.mjs │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ └── mock_data.json │ │ │ ├── src/ │ │ │ │ ├── components/ │ │ │ │ │ ├── ChatMode.tsx │ │ │ │ │ ├── EmailMode.tsx │ │ │ │ │ ├── SupportSimulator.tsx │ │ │ │ │ └── email-templates.json │ │ │ │ ├── consts.ts │ │ │ │ ├── content/ │ │ │ │ │ └── config.ts │ │ │ │ ├── layouts/ │ │ │ │ │ └── Layout.astro │ │ │ │ ├── pages/ │ │ │ │ │ └── index.astro │ │ │ │ ├── styles/ │ │ │ │ │ └── global.css │ │ │ │ ├── types.ts │ │ │ │ └── utils/ │ │ │ │ └── amplifyConfig.ts │ │ │ ├── tailwind.config.js │ │ │ └── tsconfig.json │ │ ├── test/ │ │ │ └── ai-ecommerce-support-simulator.test.ts │ │ └── tsconfig.json │ ├── fast-api-streaming/ │ │ ├── README.MD │ │ ├── main.py │ │ └── requirements.txt │ ├── langfuse-demo/ │ │ ├── main.py │ │ ├── readme.md │ │ ├── requirements.txt │ │ └── tools/ │ │ └── weather_tool.py │ ├── local-demo/ │ │ ├── local-orchestrator.ts │ │ ├── package.json │ │ └── tools/ │ │ ├── math_tool.ts │ │ └── weather_tool.ts │ ├── python/ │ │ ├── imports.py │ │ ├── main-app.py │ │ ├── movie-production/ │ │ │ ├── movie-production-demo.py │ │ │ ├── readme.md │ │ │ ├── requirements.txt │ │ │ └── search_web.py │ │ ├── pages/ │ │ │ └── home.py │ │ ├── readme.md │ │ ├── requirements.txt │ │ └── travel-planner/ │ │ ├── readme.md │ │ ├── requirements.txt │ │ ├── search_web.py │ │ └── travel-planner-demo.py │ ├── python-demo/ │ │ ├── main-stream.py │ │ ├── main.py │ │ └── tools/ │ │ └── weather_tool.py │ ├── strands-agents-demo/ │ │ ├── main.py │ │ └── requirements.txt │ ├── supervisor-mode/ │ │ ├── main.py │ │ └── weather_tool.py │ ├── text-2-structured-output/ │ │ ├── README.md │ │ ├── multi_agent_query_analyzer.py │ │ ├── product_search_agent.py │ │ ├── prompts.py │ │ └── requirements.txt │ └── tools/ │ └── python/ │ └── weather_tool_example.py ├── python/ │ ├── .gitignore │ ├── CONTRIBUTING.md │ ├── Makefile │ ├── README.md │ ├── pyproject.toml │ ├── ruff.toml │ ├── setup.cfg │ ├── setup.py │ ├── src/ │ │ ├── agent_squad/ │ │ │ ├── __init__.py │ │ │ ├── agents/ │ │ │ │ ├── __init__.py │ │ │ │ ├── agent.py │ │ │ │ ├── amazon_bedrock_agent.py │ │ │ │ ├── anthropic_agent.py │ │ │ │ ├── bedrock_flows_agent.py │ │ │ │ ├── bedrock_inline_agent.py │ │ │ │ ├── bedrock_llm_agent.py │ │ │ │ ├── bedrock_translator_agent.py │ │ │ │ ├── chain_agent.py │ │ │ │ ├── comprehend_filter_agent.py │ │ │ │ ├── lambda_agent.py │ │ │ │ ├── lex_bot_agent.py │ │ │ │ ├── openai_agent.py │ │ │ │ ├── strands_agent.py │ │ │ │ └── supervisor_agent.py │ │ │ ├── classifiers/ │ │ │ │ ├── __init__.py │ │ │ │ ├── anthropic_classifier.py │ │ │ │ ├── bedrock_classifier.py │ │ │ │ ├── classifier.py │ │ │ │ └── openai_classifier.py │ │ │ ├── orchestrator.py │ │ │ ├── retrievers/ │ │ │ │ ├── __init__.py │ │ │ │ ├── amazon_kb_retriever.py │ │ │ │ └── retriever.py │ │ │ ├── shared/ │ │ │ │ ├── __init__.py │ │ │ │ ├── user_agent.py │ │ │ │ └── version.py │ │ │ ├── storage/ │ │ │ │ ├── __init__.py │ │ │ │ ├── chat_storage.py │ │ │ │ ├── dynamodb_chat_storage.py │ │ │ │ ├── in_memory_chat_storage.py │ │ │ │ └── sql_chat_storage.py │ │ │ ├── types/ │ │ │ │ ├── __init__.py │ │ │ │ └── types.py │ │ │ └── utils/ │ │ │ ├── __init__.py │ │ │ ├── helpers.py │ │ │ ├── logger.py │ │ │ └── tool.py │ │ └── tests/ │ │ ├── __init__.py │ │ ├── agents/ │ │ │ ├── __init__.py │ │ │ ├── test_agent.py │ │ │ ├── test_amazon_bedrock_agent.py │ │ │ ├── test_anthropic_agent.py │ │ │ ├── test_bedrock_flows_agent.py │ │ │ ├── test_bedrock_inline_agent.py │ │ │ ├── test_bedrock_llm_agent.py │ │ │ ├── test_comprehend_agent.py │ │ │ ├── test_lambda_agent.py │ │ │ ├── test_lex_bot_agent.py │ │ │ ├── test_openai_agent.py │ │ │ ├── test_strands_agent.py │ │ │ └── test_supervisor_agent.py │ │ ├── classifiers/ │ │ │ ├── __init__.py │ │ │ ├── test_anthropic_classifier.py │ │ │ └── test_classifier.py │ │ ├── pytest.ini │ │ ├── retrievers/ │ │ │ └── test_retriever.py │ │ ├── storage/ │ │ │ ├── __init__.py │ │ │ ├── test_chat_storage.py │ │ │ ├── test_dynamodb_chat_storage.py │ │ │ ├── test_in_memory_chat_storage.py │ │ │ └── test_sql_chat_storage.py │ │ ├── test_orchestrator.py │ │ └── utils/ │ │ ├── test_helpers.py │ │ ├── test_logger.py │ │ └── test_tool.py │ └── test_requirements.txt └── typescript/ ├── .eslintrc.js ├── .npmignore ├── README.md ├── jest.config.js ├── package.json ├── src/ │ ├── agentOverlapAnalyzer.ts │ ├── agents/ │ │ ├── agent.ts │ │ ├── amazonBedrockAgent.ts │ │ ├── anthropicAgent.ts │ │ ├── bedrockFlowsAgent.ts │ │ ├── bedrockInlineAgent.ts │ │ ├── bedrockLLMAgent.ts │ │ ├── bedrockTranslatorAgent.ts │ │ ├── chainAgent.ts │ │ ├── comprehendFilterAgent.ts │ │ ├── lambdaAgent.ts │ │ ├── lexBotAgent.ts │ │ ├── openAIAgent.ts │ │ └── supervisorAgent.ts │ ├── classifiers/ │ │ ├── anthropicClassifier.ts │ │ ├── bedrockClassifier.ts │ │ ├── classifier.ts │ │ └── openAIClassifier.ts │ ├── common/ │ │ └── src/ │ │ ├── awsSdkUtils.ts │ │ ├── types/ │ │ │ └── awsSdk.ts │ │ └── version.ts │ ├── index.ts │ ├── orchestrator.ts │ ├── retrievers/ │ │ ├── AmazonKBRetriever.ts │ │ └── retriever.ts │ ├── storage/ │ │ ├── chatStorage.ts │ │ ├── dynamoDbChatStorage.ts │ │ ├── memoryChatStorage.ts │ │ └── sqlChatStorage.ts │ ├── types/ │ │ └── index.ts │ └── utils/ │ ├── chatUtils.ts │ ├── helpers.ts │ ├── logger.ts │ └── tool.ts ├── tests/ │ ├── Orchestrator.test.ts │ ├── agents/ │ │ ├── Agents.test.ts │ │ ├── LambdaAgent.test.ts │ │ └── OpenAi.test.ts │ ├── classifiers/ │ │ ├── AnthropicClassifier.test.ts │ │ ├── BedrockClassifier.test.ts │ │ ├── Classifier.test.ts │ │ └── OpenAIClassifier.test.ts │ ├── mock/ │ │ └── mockAgent.ts │ ├── retrievers/ │ │ └── Retriever.test.ts │ ├── storage/ │ │ └── ChatStorage.test.ts │ └── utils/ │ └── Utils.test.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Include TypeScript and Python as detectable languages *.py linguist-detectable=true *.ts linguist-detectable=true *.js linguist-detectable=false ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug report description: Report a reproducible bug to help us improve title: "Bug: TITLE" labels: ["bug"] body: - type: markdown attributes: value: | Thank you for submitting a bug report. Please add as much information as possible to help us reproduce, and remove any potential sensitive data. - type: textarea id: expected_behaviour attributes: label: Expected Behaviour description: Please share details on the behaviour you expected validations: required: true - type: textarea id: current_behaviour attributes: label: Current Behaviour description: Please share details on the current issue validations: required: true - type: textarea id: code_snippet attributes: label: Code snippet description: Please share a code snippet to help us reproduce the issue render: python validations: required: true - type: textarea id: solution attributes: label: Possible Solution description: If known, please suggest a potential resolution validations: required: false - type: textarea id: steps attributes: label: Steps to Reproduce description: Please share how we might be able to reproduce this issue validations: required: true - type: markdown attributes: value: | --- ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature request description: Suggest an idea for Agent Squad title: "Feature request: TITLE" labels: ["feature-request", "triage"] body: - type: markdown attributes: value: | Thank you for taking the time to suggest an idea to the Agent Squad project. *Future readers*: Please react with 👍 and your use case to help us understand customer demand. - type: textarea id: problem attributes: label: Use case description: Please help us understand your use case or problem you're facing validations: required: true - type: textarea id: suggestion attributes: label: Solution/User Experience description: Please share what a good solution would look like to this use case validations: required: true - type: textarea id: alternatives attributes: label: Alternative solutions description: Please describe what alternative solutions to this use case, if any render: markdown validations: required: false ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Issue Link (REQUIRED) Fixes # ## Summary ### Changes ### User experience ## Checklist If your change doesn't seem to apply, please leave them unchecked. * [ ] I have performed a self-review of this change * [ ] Changes have been tested * [ ] Changes are documented * [ ] I have linked this PR to an existing issue (required)
Is this a breaking change? **RFC issue number**: Checklist: * [ ] Migration process documented * [ ] Implement warnings (if it can live side by side)
## Acknowledgment By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. **Disclaimer**: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful. ================================================ FILE: .github/workflows/npm-publish.yml ================================================ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages name: Publish Typescript Package to NPM on: workflow_dispatch: jobs: build-and-publish: runs-on: ubuntu-latest defaults: run: working-directory: typescript steps: - uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 with: node-version: 20 registry-url: https://registry.npmjs.org/ - run: cp ../LICENSE . - run: npm install - run: npm run build - run: npm pack - run: npm publish --access=public env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} ================================================ FILE: .github/workflows/on-docs-update.yml ================================================ name: Build and Deploy Documentation on: push: branches: - main paths: - 'docs/**' workflow_dispatch: permissions: contents: read pages: write id-token: write jobs: # Build the documentation. build: concurrency: ci-${{ github.ref }} runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install, build, and upload documentation uses: withastro/action@v2 with: path: ./docs - name: Upload artifact uses: actions/upload-artifact@v4 with: path: ./docs/dist # Deploy the documentation to GitHub Pages. deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@7a9bd943aa5e5175aeb8502edcc6c1c02d398e10 ================================================ FILE: .github/workflows/on-issue-opened.yml ================================================ name: Label issues on: issues: types: - reopened - opened permissions: issues: write jobs: label_issues: runs-on: ubuntu-latest steps: - uses: actions/github-script@1f16022c7518aad314c43abcd029895291be0f52 with: script: | github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, labels: ["triage"] }) ================================================ FILE: .github/workflows/on-push.yml ================================================ name: Push Workflow on: push: branches: - main pull_request: types: - opened - edited permissions: contents: read jobs: security-checks: uses: ./.github/workflows/ts-run-security-checks.yml secrets: inherit ================================================ FILE: .github/workflows/pr-issue-link-checker.yml ================================================ name: PR Issue Link Checker on: pull_request: types: [opened, edited, reopened, synchronize] jobs: check-issue-link: runs-on: ubuntu-latest steps: - name: Check for Linked Issue uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { owner, repo, number } = context.issue; // Get the PR details const pr = await github.rest.pulls.get({ owner, repo, pull_number: number }); // Check PR body for issue links const body = pr.data.body || ''; // Regular expressions to match different formats of issue links const issueRegexes = [ /#(\d+)/, // #123 /[Cc]loses #(\d+)/, // Closes #123 /[Ff]ixes #(\d+)/, // Fixes #123 /[Rr]esolves #(\d+)/, // Resolves #123 /[Cc]lose #(\d+)/, // Close #123 /[Ff]ix #(\d+)/, // Fix #123 /[Rr]esolve #(\d+)/, // Resolve #123 /[Cc]loses: #(\d+)/, // Closes: #123 /[Ff]ixes: #(\d+)/, // Fixes: #123 /[Rr]esolves: #(\d+)/, // Resolves: #123 /[Cc]lose: #(\d+)/, // Close: #123 /[Ff]ix: #(\d+)/, // Fix: #123 /[Rr]esolve: #(\d+)/, // Resolve: #123 /(?:issues?|closes?|fixes?|resolves?)[ ]*?(?:\/|#)(\d+)/i // Various other formats ]; let hasIssueLink = false; // Also check if the PR is linked to issues through GitHub's UI const linkedIssues = await github.rest.issues.listEventsForTimeline({ owner, repo, issue_number: number }); const crossReferences = linkedIssues.data.filter(event => event.event === 'cross-referenced' && event.source?.issue?.html_url.includes(`/${owner}/${repo}/issues/`) ); if (crossReferences.length > 0) { hasIssueLink = true; } // Check for issue links in PR body if (!hasIssueLink) { for (const regex of issueRegexes) { if (regex.test(body)) { hasIssueLink = true; break; } } } if (!hasIssueLink) { core.setFailed('Pull request must be linked to an issue. Please add a reference to an issue in your PR description (e.g., "Fixes #123") or link an issue through the GitHub UI.'); } ================================================ FILE: .github/workflows/py-run-tests.yml ================================================ name: Run Python tests on: push: branches: - main paths: - "python/**" pull_request: paths: - "python/**" workflow_dispatch: permissions: contents: read jobs: test_and_quality_check: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: ["3.11","3.12","3.13"] env: PYTHON: "${{ matrix.python-version }}" permissions: contents: read # checkout code only defaults: run: working-directory: python steps: - name: Checkout repository uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - name: Set up Python uses: actions/setup-python@2bd53f9a4d1dd1cd21eaffcc01a7b91a8e73ea4c with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | pip install --upgrade pip pip install -r test_requirements.txt - name: Code format and linter with Ruff run: make code-quality - name: Run tests run: make test ================================================ FILE: .github/workflows/pypi-publish.yml ================================================ name: Publish Python Package to PyPI on: workflow_dispatch: jobs: build-and-publish: runs-on: ubuntu-latest defaults: run: working-directory: python steps: - uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - name: Copy files run: | cp ../LICENSE . - name: Set up Python uses: actions/setup-python@2bd53f9a4d1dd1cd21eaffcc01a7b91a8e73ea4c with: python-version: '3.12' - name: Install build dependencies run: | python -m pip install --upgrade pip python -m pip install --upgrade build twine - name: Build package run: python -m build - name: Check distribution run: twine check dist/* - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{secrets.PYPI_API_TOKEN}} run: python -m twine upload dist/* --verbose ================================================ FILE: .github/workflows/ts-run-lint.yml ================================================ name: Run lint checks on the project on: push: paths: - 'typescript/**' pull_request: types: - opened - reopened - synchronize workflow_dispatch: # Allows manual triggering on any branch permissions: contents: read jobs: lint: runs-on: ubuntu-latest defaults: run: working-directory: typescript steps: - name: Checkout repository uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - name: Link Checker uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a with: fail: true args: --scheme=https . --exclude-all-private --accept '999, 429' --max-concurrency 1 --retry-wait-time 5 --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" --exclude https://docs.anthropic.com/en/api/getting-started - name: Install dependencies run: npm install - name: Run linting run: npm run lint ================================================ FILE: .github/workflows/ts-run-security-checks.yml ================================================ name: Run security checks on the project on: workflow_call: workflow_dispatch: permissions: contents: read jobs: scan: runs-on: ubuntu-latest defaults: run: working-directory: typescript env: ACTIONS_STEP_DEBUG: true steps: # Checkout and setup. - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install dependencies run: npm install # NPM audit. - name: Run audit run: npm audit continue-on-error: true # GitLeaks. - name: Run Gitleaks uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} ================================================ FILE: .github/workflows/ts-run-tests.yml ================================================ name: Run Typescript tests on: push: branches: - main paths: - "typescript/**" pull_request: paths: - "typescript/**" workflow_dispatch: permissions: contents: read jobs: lint: runs-on: ubuntu-latest defaults: run: working-directory: typescript steps: - name: Checkout repository uses: actions/checkout@9a9194f87191a7e9055e3e9b95b8cfb13023bb08 - name: Link Checker uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a with: fail: true args: --scheme=https . --exclude-all-private --accept 999 --user-agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" - name: Install dependencies run: npm install - name: Run tests run: npm run coverage ================================================ FILE: .gitignore ================================================ !typescript/jest.config.js typescript/*.d.ts node_modules typescript/.package-lock.json examples/chat-demo-app/cdk.out examples/chat-demo-app/lib/**/*.js examples/chat-demo-app/bin/*.js !examples/lambda/url_rewrite/*.js examples/resources/ui/public/aws-exports.json examples/resources/ui/dist examples/text-2-structured-output/venv .DS_Store typescript/dist/**/* typescript/*.tgz *aws-exports.json !download.js examples/local-demo/.env typescript/coverage/**/* .venv examples/chat-chainlit-app/venv *.env *__pycache__ git-release-notes.genai.mjs ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact [opensource-codeofconduct@amazon.com](opensource-codeofconduct@amazon.com) with any additional questions or comments. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guidelines Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional documentation, we greatly value feedback and contributions from our community. Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. ## Reporting Bugs/Feature Requests We welcome you to use the GitHub issue tracker to report bugs or suggest features. When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps * The version of our code being used * Any modifications you've made relevant to the bug * Anything unusual about your environment or deployment ## Contributing via Pull Requests Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 1. You are working against the latest source on the *main* branch. 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. To send us a pull request, please: 1. Fork the repository. 2. Create a new branch to focus on the specific change you are contributing e.g. improv/lambda-agent 3. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 4. Ensure local tests pass. 5. Commit to your fork using clear commit messages. 6. Send us a pull request, answering any default questions in the pull request interface. 7. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). ## Finding contributions to work on Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. ## Security issue notifications If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. ## Licensing See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================

Agent Squad

Flexible, lightweight open-source framework for orchestrating multiple AI agents to handle complex conversations.

---

📢 New Name Alert: Multi-Agent Orchestrator is now Agent Squad! 🎉
Same powerful functionalities, new catchy name. Embrace the squad!

---

GitHub Repo npm PyPI

GitHub stars GitHub forks GitHub watchers

Last Commit Issues Pull Requests

📚 Explore Full Documentation

## 🔖 Features - 🧠 **Intelligent intent classification** — Dynamically route queries to the most suitable agent based on context and content. - 🔤 **Dual language support** — Fully implemented in both **Python** and **TypeScript**. - 🌊 **Flexible agent responses** — Support for both streaming and non-streaming responses from different agents. - 📚 **Context management** — Maintain and utilize conversation context across multiple agents for coherent interactions. - 🔧 **Extensible architecture** — Easily integrate new agents or customize existing ones to fit your specific needs. - 🌐 **Universal deployment** — Run anywhere - from AWS Lambda to your local environment or any cloud platform. - 📦 **Pre-built agents and classifiers** — A variety of ready-to-use agents and multiple classifier implementations available. ## What's the Agent Squad ❓ The Agent Squad is a flexible framework for managing multiple AI agents and handling complex conversations. It intelligently routes queries and maintains context across interactions. The system offers pre-built components for quick deployment, while also allowing easy integration of custom agents and conversation messages storage solutions. This adaptability makes it suitable for a wide range of applications, from simple chatbots to sophisticated AI systems, accommodating diverse requirements and scaling efficiently.
## 🏗️ High-level architecture flow diagram

![High-level architecture flow diagram](https://raw.githubusercontent.com/awslabs/agent-squad/main/img/flow.jpg)

1. The process begins with user input, which is analyzed by a Classifier. 2. The Classifier leverages both Agents' Characteristics and Agents' Conversation history to select the most appropriate agent for the task. 3. Once an agent is selected, it processes the user input. 4. The orchestrator then saves the conversation, updating the Agents' Conversation history, before delivering the response back to the user. ## ![](https://raw.githubusercontent.com/awslabs/agent-squad/main/img/new.png) Introducing SupervisorAgent: Agents Coordination The Agent Squad now includes a powerful new SupervisorAgent that enables sophisticated team coordination between multiple specialized agents. This new component implements a "agent-as-tools" architecture, allowing a lead agent to coordinate a team of specialized agents in parallel, maintaining context and delivering coherent responses. ![SupervisorAgent flow diagram](https://raw.githubusercontent.com/awslabs/agent-squad/main/img/flow-supervisor.jpg) Key capabilities: - 🤝 **Team Coordination** - Coordinate multiple specialized agents working together on complex tasks - ⚡ **Parallel Processing** - Execute multiple agent queries simultaneously - 🧠 **Smart Context Management** - Maintain conversation history across all team members - 🔄 **Dynamic Delegation** - Intelligently distribute subtasks to appropriate team members - 🤖 **Agent Compatibility** - Works with all agent types (Bedrock, Anthropic, Lex, etc.) The SupervisorAgent can be used in two powerful ways: 1. **Direct Usage** - Call it directly when you need dedicated team coordination for specific tasks 2. **Classifier Integration** - Add it as an agent within the classifier to build complex hierarchical systems with multiple specialized teams Here are just a few examples where this agent can be used: - Customer Support Teams with specialized sub-teams - AI Movie Production Studios - Travel Planning Services - Product Development Teams - Healthcare Coordination Systems [Learn more about SupervisorAgent →](https://awslabs.github.io/agent-squad/agents/built-in/supervisor-agent) ## 💬 Demo App In the screen recording below, we demonstrate an extended version of the demo app that uses 6 specialized agents: - **Travel Agent**: Powered by an Amazon Lex Bot - **Weather Agent**: Utilizes a Bedrock LLM Agent with a tool to query the open-meteo API - **Restaurant Agent**: Implemented as an Amazon Bedrock Agent - **Math Agent**: Utilizes a Bedrock LLM Agent with two tools for executing mathematical operations - **Tech Agent**: A Bedrock LLM Agent designed to answer questions on technical topics - **Health Agent**: A Bedrock LLM Agent focused on addressing health-related queries Watch as the system seamlessly switches context between diverse topics, from booking flights to checking weather, solving math problems, and providing health information. Notice how the appropriate agent is selected for each query, maintaining coherence even with brief follow-up inputs. The demo highlights the system's ability to handle complex, multi-turn conversations while preserving context and leveraging specialized agents across various domains. ![](https://raw.githubusercontent.com/awslabs/agent-squad/main/img/demo-app.gif?raw=true) ## 🎯 Examples & Quick Start Get hands-on experience with the Agent Squad through our diverse set of examples: - **Demo Applications**: - [Streamlit Global Demo](https://github.com/awslabs/agent-squad/tree/main/examples/python): A single Streamlit application showcasing multiple demos, including: - AI Movie Production Studio - AI Travel Planner - [Chat Demo App](https://awslabs.github.io/agent-squad/cookbook/examples/chat-demo-app/): - Explore multiple specialized agents handling various domains like travel, weather, math, and health - [E-commerce Support Simulator](https://awslabs.github.io/agent-squad/cookbook/examples/ecommerce-support-simulator/): Experience AI-powered customer support with: - Automated response generation for common queries - Intelligent routing of complex issues to human support - Real-time chat and email-style communication - Human-in-the-loop interactions for complex cases - **Sample Projects**: Explore our example implementations in the `examples` folder: - [`chat-demo-app`](https://github.com/awslabs/agent-squad/tree/main/examples/chat-demo-app): Web-based chat interface with multiple specialized agents - [`ecommerce-support-simulator`](https://github.com/awslabs/agent-squad/tree/main/examples/ecommerce-support-simulator): AI-powered customer support system - [`chat-chainlit-app`](https://github.com/awslabs/agent-squad/tree/main/examples/chat-chainlit-app): Chat application built with Chainlit - [`fast-api-streaming`](https://github.com/awslabs/agent-squad/tree/main/examples/fast-api-streaming): FastAPI implementation with streaming support - [`text-2-structured-output`](https://github.com/awslabs/agent-squad/tree/main/examples/text-2-structured-output): Natural Language to Structured Data - [`bedrock-inline-agents`](https://github.com/awslabs/agent-squad/tree/main/examples/bedrock-inline-agents): Bedrock Inline Agents sample - [`bedrock-prompt-routing`](https://github.com/awslabs/agent-squad/tree/main/examples/bedrock-prompt-routing): Bedrock Prompt Routing sample code Examples are available in both Python and TypeScript. Check out our [documentation](https://awslabs.github.io/agent-squad/) for comprehensive guides on setting up and using the Agent Squad framework! ## 📚 Deep Dives: Stories, Blogs & Podcasts Discover creative implementations and diverse applications of the Agent Squad: - **[From 'Bonjour' to 'Boarding Pass': Multilingual AI Chatbot for Flight Reservations](https://community.aws/content/2lCi8jEKydhDm8eE8QFIQ5K23pF/from-bonjour-to-boarding-pass-multilingual-ai-chatbot-for-flight-reservations)** This article demonstrates how to build a multilingual chatbot using the Agent Squad framework. The article explains how to use an **Amazon Lex** bot as an agent, along with 2 other new agents to make it work in many languages with just a few lines of code. - **[Beyond Auto-Replies: Building an AI-Powered E-commerce Support system](https://community.aws/content/2lq6cYYwTYGc7S3Zmz28xZoQNQj/beyond-auto-replies-building-an-ai-powered-e-commerce-support-system)** This article demonstrates how to build an AI-driven multi-agent system for automated e-commerce customer email support. It covers the architecture and setup of specialized AI agents using the Agent Squad framework, integrating automated processing with human-in-the-loop oversight. The guide explores email ingestion, intelligent routing, automated response generation, and human verification, providing a comprehensive approach to balancing AI efficiency with human expertise in customer support. - **[Speak Up, AI: Voicing Your Agents with Amazon Connect, Lex, and Bedrock](https://community.aws/content/2mt7CFG7xg4yw6GRHwH9akhg0oD/speak-up-ai-voicing-your-agents-with-amazon-connect-lex-and-bedrock)** This article demonstrates how to build an AI customer call center. It covers the architecture and setup of specialized AI agents using the Agent Squad framework interacting with voice via **Amazon Connect** and **Amazon Lex**. - **[Unlock Bedrock InvokeInlineAgent API's Hidden Potential](https://community.aws/content/2pTsHrYPqvAbJBl9ht1XxPOSPjR/unlock-bedrock-invokeinlineagent-api-s-hidden-potential-with-agent-squad)** Learn how to scale **Amazon Bedrock Agents** beyond knowledge base limitations using the Agent Squad framework and **InvokeInlineAgent API**. This article demonstrates dynamic agent creation and knowledge base selection for enterprise-scale AI applications. - **[Supercharging Amazon Bedrock Flows](https://community.aws/content/2phMjQ0bqWMg4PBwejBs1uf4YQE/supercharging-amazon-bedrock-flows-with-aws-agent-squad)** Learn how to enhance **Amazon Bedrock Flows** with conversation memory and multi-flow orchestration using the Agent Squad framework. This guide shows how to overcome Bedrock Flows' limitations to build more sophisticated AI workflows with persistent memory and intelligent routing between flows. ### 🎙️ Podcast Discussions - **🇫🇷 Podcast (French)**: L'orchestrateur multi-agents : Un orchestrateur open source pour vos agents IA - **Platforms**: - [Apple Podcasts](https://podcasts.apple.com/be/podcast/lorchestrateur-multi-agents/id1452118442?i=1000684332612) - [Spotify](https://open.spotify.com/episode/4RdMazSRhZUyW2pniG91Vf) - **🇬🇧 Podcast (English)**: An Orchestrator for Your AI Agents - **Platforms**: - [Apple Podcasts](https://podcasts.apple.com/us/podcast/an-orchestrator-for-your-ai-agents/id1574162669?i=1000677039579) - [Spotify](https://open.spotify.com/episode/2a9DBGZn2lVqVMBLWGipHU) ### TypeScript Version #### Installation > 🔄 `multi-agent-orchestrator` becomes `agent-squad` ```bash npm install agent-squad ``` #### Usage The following example demonstrates how to use the Agent Squad with two different types of agents: a Bedrock LLM Agent with Converse API support and a Lex Bot Agent. This showcases the flexibility of the system in integrating various AI services. ```typescript import { AgentSquad, BedrockLLMAgent, LexBotAgent } from "agent-squad"; const orchestrator = new AgentSquad(); // Add a Bedrock LLM Agent with Converse API support orchestrator.addAgent( new BedrockLLMAgent({ name: "Tech Agent", description: "Specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.", streaming: true }) ); // Add a Lex Bot Agent for handling travel-related queries orchestrator.addAgent( new LexBotAgent({ name: "Travel Agent", description: "Helps users book and manage their flight reservations", botId: process.env.LEX_BOT_ID, botAliasId: process.env.LEX_BOT_ALIAS_ID, localeId: "en_US", }) ); // Example usage const response = await orchestrator.routeRequest( "I want to book a flight", 'user123', 'session456' ); // Handle the response (streaming or non-streaming) if (response.streaming == true) { console.log("\n** RESPONSE STREAMING ** \n"); // Send metadata immediately console.log(`> Agent ID: ${response.metadata.agentId}`); console.log(`> Agent Name: ${response.metadata.agentName}`); console.log(`> User Input: ${response.metadata.userInput}`); console.log(`> User ID: ${response.metadata.userId}`); console.log(`> Session ID: ${response.metadata.sessionId}`); console.log( `> Additional Parameters:`, response.metadata.additionalParams ); console.log(`\n> Response: `); // Stream the content for await (const chunk of response.output) { if (typeof chunk === "string") { process.stdout.write(chunk); } else { console.error("Received unexpected chunk type:", typeof chunk); } } } else { // Handle non-streaming response (AgentProcessingResult) console.log("\n** RESPONSE ** \n"); console.log(`> Agent ID: ${response.metadata.agentId}`); console.log(`> Agent Name: ${response.metadata.agentName}`); console.log(`> User Input: ${response.metadata.userInput}`); console.log(`> User ID: ${response.metadata.userId}`); console.log(`> Session ID: ${response.metadata.sessionId}`); console.log( `> Additional Parameters:`, response.metadata.additionalParams ); console.log(`\n> Response: ${response.output}`); } ``` ### Python Version > 🔄 `multi-agent-orchestrator` becomes `agent-squad` ```bash # Optional: Set up a virtual environment python -m venv venv source venv/bin/activate # On Windows use `venv\Scripts\activate` pip install agent-squad[aws] ``` #### Default Usage Here's an equivalent Python example demonstrating the use of the Agent Squad with a Bedrock LLM Agent and a Lex Bot Agent: ```python import sys import asyncio from agent_squad.orchestrator import AgentSquad from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions, AgentStreamResponse orchestrator = AgentSquad() tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", streaming=True, description="Specializes in technology areas including software development, hardware, AI, \ cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs \ related to technology products and services.", model_id="anthropic.claude-3-sonnet-20240229-v1:0", )) orchestrator.add_agent(tech_agent) health_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Health Agent", streaming=True, description="Specializes in health and well being", )) orchestrator.add_agent(health_agent) async def main(): # Example usage response = await orchestrator.route_request( "What is AWS Lambda?", 'user123', 'session456', {}, True ) # Handle the response (streaming or non-streaming) if response.streaming: print("\n** RESPONSE STREAMING ** \n") # Send metadata immediately print(f"> Agent ID: {response.metadata.agent_id}") print(f"> Agent Name: {response.metadata.agent_name}") print(f"> User Input: {response.metadata.user_input}") print(f"> User ID: {response.metadata.user_id}") print(f"> Session ID: {response.metadata.session_id}") print(f"> Additional Parameters: {response.metadata.additional_params}") print("\n> Response: ") # Stream the content async for chunk in response.output: async for chunk in response.output: if isinstance(chunk, AgentStreamResponse): print(chunk.text, end='', flush=True) else: print(f"Received unexpected chunk type: {type(chunk)}", file=sys.stderr) else: # Handle non-streaming response (AgentProcessingResult) print("\n** RESPONSE ** \n") print(f"> Agent ID: {response.metadata.agent_id}") print(f"> Agent Name: {response.metadata.agent_name}") print(f"> User Input: {response.metadata.user_input}") print(f"> User ID: {response.metadata.user_id}") print(f"> Session ID: {response.metadata.session_id}") print(f"> Additional Parameters: {response.metadata.additional_params}") print(f"\n> Response: {response.output.content}") if __name__ == "__main__": asyncio.run(main()) ``` These examples showcase: 1. The use of a Bedrock LLM Agent with Converse API support, allowing for multi-turn conversations. 2. Integration of a Lex Bot Agent for specialized tasks (in this case, travel-related queries). 3. The orchestrator's ability to route requests to the most appropriate agent based on the input. 4. Handling of both streaming and non-streaming responses from different types of agents. ### Modular Installation Options The Agent Squad is designed with a modular architecture, allowing you to install only the components you need while ensuring you always get the core functionality. #### Installation Options **1. AWS Integration**: ```bash pip install "agent-squad[aws]" ``` Includes core orchestration functionality with comprehensive AWS service integrations (`BedrockLLMAgent`, `AmazonBedrockAgent`, `LambdaAgent`, etc.) **2. Anthropic Integration**: ```bash pip install "agent-squad[anthropic]" ``` **3. OpenAI Integration**: ```bash pip install "agent-squad[openai]" ``` Adds OpenAI's GPT models for agents and classification, along with core packages. **4. Full Installation**: ```bash pip install "agent-squad[all]" ``` Includes all optional dependencies for maximum flexibility. ### 🙌 **We Want to Hear From You!** Have something to share, discuss, or brainstorm? We’d love to connect with you and hear about your journey with the **Agent Squad framework**. Here’s how you can get involved: - **🙌 Show & Tell**: Got a success story, cool project, or creative implementation? Share it with us in the [**Show and Tell**](https://github.com/awslabs/agent-squad/discussions/categories/show-and-tell) section. Your work might inspire the entire community! 🎉 - **💬 General Discussion**: Have questions, feedback, or suggestions? Join the conversation in our [**General Discussions**](https://github.com/awslabs/agent-squad/discussions/categories/general) section. It’s the perfect place to connect with other users and contributors. - **💡 Ideas**: Thinking of a new feature or improvement? Share your thoughts in the [**Ideas**](https://github.com/awslabs/agent-squad/discussions/categories/ideas) section. We’re always open to exploring innovative ways to make the orchestrator even better! Let’s collaborate, learn from each other, and build something incredible together! 🚀 ## 📝 Pull Request Guidelines ### Issue-First Policy This repository follows an **Issue-First** policy: - **Every pull request must be linked to an existing issue** - If there isn't an issue for the changes you want to make, please create one first - Use the issue to discuss proposed changes before investing time in implementation ### How to Link Pull Requests to Issues When creating a pull request, you must link it to an issue using one of these methods: 1. Include a reference in the PR description using keywords: - `Fixes #123` - `Resolves #123` - `Closes #123` 2. Manually link the PR to an issue through GitHub's UI: - On the right sidebar of your PR, click "Development" and then "Link an issue" ### Automated Enforcement We use GitHub Actions to automatically verify that each PR is linked to an issue. PRs without linked issues will not pass required checks and cannot be merged. This policy helps us: - Maintain clear documentation of changes and their purposes - Ensure community discussion before implementation - Keep a structured development process - Make project history more traceable and understandable ## 🤝 Contributing ⚠️ Note: Our project has been renamed from **Multi-Agent Orchestrator** to **Agent Squad**. Please use the new name in your contributions and discussions. ⚠️ We value your contributions! Before submitting changes, please start a discussion by opening an issue to share your proposal. Once your proposal is approved, here are the next steps: 1. 📚 Review our [Contributing Guide](CONTRIBUTING.md) 2. 💡 Create a [GitHub Issue](https://github.com/awslabs/agent-squad/issues) 3. 🔨 Submit a pull request ✅ Follow existing project structure and include documentation for new features. 🌟 **Stay Updated**: Star the repository to be notified about new features, improvements, and exciting developments in the Agent Squad framework! # Authors - [Corneliu Croitoru](https://www.linkedin.com/in/corneliucroitoru/) - [Anthony Bernabeu](https://www.linkedin.com/in/anthonybernabeu/) # 👥 Contributors Big shout out to our awesome contributors! Thank you for making this project better! 🌟 ⭐ 🚀 [![contributors](https://contrib.rocks/image?repo=awslabs/agent-squad&max=2000)](https://github.com/awslabs/agent-squad/graphs/contributors) Please see our [contributing guide](./CONTRIBUTING.md) for guidelines on how to propose bugfixes and improvements. ## 📄 LICENSE This project is licensed under the Apache 2.0 licence - see the [LICENSE](https://raw.githubusercontent.com/awslabs/agent-squad/main/LICENSE) file for details. ## 📄 Font License This project uses the JetBrainsMono NF font, licensed under the SIL Open Font License 1.1. For full license details, see [FONT-LICENSE.md](https://github.com/JetBrains/JetBrainsMono/blob/master/OFL.txt). ================================================ FILE: docs/.gitignore ================================================ # build output dist/ # generated types .astro/ # dependencies node_modules/ # logs npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # environment variables .env .env.production # macOS-specific files .DS_Store ================================================ FILE: docs/README.md ================================================


## 🚀 Run To run the documentation locally, clone the repository and run: ```bash npm run dev ``` ## 🧞 Commands All commands are run from the root of the project, from a terminal: | Command | Action | | :------------------------ | :----------------------------------------------- | | `npm install` | Installs dependencies | | `npm run dev` | Starts local dev server at `localhost:4321` | | `npm run build` | Build your production site to `./dist/` | | `npm run preview` | Preview your build locally, before deploying | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | | `npm run astro -- --help` | Get help using the Astro CLI | ## 👀 Want to learn more? Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat). ================================================ FILE: docs/astro.config.mjs ================================================ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; // https://astro.build/config export default defineConfig({ site: process.env.ASTRO_SITE, base: '/agent-squad', markdown: { gfm: true }, integrations: [ starlight({ title: 'Agent Squad', description: 'Flexible and powerful framework for managing multiple AI agents and handling complex conversations 🤖🚀', defaultLocale: 'en', favicon: '/src/assets/favicon.ico', customCss: [ './src/styles/landing.css', './src/styles/font.css', './src/styles/custom.css', './src/styles/terminal.css' ], social: { github: 'https://github.com/awslabs/agent-squad' }, sidebar: [ { label: 'Introduction', items: [ { label: 'Introduction', link: '/general/introduction' }, { label: 'How it works', link: '/general/how-it-works' }, { label: 'Quickstart', link: '/general/quickstart' }, { label: 'FAQ', link: '/general/faq' } ] }, { label: 'Orchestrator', items: [ { label: 'Overview', link: '/orchestrator/overview' }, ] },{ label: 'Classifier', items: [ { label: 'Overview', link: '/classifiers/overview' }, { label: 'Built-in classifiers', items: [ { label: 'Bedrock Classifier', link: '/classifiers/built-in/bedrock-classifier'}, { label: 'Anthropic Classifier', link: '/classifiers/built-in/anthropic-classifier' }, { label: 'OpenAI Classifier', link: '/classifiers/built-in/openai-classifier' }, ] }, { label: 'Custom Classifier', link: '/classifiers/custom-classifier' }, ] }, { label: 'Agents', items: [ { label: 'Overview', link: '/agents/overview' }, { label: 'Built-in Agents', items: [ { label: 'Supervisor Agent', link: '/agents/built-in/supervisor-agent' }, { label: 'Bedrock LLM Agent', link: '/agents/built-in/bedrock-llm-agent'}, { label: 'Amazon Bedrock Agent', link: '/agents/built-in/amazon-bedrock-agent' }, { label: 'Amazon Lex Bot Agent', link: '/agents/built-in/lex-bot-agent' }, { label: 'AWS Lambda Agent', link: '/agents/built-in/lambda-agent' }, { label: 'OpenAI Agent', link: '/agents/built-in/openai-agent' }, { label: 'Anthropic Agent', link: '/agents/built-in/anthropic-agent'}, { label: 'Chain Agent', link: '/agents/built-in/chain-agent' }, { label: 'Comprehend Filter Agent', link: '/agents/built-in/comprehend-filter-agent' }, { label: 'Amazon Bedrock Translator Agent', link: '/agents/built-in/bedrock-translator-agent' }, { label: 'Amazon Bedrock Inline Agent', link: '/agents/built-in/bedrock-inline-agent' }, { label: 'Bedrock Flows Agent', link: '/agents/built-in/bedrock-flows-agent' }, ] }, { label: 'Custom Agents', link: '/agents/custom-agents' }, { label: 'Tools for Agents', link: '/agents/tools' }, ] }, { label: 'Conversation Storage', items: [ { label: 'Overview', link: '/storage/overview' }, { label: 'Built-in storage', items: [ { label: 'In-Memory', link: '/storage/in-memory' }, { label: 'DynamoDB', link: '/storage/dynamodb' }, { label: 'SQL Storage', link: '/storage/sql' }, ] }, { label: 'Custom Storage', link: '/storage/custom' } ] }, { label: 'Retrievers', items: [ { label: 'Overview', link: '/retrievers/overview' }, { label: 'Built-in retrievers', items: [ { label: 'Bedrock Knowledge Base', link: '/retrievers/built-in/bedrock-kb-retriever' }, ] }, { label: 'Custom Retriever', link: '/retrievers/custom-retriever' }, ] }, { label: 'Cookbook', items: [ { label: 'Examples', items: [ { label: 'Chat Chainlit App', link: '/cookbook/examples/chat-chainlit-app' }, { label: 'Chat Demo App', link: '/cookbook/examples/chat-demo-app' }, { label: 'E-commerce Support Simulator', link: '/cookbook/examples/ecommerce-support-simulator' }, { label: 'Fast API Streaming', link: '/cookbook/examples/fast-api-streaming' }, { label: 'Typescript Local Demo', link: '/cookbook/examples/typescript-local-demo' }, { label: 'Python Local Demo', link: '/cookbook/examples/python-local-demo' }, { label: 'Api Agent', link: '/cookbook/examples/api-agent' }, { label: 'Ollama Agent', link: '/cookbook/examples/ollama-agent' }, { label: 'Ollama Classifier', link: '/cookbook/examples/ollama-classifier' } ] }, { label: 'Lambda Implementations', items: [ { label: 'Python Lambda', link: '/cookbook/lambda/aws-lambda-python' }, { label: 'NodeJs Lambda', link: '/cookbook/lambda/aws-lambda-nodejs' } ] }, { label: 'Tool Integration', items: [ { label: 'Weather API Integration', link: '/cookbook/tools/weather-api' }, { label: 'Math Operations', link: '/cookbook/tools/math-operations' } ] }, { label: 'Routing Patterns', items: [ { label: 'Cost-Efficient Routing', link: '/cookbook/patterns/cost-efficient' }, { label: 'Multi-lingual Routing', link: '/cookbook/patterns/multi-lingual' } ] }, { label: 'Optimization, Logging & Observability', items: [ { label: 'Agent Overlap Analysis', link: '/cookbook/monitoring/agent-overlap' }, { label: 'Logging', link: '/cookbook/monitoring/logging' }, { label: 'Observability', link: '/cookbook/monitoring/observability' } ] } ] } ] }) ] }); ================================================ FILE: docs/package.json ================================================ { "name": "@agent-squad/docs", "description": "The official documentation for Agent Squad", "type": "module", "version": "0.7.0", "private": true, "scripts": { "dev": "npx astro dev", "start": "npx astro dev", "build": "npx astro build", "preview": "npx astro preview", "astro": "npx astro", "audit": "npm audit", "clean": "npx rimraf .astro/ node_modules/ dist/" }, "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com" }, "repository": { "type": "git", "url": "git://github.com/awslabs/agent-squad" }, "license": "Apache-2.0", "dependencies": { "@astrojs/starlight": "^0.30.3", "astro": "^5.1.1", "sharp": "^0.33.4", "shiki": "^1.10.3" }, "devDependencies": { "rimraf": "^5.0.7" } } ================================================ FILE: docs/src/components/code.astro ================================================ --- import { ExpressiveCode, ExpressiveCodeConfig } from 'expressive-code'; import { toHtml } from 'hast-util-to-html'; import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'; import fs from 'node:fs/promises'; interface Props { file: string; language?: string; meta?: string; } const { file, language, meta } = Astro.props; const fileNamePath = '../' + file; const fileEtension = file.split('.').pop() ?? 'js'; const code = await fs.readFile(fileNamePath, 'utf-8'); const ec = new ExpressiveCode({ plugins: [pluginCollapsibleSections()], }); // Get base styles that should be included on the page // (they are independent of the rendered code blocks) const baseStyles = await ec.getBaseStyles(); // Render some example code to AST const { renderedGroupAst, styles } = await ec.render({ code: code, language: language ?? fileEtension, meta: `title="${file}"` + (meta ? ` ${meta}` : ''), }); // Convert the rendered AST to HTML let htmlContent = toHtml(renderedGroupAst); // Collect styles and add them before the HTML content const stylesToPrepend: string[] = []; stylesToPrepend.push(baseStyles); stylesToPrepend.push(...styles); if (stylesToPrepend.length) { htmlContent = `${htmlContent}`; } ---
================================================ FILE: docs/src/content/config.ts ================================================ /* * Copyright (C) 2023 Amazon.com, Inc. or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { defineCollection } from 'astro:content'; import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'; export const collections = { docs: defineCollection({ schema: docsSchema() }), i18n: defineCollection({ type: 'data', schema: i18nSchema() }), }; ================================================ FILE: docs/src/content/docs/agents/built-in/amazon-bedrock-agent.mdx ================================================ --- title: AmazonBedrockAgent description: Documentation for the AmazonBedrockAgent in the Agent Squad --- The `AmazonBedrockAgent` is a specialized agent class in the Agent Squad that integrates directly with [Amazon Bedrock agents](https://aws.amazon.com/bedrock/agents/?nc1=h_ls). ## Creating an AmazonBedrockAgent Here are various examples showing different ways to create and configure an AmazonBedrockAgent: ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` ### Basic Examples **1. Minimal Configuration** import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript const agent = new AmazonBedrockAgent({ name: 'My Bank Agent', description: 'A helpful and friendly agent that answers questions about loan-related inquiries', agentId: 'your-agent-id', agentAliasId: 'your-agent-alias-id' }); ``` ```python agent = AmazonBedrockAgent(AmazonBedrockAgentOptions( name='My Bank Agent', description='A helpful and friendly agent that answers questions about loan-related inquiries', agent_id='your-agent-id', agent_alias_id='your-agent-alias-id' )) ```
**2. Using Custom Client** ```typescript import { BedrockAgentRuntimeClient } from "@aws-sdk/client-bedrock-agent-runtime"; const customClient = new BedrockAgentRuntimeClient({ region: 'us-east-1' }); const agent = new AmazonBedrockAgent({ name: 'My Bank Agent', description: 'A helpful and friendly agent for banking inquiries', agentId: 'your-agent-id', agentAliasId: 'your-agent-alias-id', client: customClient }); ``` ```python import boto3 custom_client = boto3.client('bedrock-agent-runtime', region_name='us-east-1') agent = AmazonBedrockAgent(AmazonBedrockAgentOptions( name='My Bank Agent', description='A helpful and friendly agent for banking inquiries', agent_id='your-agent-id', agent_alias_id='your-agent-alias-id', client=custom_client )) ```
**3. With Tracing Enabled** ```typescript const agent = new AmazonBedrockAgent({ name: 'My Bank Agent', description: 'A banking agent with tracing enabled', agentId: 'your-agent-id', agentAliasId: 'your-agent-alias-id', enableTrace: true }); ``` ```python agent = AmazonBedrockAgent(AmazonBedrockAgentOptions( name='My Bank Agent', description='A banking agent with tracing enabled', agent_id='your-agent-id', agent_alias_id='your-agent-alias-id', enable_trace=True )) ```
**4. With Streaming Enabled** ```typescript const agent = new AmazonBedrockAgent({ name: 'My Bank Agent', description: 'A streaming-enabled banking agent', agentId: 'your-agent-id', agentAliasId: 'your-agent-alias-id', streaming: true }); ``` ```python agent = AmazonBedrockAgent(AmazonBedrockAgentOptions( name='My Bank Agent', description='A streaming-enabled banking agent', agent_id='your-agent-id', agent_alias_id='your-agent-alias-id', streaming=True )) ```
**5. Complete Example with All Options** ```typescript import { AmazonBedrockAgent } from "agent-squad"; import { BedrockAgentRuntimeClient } from "@aws-sdk/client-bedrock-agent-runtime"; const agent = new AmazonBedrockAgent({ // Required fields name: "Advanced Bank Agent", description: "A fully configured banking agent with all features enabled", agentId: "your-agent-id", agentAliasId: "your-agent-alias-id", // Optional fields region: "us-west-2", streaming: true, enableTrace: true, client: new BedrockAgentRuntimeClient({ region: "us-west-2" }), }); ``` ```python import boto3 from agent_squad.agents import AmazonBedrockAgent, AmazonBedrockAgentOptions custom_client = boto3.client('bedrock-agent-runtime', region_name='us-west-2') agent = AmazonBedrockAgent(AmazonBedrockAgentOptions( # Required fields name='Advanced Bank Agent', description='A fully configured banking agent with all features enabled', agent_id='your-agent-id', agent_alias_id='your-agent-alias-id', # Optional fields region='us-west-2', streaming=True, enable_trace=True, client=custom_client )) ``` ### Option Explanations - `name`: (Required) Identifies the agent within your system. - `description`: (Required) Describes the agent's purpose or capabilities. - `agentId/agent_id`: (Required) The ID of the Amazon Bedrock agent you want to use. - `agentAliasId/agent_alias_id`: (Required) The alias ID of the Amazon Bedrock agent. - `region`: (Optional) AWS region for the Bedrock service. If not provided, uses the default AWS region. - `client`: (Optional) Custom BedrockAgentRuntimeClient for specialized configurations. - `enableTrace/enable_trace`: (Optional) When set to true, enables tracing of the agent's steps and reasoning process. - `streaming`: (Optional) Enables streaming for the final response. Defaults to false. ## Adding the Agent to the Orchestrator To integrate the AmazonBedrockAgent into your Agent Squad, follow these steps: 1. First, ensure you have created an instance of the orchestrator: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() ``` 2. Then, add the agent to the orchestrator: ```typescript orchestrator.addAgent(agent); ``` ```python orchestrator.add_agent(agent) ``` 3. Now you can use the orchestrator to route requests to the appropriate agent, including your Amazon Bedrock agent: ```typescript const response = await orchestrator.routeRequest( "What is the base rate interest for 30 years?", "user123", "session456" ); ``` ```python response = await orchestrator.route_request( "What is the base rate interest for 30 years?", "user123", "session456" ) ``` --- By leveraging the `AmazonBedrockAgent`, you can easily integrate **pre-built Amazon Bedrock agents** into your Agent Squad. ================================================ FILE: docs/src/content/docs/agents/built-in/anthropic-agent.mdx ================================================ --- title: Anthropic Agent description: Documentation for the AnthropicAgent in the Agent Squad --- ## Overview The `AnthropicAgent` is a powerful and flexible agent class in the Agent Squad System. It leverages the [Anthropic API](https://docs.anthropic.com/en/api/getting-started) to interact with various Large Language Models (LLMs) provided by Anthropic, such as Claude. This agent can handle a wide range of processing tasks, making it suitable for diverse applications such as conversational AI, question-answering systems, and more. ## Key Features - Integration with Anthropic's API - Support for multiple LLM models available on Anthropic's platform - Streaming and non-streaming response options - Customizable inference configuration - Ability to set and update custom system prompts - Optional integration with retrieval systems for enhanced context - Support for Tool use within the conversation flow ## Creating an AnthropicAgent Here are various examples showing different ways to create and configure an AnthropicAgent: ### Python Package If you haven't already installed the Anthropic-related dependencies, make sure to install them: ```bash pip install "agent-squad[anthropic]" ``` ### Basic Examples **1. Minimal Configuration** import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'A versatile AI assistant', apiKey: 'your-anthropic-api-key' }); ``` ```python agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='A versatile AI assistant', api_key='your-anthropic-api-key' )) ```
**2. Using Custom Client** ```typescript import { Anthropic } from '@anthropic-ai/sdk'; const customClient = new Anthropic({ apiKey: 'your-anthropic-api-key' }); const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'A versatile AI assistant', client: customClient }); ``` ```python from anthropic import Anthropic custom_client = Anthropic(api_key='your-anthropic-api-key') agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='A versatile AI assistant', client=custom_client )) ```
**3. Custom Model and Streaming** ```typescript const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'A streaming-enabled assistant', apiKey: 'your-anthropic-api-key', modelId: 'claude-3-opus-20240229', streaming: true }); ``` ```python agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='A streaming-enabled assistant', api_key='your-anthropic-api-key', model_id='claude-3-opus-20240229', streaming=True )) ```
**4. With Inference Configuration** ```typescript const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'An assistant with custom inference settings', apiKey: 'your-anthropic-api-key', inferenceConfig: { maxTokens: 500, temperature: 0.7, topP: 0.9, stopSequences: ['Human:', 'AI:'] } }); ``` ```python agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='An assistant with custom inference settings', api_key='your-anthropic-api-key', inference_config={ 'maxTokens': 500, 'temperature': 0.7, 'topP': 0.9, 'stopSequences': ['Human:', 'AI:'] } )) ```
**5. With Simple System Prompt** ```typescript const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'An assistant with custom prompt', apiKey: 'your-anthropic-api-key', customSystemPrompt: { template: 'You are a helpful AI assistant focused on technical support.' } }); ``` ```python agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='An assistant with custom prompt', api_key='your-anthropic-api-key', custom_system_prompt={ 'template': 'You are a helpful AI assistant focused on technical support.' } )) ```
**6. With System Prompt Variables** ```typescript const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'An assistant with variable prompt', apiKey: 'your-anthropic-api-key', customSystemPrompt: { template: 'You are an AI assistant specialized in {{DOMAIN}}. Always use a {{TONE}} tone.', variables: { DOMAIN: 'customer support', TONE: 'friendly and helpful' } } }); ``` ```python agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='An assistant with variable prompt', api_key='your-anthropic-api-key', custom_system_prompt={ 'template': 'You are an AI assistant specialized in {{DOMAIN}}. Always use a {{TONE}} tone.', 'variables': { 'DOMAIN': 'customer support', 'TONE': 'friendly and helpful' } } )) ```
**7. With Custom Retriever** ```typescript const retriever = new CustomRetriever({ // Retriever configuration }); const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'An assistant with retriever', apiKey: 'your-anthropic-api-key', retriever: retriever }); ``` ```python retriever = CustomRetriever( # Retriever configuration ) agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='An assistant with retriever', api_key='your-anthropic-api-key', retriever=retriever )) ```
**8. With Tool Configuration** ```typescript const agent = new AnthropicAgent({ name: 'Anthropic Assistant', description: 'An assistant with tool support', apiKey: 'your-anthropic-api-key', toolConfig: { tool: [ { name: "Weather_Tool", description: "Get current weather data", input_schema: { type: "object", properties: { location: { type: "string", description: "City name", } }, required: ["location"] } } ], useToolHandler: (response, conversation) => { return { role: ParticipantRole.USER, content: { "type": "tool_result", "tool_use_id": "weather_tool", "content": "Current weather data for the location" } } } } }); ``` ```python agent = AnthropicAgent(AnthropicAgentOptions( name='Anthropic Assistant', description='An assistant with tool support', api_key='your-anthropic-api-key', tool_config={ 'tool': [{ 'name': 'Weather_Tool', 'description': 'Get current weather data', 'input_schema': { 'type': 'object', 'properties': { 'location': { 'type': 'string', 'description': 'City name' } }, 'required': ['location'] } }], 'useToolHandler': lambda response, conversation: { 'role': ParticipantRole.USER.value, 'content': { 'type': 'tool_result', 'tool_use_id': 'weather_tool', 'content': 'Current weather data for the location' } } } )) ```
**9. With Reasoning enabled** ```typescript import { AnthropicAgent } from 'agent-squad'; const agent = new AnthropicAgent({ name: "Tech Agent", description: "Specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.", inferenceConfig: { maxTokens: 2500, temperature: 1, // 1 for thinking topP: 0.96 // 0.95 or above }, modelId: "claude-3-7-sonnet-20250219", // Claude 3.7 or above thinking: {type: "enabled", budget_tokens: 1024}, streaming: true, apiKey: 'your-anthropic-api-key', }); ``` ```python agent = AnthropicAgent( AnthropicAgentOptions( name="Tech Agent", api_key='your-anthropic-api-key', streaming=True, description="Specializes in technology areas including software development, hardware, AI, \ cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs \ related to technology products and services.", model_id="claude-3-7-sonnet-20250219", callbacks=LLMAgentCallbacks(), inference_config={"maxTokens": 2500, "temperature": 1, "topP": 0.95}, # temperature set to 1 and topP 0.95 or above thinking={"type": "enabled", "budget_tokens": 2000}, ) ) ```
**10. Complete Example with All Options** ```typescript import { AnthropicAgent } from 'agent-squad'; const agent = new AnthropicAgent({ // Required fields name: 'Advanced Anthropic Assistant', description: 'A fully configured AI assistant powered by Anthropic models', apiKey: 'your-anthropic-api-key', // Optional fields modelId: 'claude-3-opus-20240229', // Choose Anthropic model streaming: true, // Enable streaming responses retriever: customRetriever, // Custom retriever for additional context // Inference configuration inferenceConfig: { maxTokens: 500, // Maximum tokens to generate temperature: 0.7, // Control randomness (0-1) topP: 0.9, // Control diversity via nucleus sampling stopSequences: ['Human:', 'AI:'] // Sequences that stop generation }, // Tool configuration toolConfig: { tool: [{ name: "Weather_Tool", description: "Get the current weather for a given location", input_schema: { type: "object", properties: { latitude: { type: "string", description: "Geographical WGS84 latitude" }, longitude: { type: "string", description: "Geographical WGS84 longitude" } }, required: ["latitude", "longitude"] } }], useToolHandler: (response, conversation) => ({ role: ParticipantRole.USER, content: { type: "tool_result", tool_use_id: "tool_user_id", content: "Response from the tool" } }) }, // Custom system prompt with variables customSystemPrompt: { template: `You are an AI assistant specialized in {{DOMAIN}}. Your core competencies: {{SKILLS}} Communication style: - Maintain a {{TONE}} tone - Focus on {{FOCUS}} - Prioritize {{PRIORITY}}`, variables: { DOMAIN: 'scientific research', SKILLS: [ '- Advanced data analysis', '- Statistical methodology', '- Research design', '- Technical writing' ], TONE: 'professional and academic', FOCUS: 'accuracy and clarity', PRIORITY: 'evidence-based insights' } } }); ``` ```python from agent_squad import AnthropicAgent, AnthropicAgentOptions from agent_squad.types import ParticipantRole agent = AnthropicAgent(AnthropicAgentOptions( # Required fields name='Advanced Anthropic Assistant', description='A fully configured AI assistant powered by Anthropic models', api_key='your-anthropic-api-key', # Optional fields model_id='claude-3-opus-20240229', # Choose Anthropic model streaming=True, # Enable streaming responses retriever=custom_retriever, # Custom retriever for additional context # Inference configuration inference_config={ 'maxTokens': 500, # Maximum tokens to generate 'temperature': 0.7, # Control randomness (0-1) 'topP': 0.9, # Control diversity via nucleus sampling 'stopSequences': ['Human:', 'AI:'] # Sequences that stop generation }, # Tool configuration tool_config={ 'tool': [{ 'name': 'Weather_Tool', 'description': 'Get the current weather for a given location', 'input_schema': { 'type': 'object', 'properties': { 'latitude': { 'type': 'string', 'description': 'Geographical WGS84 latitude' }, 'longitude': { 'type': 'string', 'description': 'Geographical WGS84 longitude' } }, 'required': ['latitude', 'longitude'] } }], 'useToolHandler': lambda response, conversation: { 'role': ParticipantRole.USER.value, 'content': { 'type': 'tool_result', 'tool_use_id': 'tool_user_id', 'content': 'Response from the tool' } } }, # Custom system prompt with variables custom_system_prompt={ 'template': """You are an AI assistant specialized in {{DOMAIN}}. Your core competencies: {{SKILLS}} Communication style: - Maintain a {{TONE}} tone - Focus on {{FOCUS}} - Prioritize {{PRIORITY}}""", 'variables': { 'DOMAIN': 'scientific research', 'SKILLS': [ '- Advanced data analysis', '- Statistical methodology', '- Research design', '- Technical writing' ], 'TONE': 'professional and academic', 'FOCUS': 'accuracy and clarity', 'PRIORITY': 'evidence-based insights' } } )) ``` ### Option Explanations - `name` and `description`: Identify and describe the agent's purpose. - `apiKey`: Your Anthropic API key for authentication. - `modelId`: Specifies the LLM model to use (e.g., Claude 3 Sonnet). - `streaming`: Enables streaming responses for real-time output. - `inferenceConfig`: Fine-tunes the model's output characteristics. - `retriever`: Integrates a retrieval system for enhanced context. - `toolConfig`: Defines tools the agent can use and how to handle their responses ([See AgentTools for Agents for seamless tool definition](/agent-squad/agents/tools)) ## Setting a New Prompt You can dynamically set or update the system prompt for the agent: ```typescript agent.setSystemPrompt( `You are an AI assistant specialized in {{DOMAIN}}. Your main goal is to {{GOAL}}. Always maintain a {{TONE}} tone in your responses.`, { DOMAIN: "cybersecurity", GOAL: "help users understand and mitigate potential security threats", TONE: "professional and reassuring" } ); ``` ```python agent.set_system_prompt( """You are an AI assistant specialized in {{DOMAIN}}. Your main goal is to {{GOAL}}. Always maintain a {{TONE}} tone in your responses.""", { "DOMAIN": "cybersecurity", "GOAL": "help users understand and mitigate potential security threats", "TONE": "professional and reassuring" } ) ``` This method allows you to dynamically change the agent's behavior and focus without creating a new instance. ## Adding the Agent to the Orchestrator To integrate the **Anthropic Agent** into your orchestrator, follow these steps: 1. First, ensure you have created an instance of the orchestrator: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() ``` 2. Then, add the agent to the orchestrator: ```typescript orchestrator.addAgent(agent); ``` ```python orchestrator.add_agent(agent) ``` 3. Now you can use the orchestrator to route requests to the appropriate agent, including your Anthropic agent: ```typescript const response = await orchestrator.routeRequest( "What is the base rate interest for 30 years?", "user123", "session456" ); ``` ```python response = await orchestrator.route_request( "What is the base rate interest for 30 years?", "user123", "session456" ) ``` --- By leveraging the **AnthropicAgent**, you can create sophisticated, context-aware AI agents capable of handling a wide range of tasks and interactions, all powered by the latest LLM models available through Anthropic's platform. ================================================ FILE: docs/src/content/docs/agents/built-in/bedrock-flows-agent.mdx ================================================ --- title: Amazon Bedrock Flows Agent description: Documentation for the BedrockFlowsAgent in the Agent Squad --- ## Overview The **Bedrock Flows Agent** is a specialized agent class in the Agent Squad that integrates directly with [Amazon Bedrock Flows](https://aws.amazon.com/bedrock/flows/). This integration enables you to orchestrate your Bedrock Flows alongside other agent types (Bedrock Agent, Lex, Bedrock API...), providing a unified and flexible approach to agents orchestration. ## Key Features - Support for cross-region Bedrock Flows invocation - Support for multiple flow input output type via flow input/output encoder/decoder callbacks ## Creating a BedrockFlowsAgent ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` ### Basic Example import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { BedrockFlowsAgent } from 'agent-squad'; const techFlowAgent = new BedrockFlowsAgent({ name: 'tech-flow-agent', description: 'Specialized in AWS services', flowIdentifier: 'AEXAMPLID', flowAliasIdentifier: 'AEXAMPLEALIASID', enableTrace:true }); ``` ```python from agent_squad.agents import BedrockFlowsAgent, BedrockFlowsAgentOptions tech_flow_agent = BedrockFlowsAgent(BedrockFlowsAgentOptions( name="tech-flow-agent", description="Specializes in handling tech questions about AWS services", flowIdentifier='AEXAMPLID', flowAliasIdentifier='AEXAMPLEALIASID', enableTrace=True )) ``` ### Flow Input Encoder callback Amazon [Bedrock Flows Input](https://docs.aws.amazon.com/bedrock/latest/userguide/flows-nodes.html) supports multiple type of document output: - String - Number - Boolean - Object - Array In the default definition of the BedrockFlowsAgent, the output document type is a string. If you need to send an object, array, number or a boolean to your Flow input, you can use the flow input callback to transform the input payload based on your needs. Here are an example for TS and python: ```typescript // implementation of the custom flowInputEncoder callback const flowInputEncoder = ( agent: Agent, input: string, kwargs: { userId?: string, sessionId?: string, chatHistory?: any[], [key: string]: any // This allows any additional properties } ) => { if (agent.name == 'tech-flow-agent'){ return { "question":input, }; } else { return input } } // passing flowInputEncoder to our BedrockFlowsAgent const techFlowAgent = new BedrockFlowsAgent({ name: 'tech-flow-agent', description: 'Specialized in AWS services', flowIdentifier: 'AEXAMPLID', flowAliasIdentifier: 'AEXAMPLEALIASID', flowInputEncoder: flowInputEncoder, enableTrace: true }); ``` ```python # implementation of the custom flowInputEncoder callback def flow_input_encoder(agent:Agent, input: str, **kwargs) -> Any: if agent.name == 'tech-flow-agent': # return a dict return { "question": input } else: return input #input as string # passing flowInputEncoder to our BedrockFlowsAgent tech_flow_agent = BedrockFlowsAgent(BedrockFlowsAgentOptions( name="tech-flow-agent", description="Specializes in handling tech questions about AWS services", flowIdentifier='AEXAMPLID', flowAliasIdentifier='AEXAMPLEALIASID', flow_input_encoder=flow_input_encoder, enableTrace=True )) ``` ## Sample Code You can find sample code for using the BedrockFlowsAgent in both TypeScript and Python: - [TypeScript Sample](https://github.com/awslabs/agent-squad/tree/main/examples/bedrock-flows/typescript) - [Python Sample](https://github.com/awslabs/agent-squad/tree/main/examples/bedrock-flows/python) ================================================ FILE: docs/src/content/docs/agents/built-in/bedrock-inline-agent.mdx ================================================ --- title: Bedrock Inline Agent description: Documentation for the BedrockInlineAgent in the Agent Squad --- ## Overview The **Bedrock Inline Agent** represents a powerful new approach to dynamic agent creation. At its core, it leverages [Amazon Bedrock's Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) and its tool capabilities to interact with foundation models and orchestrate agent creation. Through a specialized tool, it intelligently analyzes user requests and selects the most relevant action groups and knowledge bases from your available resources. Once the optimal [Action Groups](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-action-create.html) and/or [Knowledge Bases](https://aws.amazon.com/bedrock/knowledge-bases/) are identified, the agent uses the [InvokeInlineAgent API](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create-inline.html) to dynamically create purpose-specific Agents for Amazon Bedrock. This eliminates the need to pre-configure static agent combinations - instead, agents are created on-demand with precisely the capabilities needed for each specific request. This architecture removes practical limits on the number of action groups and knowledge bases you can maintain. Whether you have dozens or hundreds of different action groups and knowledge bases, the agent can efficiently select and combine just the ones needed for each query. This enables sophisticated use cases that would be impractical with traditional static agent configurations. ## Key Features - Dynamic agent creation through InvokeInlineAgent API - Tool-based selection of action groups and knowledge bases - Support for multiple foundation models - Customizable inference configuration - Enhanced debug logging capabilities - Support for custom logging implementations ## Creating a BedrockInlineAgent ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` ### Basic Example import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { BedrockInlineAgent } from 'agent-squad'; import { CustomLogger } from './logger'; const actionGroups = [ { actionGroupName: "OrderManagement", description: "Handles order-related operations like status checks and updates" }, { actionGroupName: "InventoryLookup", description: "Checks product availability and stock levels" } ]; const knowledgeBases = [ { knowledgeBaseId: "KB001", description: "Product catalog and specifications" } ]; const agent = new BedrockInlineAgent({ name: 'Inline Agent Creator for Agents for Amazon Bedrock', description: 'Specialized in creating Agent to solve customer request dynamically. You are provided with a list of Action groups and Knowledge bases which can help you in answering customer request', actionGroupsList: actionGroups, knowledgeBases: knowledgeBases, region: "us-east-1", LOG_AGENT_DEBUG_TRACE: true, inferenceConfig: { maxTokens: 500, temperature: 0.5, topP: 0.9 } }); ``` ```python from agent_squad.agents import BedrockInlineAgent, BedrockInlineAgentOptions from custom_logger import CustomLogger action_groups = [ { "actionGroupName": "OrderManagement", "description": "Handles order-related operations like status checks and updates" }, { "actionGroupName": "InventoryLookup", "description": "Checks product availability and stock levels" } ] knowledge_bases = [ { "knowledgeBaseId": "KB001", "description": "Product catalog and specifications" } ] agent = BedrockInlineAgent(BedrockInlineAgentOptions( name='Inline Agent Creator for Agents for Amazon Bedrock', description='Specialized in creating Agent to solve customer request dynamically. You are provided with a list of Action groups and Knowledge bases which can help you in answering customer request', action_groups_list=action_groups, knowledge_bases=knowledge_bases, region="us-east-1", LOG_AGENT_DEBUG_TRACE=True, inference_config={ 'maxTokens': 500, 'temperature': 0.5, 'topP': 0.9 } )) ``` ## Debug Logging ### LOG_AGENT_DEBUG_TRACE When enabled, this flag activates detailed debug logging that helps you understand the agent's operation. Example output: ```text > BedrockInlineAgent > Inline Agent Creator for Agents for Amazon Bedrock > System Prompt > You are a Inline Agent Creator for Agents for Amazon Bedrock... > BedrockInlineAgent > Inline Agent Creator for Agents for Amazon Bedrock > Tool Handler Parameters > { userRequest: 'Please execute...', actionGroupNames: ['CodeInterpreterAction'], knowledgeBases: [], description: 'To solve this request...', sessionId: 'session-456' } > BedrockInlineAgent > Inline Agent Creator for Agents for Amazon Bedrock > Action Group & Knowledge Base > { actionGroups: [ { actionGroupName: 'CodeInterpreterAction', parentActionGroupSignature: 'AMAZON.CodeInterpreter' } ], knowledgeBases: [] } ``` ### Custom Logger Implementation You can provide your own logger implementation to customize log formatting and handling. Here's an example: ```typescript export class CustomLogger { private static instance: CustomLogger; private constructor() {} static getInstance(): CustomLogger { if (!CustomLogger.instance) { CustomLogger.instance = new CustomLogger(); } return CustomLogger.instance; } info(message: string, ...args: any[]): void { console.info(">>: " + message, ...args); } warn(message: string, ...args: any[]): void { console.warn(">>: " + message, ...args); } error(message: string, ...args: any[]): void { console.error(">>: " + message, ...args); } debug(message: string, ...args: any[]): void { console.debug(">>: " + message, ...args); } log(message: string, ...args: any[]): void { console.log(">>: " + message, ...args); } } ``` ```python class CustomLogger: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(CustomLogger, cls).__new__(cls) return cls._instance @classmethod def get_instance(cls): if cls._instance is None: cls._instance = CustomLogger() return cls._instance def info(self, message: str, *args): print(f">>: {message}", *args) def warn(self, message: str, *args): print(f">>: [WARNING] {message}", *args) def error(self, message: str, *args): print(f">>: [ERROR] {message}", *args) def debug(self, message: str, *args): print(f">>: [DEBUG] {message}", *args) def log(self, message: str, *args): print(f">>: {message}", *args) ``` ## Sample Code You can find sample code for using the BedrockInlineAgent in both TypeScript and Python: - [TypeScript Sample](https://github.com/awslabs/agent-squad/tree/main/examples/bedrock-inline-agents/typescript) - [Python Sample](https://github.com/awslabs/agent-squad/tree/main/examples/bedrock-inline-agents/python) The BedrockInlineAgent represents a significant advancement in agent flexibility and efficiency, enabling truly dynamic, context-aware responses while optimizing resource usage. ================================================ FILE: docs/src/content/docs/agents/built-in/bedrock-llm-agent.mdx ================================================ --- title: Bedrock LLM Agent description: Documentation for the BedrockLLMAgent in the Agent Squad --- ## Overview The **Bedrock LLM Agent** is a powerful and flexible agent class in the Agent Squad System. It leverages [Amazon Bedrock's Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) to interact with various LLMs supported by Amazon Bedrock. This agent can handle a wide range of processing tasks, making it suitable for diverse applications such as conversational AI, question-answering systems, and more. ## Key Features - Integration with Amazon Bedrock's Converse API - Support for multiple LLM models available on Amazon Bedrock - Streaming and non-streaming response options - Customizable inference configuration - Ability to set and update custom system prompts - Optional integration with [retrieval systems](/agent-squad/retrievers/overview) for enhanced context - Support for [Tool use](https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html) within the conversation flow ## Creating a BedrockLLMAgent By default, the **Bedrock LLM Agent** uses the `anthropic.claude-3-haiku-20240307-v1:0` model. ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` **1. Minimal Configuration** import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'A versatile AI assistant' }); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='A versatile AI assistant' )) ```
**2. Using Custom Client** ```typescript import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"; const customClient = new BedrockRuntimeClient({ region: 'us-east-1' }); const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'A versatile AI assistant', client: customClient }); ``` ```python import boto3 custom_client = boto3.client('bedrock-runtime', region_name='us-east-1') agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='A versatile AI assistant', client=custom_client )) ```
**3. Custom Model and Streaming** ```typescript const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'A streaming-enabled assistant', modelId: 'anthropic.claude-3-sonnet-20240229-v1:0', streaming: true }); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='A streaming-enabled assistant', model_id='anthropic.claude-3-sonnet-20240229-v1:0', streaming=True )) ```
**4. With Inference Configuration** ```typescript const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'An assistant with custom inference settings', inferenceConfig: { maxTokens: 500, temperature: 0.7, topP: 0.9, stopSequences: ['Human:', 'AI:'] } }); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='An assistant with custom inference settings', inference_config={ 'maxTokens': 500, 'temperature': 0.7, 'topP': 0.9, 'stopSequences': ['Human:', 'AI:'] } )) ```
**5. With Simple System Prompt** ```typescript const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'An assistant with custom prompt', customSystemPrompt: { template: 'You are a helpful AI assistant focused on technical support.' } }); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='An assistant with custom prompt', custom_system_prompt={ 'template': 'You are a helpful AI assistant focused on technical support.' } )) ```
**6. With System Prompt Variables** ```typescript const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'An assistant with variable prompt', customSystemPrompt: { template: 'You are an AI assistant specialized in {{DOMAIN}}. Always use a {{TONE}} tone.', variables: { DOMAIN: 'technical support', TONE: 'friendly and helpful' } } }); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='An assistant with variable prompt', custom_system_prompt={ 'template': 'You are an AI assistant specialized in {{DOMAIN}}. Always use a {{TONE}} tone.', 'variables': { 'DOMAIN': 'technical support', 'TONE': 'friendly and helpful' } } )) ```
**7. With Custom Retriever** ```typescript const retriever = new CustomRetriever({ // Retriever configuration }); const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'An assistant with retriever', retriever: retriever }); ``` ```python retriever = CustomRetriever( # Retriever configuration ) agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='An assistant with retriever', retriever=retriever )) ```
**8. With Tool Configuration** ```typescript const agent = new BedrockLLMAgent({ name: 'Bedrock Assistant', description: 'An assistant with tool support', toolConfig: { tool: [ { name: "Weather_Tool", description: "Get current weather data", input_schema: { type: "object", properties: { location: { type: "string", description: "City name", } }, required: ["location"] } } ] } }); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Bedrock Assistant', description='An assistant with tool support', tool_config={ 'tool': [{ 'name': 'Weather_Tool', 'description': 'Get current weather data', 'input_schema': { 'type': 'object', 'properties': { 'location': { 'type': 'string', 'description': 'City name' } }, 'required': ['location'] } }] } )) ```
**9. With Thinking enabled** ```typescript const agent = new BedrockLLMAgent({ name: "Tech Agent", modelId: "us.anthropic.claude-3-7-sonnet-20250219-v1:0", description:"Specializes in technology areas including software development, hardware, AI, cybersecurity, \ blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.", inferenceConfig: { maxTokens: 2500, temperature: 1, // 1 for thinking and unset topP }, additional_model_request_fields: { thinking: {type: "enabled", budget_tokens: 1024}, }, streaming: true, }); ``` ```python agent = BedrockLLMAgent( BedrockLLMAgentOptions( name="Tech Agent", streaming=False, description="Specializes in technology areas including software development, hardware, AI, \ cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs \ related to technology products and services.", model_id="us.anthropic.claude-3-7-sonnet-20250219-v1:0", callbacks=LLMAgentCallbacks(), inference_config={"maxTokens": 2500, "temperature": 1}, additional_model_request_fields={"thinking": {"type": "enabled", "budget_tokens": 2000}}, ) ) ```
**10. Complete Example with All Options** ```typescript import { BedrockLLMAgent } from "agent-squad"; const agent = new BedrockLLMAgent({ // Required fields name: "Advanced Bedrock Assistant", description: "A fully configured AI assistant powered by Bedrock models", // Optional fields modelId: "anthropic.claude-3-sonnet-20240229-v1:0", region: "us-west-2", streaming: true, retriever: customRetriever, // Custom retriever for additional context inferenceConfig: { maxTokens: 500, temperature: 0.7, topP: 0.9, stopSequences: ["Human:", "AI:"], }, guardrailConfig: { guardrailIdentifier: "my-guardrail", guardrailVersion: "1.0", }, toolConfig: { tool: [ { name: "Weather_Tool", description: "Get current weather data", input_schema: { type: "object", properties: { location: { type: "string", description: "City name", }, }, required: ["location"], }, }, ], }, customSystemPrompt: { template: `You are an AI assistant specialized in {{DOMAIN}}. Your core competencies: {{SKILLS}} Communication style: - Maintain a {{TONE}} tone - Focus on {{FOCUS}} - Prioritize {{PRIORITY}}`, variables: { DOMAIN: "scientific research", SKILLS: [ "- Advanced data analysis", "- Statistical methodology", "- Research design", "- Technical writing", ], TONE: "professional and academic", FOCUS: "accuracy and clarity", PRIORITY: "evidence-based insights", }, }, }); ``` ```python from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions agent = BedrockLLMAgent(BedrockLLMAgentOptions( # Required fields name='Advanced Bedrock Assistant', description='A fully configured AI assistant powered by Bedrock models', # Optional fields model_id='anthropic.claude-3-sonnet-20240229-v1:0', region='us-west-2', streaming=True, retriever=custom_retriever, # Custom retriever for additional context inference_config={ 'maxTokens': 500, 'temperature': 0.7, 'topP': 0.9, 'stopSequences': ['Human:', 'AI:'] }, guardrail_config={ 'guardrailIdentifier': 'my-guardrail', 'guardrailVersion': '1.0' }, tool_config={ 'tool': [{ 'name': 'Weather_Tool', 'description': 'Get current weather data', 'input_schema': { 'type': 'object', 'properties': { 'location': { 'type': 'string', 'description': 'City name' } }, 'required': ['location'] } }] }, custom_system_prompt={ 'template': """You are an AI assistant specialized in {{DOMAIN}}. Your core competencies: {{SKILLS}} Communication style: - Maintain a {{TONE}} tone - Focus on {{FOCUS}} - Prioritize {{PRIORITY}}""", 'variables': { 'DOMAIN': 'scientific research', 'SKILLS': [ '- Advanced data analysis', '- Statistical methodology', '- Research design', '- Technical writing' ], 'TONE': 'professional and academic', 'FOCUS': 'accuracy and clarity', 'PRIORITY': 'evidence-based insights' } } )) ```
The `BedrockLLMAgent` provides multiple ways to set custom prompts. You can set them either during initialization or after the agent is created, and you can use prompts with or without variables. **11. Setting Custom Prompt After Initialization (Without Variables)** ```typescript const agent = new BedrockLLMAgent({ name: 'Business Consultant', description: 'Business strategy and management expert' }); agent.setSystemPrompt(`You are a business strategy consultant. Key Areas of Focus: 1. Strategic Planning 2. Market Analysis 3. Risk Management 4. Performance Optimization When providing business advice: - Begin with clear objectives - Use data-driven insights - Consider market context - Provide actionable steps`); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Business Consultant', description='Business strategy and management expert' )) agent.set_system_prompt("""You are a business strategy consultant. Key Areas of Focus: 1. Strategic Planning 2. Market Analysis 3. Risk Management 4. Performance Optimization When providing business advice: - Begin with clear objectives - Use data-driven insights - Consider market context - Provide actionable steps""") ```
**12. Setting Custom Prompt After Initialization (With Variables)** ```typescript const agent = new BedrockLLMAgent({ name: 'Education Expert', description: 'Educational specialist and learning consultant' }); agent.setSystemPrompt( `You are a {{ROLE}} focusing on {{SPECIALTY}}. Your expertise includes: {{EXPERTISE}} Teaching approach: {{APPROACH}} Core principles: {{PRINCIPLES}} Always maintain a {{TONE}} tone.`, { ROLE: 'education specialist', SPECIALTY: 'personalized learning', EXPERTISE: [ '- Curriculum development', '- Learning assessment', '- Educational technology' ], APPROACH: [ '- Student-centered learning', '- Active engagement', '- Continuous feedback' ], PRINCIPLES: [ '- Clear objectives', '- Scaffolded learning', '- Regular assessment' ], TONE: 'supportive and encouraging' } ); ``` ```python agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='Education Expert', description='Educational specialist and learning consultant' )) agent.set_system_prompt( """You are a {{ROLE}} focusing on {{SPECIALTY}}. Your expertise includes: {{EXPERTISE}} Teaching approach: {{APPROACH}} Core principles: {{PRINCIPLES}} Always maintain a {{TONE}} tone.""", { "ROLE": "education specialist", "SPECIALTY": "personalized learning", "EXPERTISE": [ "- Curriculum development", "- Learning assessment", "- Educational technology" ], "APPROACH": [ "- Student-centered learning", "- Active engagement", "- Continuous feedback" ], "PRINCIPLES": [ "- Clear objectives", "- Scaffolded learning", "- Regular assessment" ], "TONE": "supportive and encouraging" } ) ``` ### Notes on Custom Prompts - Variables in templates use the `{{VARIABLE_NAME}}` syntax - When using arrays in variables, items are automatically joined with newlines - The same template and variable functionality is available both during initialization and after - Variables are optional - you can use plain text templates without any variables - Setting a new prompt will completely replace the previous prompt - The agent will use its default prompt if no custom prompt is specified Choose the approach that best fits your needs: - Use initialization when the prompt is part of the agent's core configuration - Use post-initialization when prompts need to be changed dynamically - Use variables when parts of the prompt need to be modified frequently - Use direct templates when the prompt is static ### Option Explanations | Parameter | Description | Required/Optional | |------------|-------------|-------------------| | `name` | Identifies the agent within the system | **Required** | | `description` | Describes the agent's purpose and capabilities | **Required** | | `modelId` | Specifies the LLM model to use (e.g., Claude 3 Sonnet) | Optional | | `region` | AWS region for the Bedrock service | Optional | | `streaming` | Enables streaming responses for real-time output | Optional | | `inferenceConfig` | Fine-tunes the model's output characteristics | Optional | | `guardrailConfig` | Applies predefined guardrails to the model's responses | Optional | | `reasoningConfig` | Enables thinking and configuration for budget_tokens | Optional | | `retriever` | Integrates a retrieval system for enhanced context | Optional | | `toolConfig` | Defines tools the agent can use and how to handle their responses | Optional | | `customSystemPrompt` | Defines the agent's system prompt and behavior, with optional variables for dynamic content | Optional | | `client` | Optional custom Bedrock client for specialized configurations | Optional | | Parameter | Description | Required/Optional | |--------|-------------|-------------------| | `name` | Identifies the agent within the system | **Required** | | `description` | Describes the agent's purpose and capabilities | **Required** | | `model_id` | Specifies the LLM model to use (e.g., Claude 3 Sonnet) | Optional | | `region` | AWS region for the Bedrock service | Optional | | `streaming` | Enables streaming responses for real-time output | Optional | | `inference_config` | Fine-tunes the model's output characteristics | Optional | | `guardrail_config` | Applies predefined guardrails to the model's responses | Optional | | `additional_model_request_fields` | Additional fields to send to the model, including thinking capability | Optional | | `retriever` | Integrates a retrieval system for enhanced context | Optional | | `tool_config` | Defines tools the agent can use and how to handle their responses | Optional | | `custom_system_prompt` | Defines the agent's system prompt and behavior, with optional variables for dynamic content | Optional | | `client` | Optional custom Bedrock client for specialized configurations | Optional | ================================================ FILE: docs/src/content/docs/agents/built-in/bedrock-translator-agent.mdx ================================================ --- title: Bedrock Translator Agent description: Documentation for the Bedrock Translator Agent in the Agent Squad System --- The `BedrockTranslatorAgent` uses Amazon Bedrock's language models to translate text between different languages. ## Key Features - Utilizes Amazon Bedrock's language models - Supports translation between multiple languages - Allows dynamic setting of source and target languages - Can be used standalone or as part of a [ChainAgent](/agent-squad/agents/built-in/chain-agent) - Configurable inference parameters for fine-tuned control ## Creating a Bedrock Translator Agent ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` ### Basic Example To create a new `BedrockTranslatorAgent` with minimal configuration: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { BedrockTranslatorAgent, BedrockTranslatorAgentOptions } from 'agent-squad'; const agent = new BedrockTranslatorAgent({ name: 'BasicTranslator', description: 'Translates text to English', targetLanguage: 'English' }); ``` ```python from agent_squad.agents import BedrockTranslatorAgent, BedrockTranslatorAgentOptions agent = BedrockTranslatorAgent(BedrockTranslatorAgentOptions( name='BasicTranslator', description='Translates text to English', target_language='English' )) ``` ### Advanced Example For more complex use cases, you can create a BedrockTranslatorAgent with custom settings: ```typescript import { BedrockTranslatorAgent, BedrockTranslatorAgentOptions, BEDROCK_MODEL_ID_CLAUDE_3_SONNET } from 'agent-squad'; const options: BedrockTranslatorAgentOptions = { name: 'AdvancedTranslator', description: 'Advanced translator with custom settings', sourceLanguage: 'French', targetLanguage: 'German', modelId: BEDROCK_MODEL_ID_CLAUDE_3_SONNET, region: 'us-west-2', inferenceConfig: { maxTokens: 2000, temperature: 0.1, topP: 0.95, stopSequences: ['###'] } }; const agent = new BedrockTranslatorAgent(options); ``` ```python from agent_squad.agents import BedrockTranslatorAgent, BedrockTranslatorAgentOptions from agent_squad.types import BEDROCK_MODEL_ID_CLAUDE_3_SONNET options = BedrockTranslatorAgentOptions( name='AdvancedTranslator', description='Advanced translator with custom settings', source_language='French', target_language='German', model_id=BEDROCK_MODEL_ID_CLAUDE_3_SONNET, region='us-west-2', inference_config={ 'maxTokens': 2000, 'temperature': 0.1, 'topP': 0.95, 'stopSequences': ['###'] } ) agent = BedrockTranslatorAgent(options) ``` ## Dynamic Language Setting To set the language during the invocation: ```typescript import { AgentSquad, BedrockTranslatorAgent } from 'agent-squad'; const translator = new BedrockTranslatorAgent({ name: 'DynamicTranslator', description: 'Translator with dynamically set languages' }); const orchestrator = new AgentSquad(); orchestrator.addAgent(translator); async function translateWithDynamicLanguages(text: string, fromLang: string, toLang: string) { translator.setSourceLanguage(fromLang); translator.setTargetLanguage(toLang); const response = await orchestrator.routeRequest( text, 'user123', 'session456' ); console.log(`Translated from ${fromLang} to ${toLang}:`, response); } // Usage translateWithDynamicLanguages("Hello, world!", "English", "French"); translateWithDynamicLanguages("Bonjour le monde!", "French", "Spanish"); ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.agents import BedrockTranslatorAgent, BedrockTranslatorAgentOptions translator = BedrockTranslatorAgent(BedrockTranslatorAgentOptions( name='DynamicTranslator', description='Translator with dynamically set languages' )) orchestrator = AgentSquad() orchestrator.add_agent(translator) async def translate_with_dynamic_languages(text: str, from_lang: str, to_lang: str): translator.set_source_language(from_lang) translator.set_target_language(to_lang) response = await orchestrator.route_request( text, 'user123', 'session456' ) print(f"Translated from {from_lang} to {to_lang}:", response) # Usage import asyncio asyncio.run(translate_with_dynamic_languages("Hello, world!", "English", "French")) asyncio.run(translate_with_dynamic_languages("Bonjour le monde!", "French", "Spanish")) ``` ## Usage with ChainAgent The `BedrockTranslatorAgent` can be effectively used within a `ChainAgent` for complex multilingual processing workflows. Here's an example that demonstrates translating user input and processing it: ```typescript import { AgentSquad, ChainAgent, BedrockTranslatorAgent, BedrockLLMAgent } from 'agent-squad'; // Create translator agents const translatorToEnglish = new BedrockTranslatorAgent({ name: 'TranslatorToEnglish', description: 'Translates input to English', targetLanguage: 'English' }); // Create a processing agent (e.g., a BedrockLLMAgent) const processor = new BedrockLLMAgent({ name: 'EnglishProcessor', description: 'Processes text in English' }); // Create a ChainAgent const chainAgent = new ChainAgent({ name: 'TranslateProcessTranslate', description: 'Translates, processes, and translates back', agents: [translatorToEnglish, processor] }); const orchestrator = new AgentSquad(); orchestrator.addAgent(chainAgent); // Function to handle user input async function handleMultilingualInput(input: string, sourceLanguage: string) { translatorToEnglish.setSourceLanguage(sourceLanguage); const response = await orchestrator.routeRequest( input, 'user123', 'session456' ); console.log('Response:', response); } // Usage handleMultilingualInput("Hola, ¿cómo estás?", "Spanish"); ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.agents import ChainAgent, BedrockTranslatorAgent, BedrockLLMAgent from agent_squad.agents import ChainAgentOptions, BedrockTranslatorAgentOptions, BedrockLLMAgentOptions # Create translator agents translator_to_english = BedrockTranslatorAgent(BedrockTranslatorAgentOptions( name='TranslatorToEnglish', description='Translates input to English', target_language='English' )) # Create a processing agent (e.g., a BedrockLLMAgent) processor = BedrockLLMAgent(BedrockLLMAgentOptions( name='EnglishProcessor', description='Processes text in English' )) # Create a ChainAgent chain_agent = ChainAgent(ChainAgentOptions( name='TranslateProcessTranslate', description='Translates, processes, and translates back', agents=[translator_to_english, processor] )) orchestrator = AgentSquad() orchestrator.add_agent(chain_agent) # Function to handle user input async def handle_multilingual_input(input_text: str, source_language: str): translator_to_english.set_source_language(source_language) response = await orchestrator.route_request( input_text, 'user123', 'session456' ) print('Response:', response) # Usage import asyncio asyncio.run(handle_multilingual_input("Hola, ¿cómo estás?", "Spanish")) ``` In this example: 1. The first translator agent converts the input to English. 2. The processor agent (e.g., a `BedrockLLMAgent`) processes the English text. This setup allows for seamless multilingual processing, where the core logic can be implemented in English while supporting input and output in various languages. --- By leveraging the `BedrockTranslatorAgent`, you can create sophisticated multilingual applications and workflows, enabling seamless communication and processing across language barriers in your Agent Squad system. ================================================ FILE: docs/src/content/docs/agents/built-in/chain-agent.mdx ================================================ --- title: Chain Agent description: Documentation for the Chain Agent in the Agent Squad System --- The `ChainAgent` is an agent class in the Agent Squad System that allows for the sequential execution of multiple agents. It processes a request by passing the output of one agent as input to the next, creating a chain of agent interactions. ## Creating a ChainAgent ### Basic Example ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` Here's how to create a ChainAgent with only the required parameters: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { ChainAgent, ChainAgentOptions } from 'agent-squad'; import { BedrockLLMAgent } from 'agent-squad'; const agent1 = new BedrockLLMAgent({ name: 'Agent 1', description: '..AGENT DESCRIPTION..' }); const agent2 = new BedrockLLMAgent({ name: 'Agent 2', description: '..AGENT DESCRIPTION..' }); const chainAgent = new ChainAgent({ name: 'Chain Tech Agent', description: 'Specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.', agents: [agent1, agent2] }); ``` ```python from agent_squad.agents import ChainAgent, ChainAgentOptions from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions agent1 = BedrockLLMAgent(BedrockLLMAgentOptions( name='Agent 1', description='..AGENT DESCRIPTION..' )) agent2 = BedrockLLMAgent(BedrockLLMAgentOptions( name='Agent 2', description='..AGENT DESCRIPTION..' )) chain_agent = ChainAgent(ChainAgentOptions( name='BasicChainAgent', description='A simple chain of multiple agents', agents=[agent1, agent2] )) ``` ### Intermediate Example This example shows how to create a ChainAgent with a custom default output: ```typescript import { ChainAgent, ChainAgentOptions } from 'agent-squad'; import { BedrockLLMAgent } from 'agent-squad'; const agent1 = new BedrockLLMAgent({ name: 'Agent 1', description: '..AGENT DESCRIPTION..' }); const agent2 = new BedrockLLMAgent({ name: 'Agent 2', description: '..AGENT DESCRIPTION..', streaming: true }); const chainAgent = new ChainAgent({ name: 'IntermediateChainAgent', description: 'A chain of agents with custom default output', agents: [agent1, agent2], defaultOutput: 'The chain encountered an issue during processing.' }); ``` ```python from agent_squad.agents import ChainAgent, ChainAgentOptions from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions agent1 = BedrockLLMAgent(BedrockLLMAgentOptions( name='Agent 1', description='..AGENT DESCRIPTION..' )) agent2 = BedrockLLMAgent(BedrockLLMAgentOptions( name='Agent 2', description='..AGENT DESCRIPTION..' )) chain_agent = ChainAgent(ChainAgentOptions( name='IntermediateChainAgent', description='A chain of agents with custom default output', agents=[agent1, agent2], default_output='The chain encountered an issue during processing.' )) ``` ### Advanced Example For more complex use cases, you can create a ChainAgent with all available options: ```typescript import { ChainAgent, ChainAgentOptions } from 'agent-squad'; import { BedrockLLMAgent } from 'agent-squad'; const agent1 = new BedrockLLMAgent({ name: 'Agent 1', description: '..AGENT DESCRIPTION..' }); const agent2 = new BedrockLLMAgent({ name: 'Agent 2', description: '..AGENT DESCRIPTION..', streaming: true }); const options: ChainAgentOptions = { name: 'AdvancedChainAgent', description: 'A sophisticated chain of agents with all options', agents: [agent1, agent2], defaultOutput: 'The chain processing encountered an issue.', saveChat: true }; const chainAgent = new ChainAgent(options); ``` ```python from agent_squad.agents import ChainAgent, ChainAgentOptions from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions agent1 = BedrockLLMAgent(BedrockLLMAgentOptions( name='Agent 1', description='..AGENT DESCRIPTION..' )) agent2 = BedrockLLMAgent(BedrockLLMAgentOptions( name='Agent 2', description='..AGENT DESCRIPTION..', streaming=True )) options = ChainAgentOptions( name='AdvancedChainAgent', description='A sophisticated chain of agents with all options', agents=[agent1, agent2], default_output='The chain processing encountered an issue.', save_chat=True ) chain_agent = ChainAgent(options) ``` ## Integrating ChainAgent into the Agent Squad To integrate the ChainAgent into your Agent Squad: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); orchestrator.addAgent(chainAgent); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() orchestrator.add_agent(chain_agent) ``` ## Streaming Responses The ChainAgent supports streaming responses only for the last agent in the chain. This design ensures efficient processing through the chain while still enabling streaming capabilities for the end result. --- By leveraging the ChainAgent, you can create sophisticated, multi-step processing pipelines within your Agent Squad system, allowing for complex interactions and transformations of user inputs, with the added flexibility of streaming output from the final processing step. ================================================ FILE: docs/src/content/docs/agents/built-in/comprehend-filter-agent.mdx ================================================ --- title: Comprehend Filter Agent description: Documentation for the Comprehend Filter Agent in the Agent Squad System --- The `ComprehendFilterAgent` is an agent class in the Agent Squad System that uses [Amazon Comprehend](https://aws.amazon.com/comprehend/?nc1=h_ls) to analyze and filter content based on sentiment, Personally Identifiable Information (PII), and toxicity. It can be used as a standalone agent within the Agent Squad or as part of a chain in the ChainAgent. When used in a [ChainAgent](/agent-squad/agents/built-in/chain-agent) configuration, it's particularly effective as the first agent in the list. In this setup, it can check the user input against all configured filters, and if the content passes these checks, it will forward the original user input to the next agent in the chain. This allows for a robust content moderation system that can be seamlessly integrated into more complex processing pipelines, ensuring that only appropriate content is processed by subsequent agents. ## Key Features - Content analysis using Amazon Comprehend - Configurable checks for sentiment, PII, and toxicity - Customizable thresholds for sentiment and toxicity - Support for multiple languages - Ability to add custom content checks ## Creating a Comprehend Filter Agent ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` ### Basic Example To create a new `ComprehendFilterAgent` with default settings: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { ComprehendFilterAgent, ComprehendFilterAgentOptions } from 'agent-squad'; const agent = new ComprehendFilterAgent({ name: 'ContentModerator', description: 'Analyzes and filters content using Amazon Comprehend' }); ``` ```python from agent_squad.agents import ComprehendFilterAgent, ComprehendFilterAgentOptions agent = ComprehendFilterAgent(ComprehendFilterAgentOptions( name='ContentModerator', description='Analyzes and filters content using Amazon Comprehend' )) ``` ### Advanced Example For more complex use cases, you can create a `ComprehendFilterAgent` with custom settings: ```typescript import { ComprehendFilterAgent, ComprehendFilterAgentOptions } from 'agent-squad'; const options: ComprehendFilterAgentOptions = { name: 'AdvancedContentModerator', description: 'Advanced content moderation with custom settings', region: 'us-west-2', enableSentimentCheck: true, enablePiiCheck: true, enableToxicityCheck: true, sentimentThreshold: 0.8, toxicityThreshold: 0.6, allowPii: false, languageCode: 'en' }; const agent = new ComprehendFilterAgent(options); ``` ```python from agent_squad.agents import ComprehendFilterAgent, ComprehendFilterAgentOptions options = ComprehendFilterAgentOptions( name='AdvancedContentModerator', description='Advanced content moderation with custom settings', region='us-west-2', enable_sentiment_check=True, enable_pii_check=True, enable_toxicity_check=True, sentiment_threshold=0.8, toxicity_threshold=0.6, allow_pii=False, language_code='en' ) agent = ComprehendFilterAgent(options) ``` ## Integrating Comprehend Filter Agent To integrate the `ComprehendFilterAgent` into your orchestrator: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); orchestrator.addAgent(agent); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() orchestrator.add_agent(agent) ``` ## Adding Custom Checks This example demonstrates how to add a **Custom Check** to the `ComprehendFilterAgent`: ```typescript import { ComprehendFilterAgent, ComprehendFilterAgentOptions } from 'agent-squad'; const filterAgent = new ComprehendFilterAgent({ name: 'AdvancedContentFilter', description: 'Advanced content filter with custom checks' }); // Add a custom check for specific keywords filterAgent.addCustomCheck(async (text: string) => { const keywords = ['banned', 'inappropriate', 'offensive']; for (const keyword of keywords) { if (text.toLowerCase().includes(keyword)) { return `Banned keyword detected: ${keyword}`; } } return null; }); const orchestrator = new AgentSquad(); orchestrator.addAgent(filterAgent); const response = await orchestrator.routeRequest( "This message contains a banned word.", "user789", "session101" ); if (response) { console.log("Content passed all checks"); } else { console.log("Content was flagged by the filter"); } ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.agents import ComprehendFilterAgent, ComprehendFilterAgentOptions filter_agent = ComprehendFilterAgent(ComprehendFilterAgentOptions( name='AdvancedContentFilter', description='Advanced content filter with custom checks' )) # Add a custom check for specific keywords async def custom_keyword_check(text: str) -> Optional[str]: keywords = ['banned', 'inappropriate', 'offensive'] for keyword in keywords: if keyword in text.lower(): return f"Banned keyword detected: {keyword}" return None filter_agent.add_custom_check(custom_keyword_check) orchestrator = AgentSquad() orchestrator.add_agent(filter_agent) response = await orchestrator.route_request( "This message contains a banned word.", "user789", "session101" ) if response: print("Content passed all checks") else: print("Content was flagged by the filter") ``` ## Dynamic Language Detection and Handling The `ComprehendFilterAgent` offers flexible language handling capabilities. You can specify the language either at initialization or dynamically during invocation. Additionally, it supports automatic language detection, allowing it to adapt to content in various languages without manual specification. This example demonstrates dynamic language detection and handling: ```typescript import { AgentSquad, ComprehendFilterAgent } from 'agent-squad'; import { ComprehendClient, DetectDominantLanguageCommand } from "@aws-sdk/client-comprehend"; const filterAgent = new ComprehendFilterAgent({ name: 'MultilingualContentFilter', description: 'Filters content in multiple languages' }); const orchestrator = new AgentSquad(); orchestrator.addAgent(filterAgent); async function detectLanguage(text: string): Promise { const comprehendClient = new ComprehendClient({ region: "us-east-1" }); const command = new DetectDominantLanguageCommand({ Text: text }); const response = await comprehendClient.send(command); return response.Languages[0].LanguageCode; } let detectedLanguage: string | null = null; async function processUserInput(userInput: string, userId: string, sessionId: string): Promise { if (!detectedLanguage) { detectedLanguage = await detectLanguage(userInput); console.log(`Detected language: ${detectedLanguage}`); } try { const response = await orchestrator.routeRequest( userInput, userId, sessionId, { languageCode: detectedLanguage } ); console.log("Processed response:", response); } catch (error) { console.error("Error:", error); } } // Example usage processUserInput("Hello, world!", "user123", "session456"); // Subsequent calls will use the same detected language processUserInput("How are you?", "user123", "session456"); ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.agents import ComprehendFilterAgent, ComprehendFilterAgentOptions import boto3 import asyncio filter_agent = ComprehendFilterAgent(ComprehendFilterAgentOptions( name='MultilingualContentFilter', description='Filters content in multiple languages' )) orchestrator = AgentSquad() orchestrator.add_agent(filter_agent) def detect_language(text: str) -> str: comprehend = boto3.client('comprehend', region_name='us-east-1') response = comprehend.detect_dominant_language(Text=text) return response['Languages'][0]['LanguageCode'] detected_language = None async def process_user_input(user_input: str, user_id: str, session_id: str): global detected_language if not detected_language: detected_language = detect_language(user_input) print(f"Detected language: {detected_language}") try: response = await orchestrator.route_request( user_input, user_id, session_id, additional_params={"language_code": detected_language} ) print("Processed response:", response) except Exception as error: print("Error:", error) # Example usage asyncio.run(process_user_input("Hello, world!", "user123", "session456")) # Subsequent calls will use the same detected language asyncio.run(process_user_input("How are you?", "user123", "session456")) ``` ## Usage with ChainAgent This example demonstrates how to use the `ComprehendFilterAgent` as part of a `ChainAgent` configuration: ```typescript import { AgentSquad, ChainAgent, ComprehendFilterAgent, BedrockLLMAgent } from 'agent-squad'; // Create a ComprehendFilterAgent const filterAgent = new ComprehendFilterAgent({ name: 'ContentFilter', description: 'Filters inappropriate content', enableSentimentCheck: true, enablePiiCheck: true, enableToxicityCheck: true, sentimentThreshold: 0.7, toxicityThreshold: 0.6 }); // Create a BedrockLLMAgent (or any other agent you want to use after filtering) const llmAgent = new BedrockLLMAgent({ name: 'LLMProcessor', description: 'Processes filtered content using a language model', streaming: true }); // Create a ChainAgent that combines the filter and LLM agents const chainAgent = new ChainAgent({ name: 'FilteredLLMChain', description: 'Chain that filters content before processing with LLM', agents: [filterAgent, llmAgent] }); // Add the chain agent to the orchestrator const orchestrator = new AgentSquad(); orchestrator.addAgent(chainAgent); // Use the chain const response = await orchestrator.routeRequest( "Process this message after ensuring it's appropriate.", "user123", "session456" ); if (response) { console.log("Message processed successfully:", response); } else { console.log("Message was filtered out due to inappropriate content"); } ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.agents import ChainAgent, ComprehendFilterAgent, BedrockLLMAgent from agent_squad.agents import ChainAgentOptions, ComprehendFilterAgentOptions, BedrockLLMAgentOptions # Create a ComprehendFilterAgent filter_agent = ComprehendFilterAgent(ComprehendFilterAgentOptions( name='ContentFilter', description='Filters inappropriate content', enable_sentiment_check=True, enable_pii_check=True, enable_toxicity_check=True, sentiment_threshold=0.7, toxicity_threshold=0.6 )) # Create a BedrockLLMAgent (or any other agent you want to use after filtering) llm_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='LLMProcessor', description='Processes filtered content using a language model', streaming=True )) # Create a ChainAgent that combines the filter and LLM agents chain_agent = ChainAgent(ChainAgentOptions( name='FilteredLLMChain', description='Chain that filters content before processing with LLM', agents=[filter_agent, llm_agent] )) # Add the chain agent to the orchestrator orchestrator = AgentSquad() orchestrator.add_agent(chain_agent) # Use the chain response = await orchestrator.route_request( "Process this message after ensuring it's appropriate.", "user123", "session456" ) if response: print("Message processed successfully:", response) else: print("Message was filtered out due to inappropriate content") ``` ## Configuration Options The `ComprehendFilterAgent` supports the following configuration options: - `enableSentimentCheck`: Enable sentiment analysis (default: true) - `enablePiiCheck`: Enable PII detection (default: true) - `enableToxicityCheck`: Enable toxicity detection (default: true) - `sentimentThreshold`: Threshold for negative sentiment (default: 0.7) - `toxicityThreshold`: Threshold for toxic content (default: 0.7) - `allowPii`: Allow PII in content (default: false) - `languageCode`: ISO 639-1 language code for analysis (default: 'en') ## Supported Languages The `ComprehendFilterAgent` supports the following languages: 'en' (English), 'es' (Spanish), 'fr' (French), 'de' (German), 'it' (Italian), 'pt' (Portuguese), 'ar' (Arabic), 'hi' (Hindi), 'ja' (Japanese), 'ko' (Korean), 'zh' (Chinese Simplified), 'zh-TW' (Chinese Traditional) --- By leveraging the `ComprehendFilterAgent`, you can implement robust content moderation in your Agent Squad system, ensuring safe and appropriate interactions while leveraging the power of Amazon Comprehend for advanced content analysis. ================================================ FILE: docs/src/content/docs/agents/built-in/lambda-agent.mdx ================================================ --- title: LambdaAgent description: Documentation for the LambdaAgent in the Agent Squad System --- The `LambdaAgent` is a versatile agent class in the Agent Squad System that allows integration with existing AWS Lambda functions. This agent will invoke your existing Lambda function written in any language (e.g., Python, Node.js, Java), providing a seamless way to utilize your existing serverless logic within the orchestrator. ## Key Features - Integration with any AWS Lambda function runtime - Custom payload encoder/decoder methods to match your payload format - Support for cross-region Lambda invocation - Default payload encoding/decoding for quick setup ## Creating a LambdaAgent ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { LambdaAgent } from 'agent-squad'; const myCustomInputPayloadEncoder = (input, chatHistory, userId, sessionId, additionalParams) => { return JSON.stringify({ userQuestion: input, myCustomField: "Hello world!", history: chatHistory, user: userId, session: sessionId, ...additionalParams }); }; const myCustomOutputPayloadDecoder = (input) => { const decodedResponse = JSON.parse(new TextDecoder("utf-8").decode(input.Payload)).body; return { role: "assistant", content: [{ text: `Response: ${decodedResponse}` }] }; }; const options: LambdaAgentOptions = { name: 'My Advanced Lambda Agent', description: 'A versatile agent that calls a custom Lambda function', functionName: 'my-advanced-lambda-function', functionRegion: 'us-west-2', inputPayloadEncoder: myCustomInputPayloadEncoder, outputPayloadDecoder: myCustomOutputPayloadDecoder }; const agent = new LambdaAgent(options); ``` ```python import json from typing import List, Dict, Optional from agent_squad.agents import LambdaAgent, LambdaAgentOptions from agent_squad.types import ConversationMessage, ParticipantRole def my_custom_input_payload_encoder(input_text: str, chat_history: List[ConversationMessage], user_id: str, session_id: str, additional_params: Optional[Dict[str, str]] = None) -> str: return json.dumps({ "userQuestion": input_text, "myCustomField": "Hello world!", "history": [message.__dict__ for message in chat_history], "user": user_id, "session": session_id, **(additional_params or {}) }) def my_custom_output_payload_decoder(response: Dict[str, Any]) -> ConversationMessage: decoded_response = json.loads(response['Payload'].read().decode('utf-8'))['body'] return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": f"Response: {decoded_response}"}] ) options = LambdaAgentOptions( name='My Advanced Lambda Agent', description='A versatile agent that calls a custom Lambda function', function_name='my-advanced-lambda-function', function_region='us-west-2', input_payload_encoder=my_custom_input_payload_encoder, output_payload_decoder=my_custom_output_payload_decoder ) agent = LambdaAgent(options) ``` ### Parameter Explanations - `name`: (Required) Identifies the agent within your system. - `description`: (Required) Describes the agent's purpose or capabilities. - `function_name`: (Required) The name or ARN of the Lambda function to invoke. - `function_region`: (Required) The AWS region where the Lambda function is deployed. - `input_payload_encoder`: (Optional) A custom function to encode the input payload. - `output_payload_decoder`: (Optional) A custom function to decode the Lambda function's response. ## Adding the Agent to the Orchestrator To integrate the LambdaAgent into your Agent Squad System, follow these steps: 1. First, ensure you have created an instance of the orchestrator: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() ``` 2. Then, add the LambdaAgent to the orchestrator: ```typescript orchestrator.addAgent(agent); ``` ```python orchestrator.add_agent(agent) ``` 3. Now you can use the orchestrator to route requests to the appropriate agent, including your Lambda function: ```typescript const response = await orchestrator.routeRequest( "I need help with my order", "user123", "session456" ); ``` ```python response = await orchestrator.route_request( "I need help with my order", "user123", "session456" ) ``` If you don't provide custom encoder/decoder functions, the LambdaAgent uses default methods: Default Input Payload ```json { "query": "inputText", "chatHistory": [...], "additionalParams": {...}, "userId": "userId", "sessionId": "sessionId" } ``` Expected Default Output Payload ```json { "body": "{\"response\":\"this is the response\"}" } ``` --- By leveraging the `LambdaAgent`, you can easily incorporate ***existing AWS Lambda functions*** into your Agent Squad System, combining serverless compute with your custom orchestration logic. ================================================ FILE: docs/src/content/docs/agents/built-in/lex-bot-agent.mdx ================================================ --- title: LexBotAgent description: Documentation for the LexBotAgent in the Agent Squad System --- The `LexBotAgent` is a specialized agent class in the Agent Squad System that integrates [Amazon Lex bots](https://aws.amazon.com/lex/). ## Key Features - Seamless integration with Amazon Lex V2 bots - Support for multiple locales - Easy configuration with bot ID and alias ## Creating a LexBotAgent ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` To create a new `LexBotAgent` with the required parameters, use the following code: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { LexBotAgent } from 'agent-squad'; const agent = new LexBotAgent({ name: 'My Basic Lex Bot Agent', description: 'An agent specialized in flight booking', botId: 'your-bot-id', botAliasId: 'your-bot-alias-id', localeId: 'en_US', region: 'us-east-1' }); ``` ```python from agent_squad.agents import LexBotAgent, LexBotAgentOptions agent = LexBotAgent(LexBotAgentOptions( name='My Basic Lex Bot Agent', description='An agent specialized in flight booking', bot_id='your-bot-id', bot_alias_id='your-bot-alias-id', locale_id='en_US', region='us-east-1' )) ``` ### Parameter Explanations - `name`: (Required) Identifies the agent within your system. - `description`: (Required) Describes the agent's purpose or capabilities. - `bot_id`: (Required) The ID of the Amazon Lex bot you want to use. - `bot_alias_id`: (Required) The alias ID of the Amazon Lex bot. - `locale_id`: (Required) The locale ID for the bot (e.g., 'en_US'). - `region`: (Optional) The AWS region where the Lex bot is deployed. If not provided, it will use the `AWS_REGION` environment variable or default to 'us-east-1'. ## Adding the Agent to the Orchestrator To integrate the LexBotAgent into your Agent Squad, follow these steps: 1. First, ensure you have created an instance of the orchestrator: ```typescript import { AgentSquad } from 'agent-squad'; const orchestrator = new AgentSquad(); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() ``` 2. Then, add the LexBotAgent to the orchestrator: ```typescript orchestrator.addAgent(agent); ``` ```python orchestrator.add_agent(agent) ``` 3. Now you can use the orchestrator to route requests to the appropriate agent, including your Lex bot: ```typescript const response = await orchestrator.routeRequest( "I would like to book a flight", "user123", "session456" ); ``` ```python response = await orchestrator.route_request( "I would like to book a flight", "user123", "session456" ) ``` --- By leveraging the `LexBotAgent`, you can easily integrate **pre-built Amazon Lex Bots** into your Agent Squad. ================================================ FILE: docs/src/content/docs/agents/built-in/openai-agent.mdx ================================================ --- title: Open AI Agent description: Documentation for the OpenAI Agent --- The `OpenAIAgent` is a powerful agent class in the Agent Squad framework that integrates with OpenAI's Chat Completion API. This agent allows you to leverage OpenAI's language models for various natural language processing tasks. ## Key Features - Integration with OpenAI's Chat Completion API - Support for multiple OpenAI models (e.g., GPT-4, GPT-3.5) - Streaming and non-streaming response options - Customizable inference configuration - Conversation history handling for context-aware responses - Customizable system prompts with variable support - Support for retrievers to enhance responses with additional context - Flexible initialization with API key or custom client ## Configuration Options The `OpenAIAgentOptions` extends the base `AgentOptions` with the following fields: ### Required Fields - `name`: Name of the agent - `description`: Description of the agent's capabilities - Authentication (one of the following is required): - `apiKey`: Your OpenAI API key - `client`: Custom OpenAI client instance ### Optional Fields - `model`: OpenAI model identifier (e.g., 'gpt-4', 'gpt-3.5-turbo'). Defaults to `OPENAI_MODEL_ID_GPT_O_MINI` - `streaming`: Enable streaming responses. Defaults to `false` - `retriever`: Custom retriever instance for enhancing responses with additional context - `inferenceConfig`: Configuration for model inference: - `maxTokens`: Maximum tokens to generate (default: 1000) - `temperature`: Controls randomness (0-1) - `topP`: Controls diversity via nucleus sampling - `stopSequences`: Sequences that stop generation - `customSystemPrompt`: System prompt configuration: - `template`: Template string with optional variable placeholders - `variables`: Key-value pairs for template variables ## Creating an OpenAIAgent ### Python Package If you haven't already installed the OpenAI-related dependencies, make sure to install them: ```bash pip install "agent-squad[openai]" ``` Here are various examples showing different ways to create and configure an OpenAIAgent: ### Basic Examples **1. Minimal Configuration** import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'A versatile AI assistant', apiKey: 'your-openai-api-key' }); ``` ```python agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='A versatile AI assistant', api_key='your-openai-api-key' )) ```
**2. Using Custom Client** ```typescript import OpenAI from 'openai'; const customClient = new OpenAI({ apiKey: 'your-openai-api-key' }); const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'A versatile AI assistant', client: customClient }); ``` ```python from openai import OpenAI custom_client = OpenAI(api_key='your-openai-api-key') agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='A versatile AI assistant', client=custom_client )) ```
**3. Custom Model and Streaming** ```typescript const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'A streaming-enabled assistant', apiKey: 'your-openai-api-key', model: 'gpt-4', streaming: true }); ``` ```python agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='A streaming-enabled assistant', api_key='your-openai-api-key', model='gpt-4', streaming=True )) ```
**4. With Inference Configuration** ```typescript const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'An assistant with custom inference settings', apiKey: 'your-openai-api-key', inferenceConfig: { maxTokens: 500, temperature: 0.7, topP: 0.9, stopSequences: ['Human:', 'AI:'] } }); ``` ```python agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='An assistant with custom inference settings', api_key='your-openai-api-key', inference_config={ 'maxTokens': 500, 'temperature': 0.7, 'topP': 0.9, 'stopSequences': ['Human:', 'AI:'] } )) ```
**5. With Simple System Prompt** ```typescript const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'An assistant with custom prompt', apiKey: 'your-openai-api-key', customSystemPrompt: { template: 'You are a helpful AI assistant focused on technical support.' } }); ``` ```python agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='An assistant with custom prompt', api_key='your-openai-api-key', custom_system_prompt={ 'template': 'You are a helpful AI assistant focused on technical support.' } )) ```
**6. With System Prompt Variables** ```typescript const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'An assistant with variable prompt', apiKey: 'your-openai-api-key', customSystemPrompt: { template: 'You are an AI assistant specialized in {{DOMAIN}}. Always use a {{TONE}} tone.', variables: { DOMAIN: 'customer support', TONE: 'friendly and helpful' } } }); ``` ```python agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='An assistant with variable prompt', api_key='your-openai-api-key', custom_system_prompt={ 'template': 'You are an AI assistant specialized in {{DOMAIN}}. Always use a {{TONE}} tone.', 'variables': { 'DOMAIN': 'customer support', 'TONE': 'friendly and helpful' } } )) ```
**7. With Custom Retriever** ```typescript const retriever = new CustomRetriever({ // Retriever configuration }); const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'An assistant with retriever', apiKey: 'your-openai-api-key', retriever: retriever }); ``` ```python retriever = CustomRetriever( # Retriever configuration ) agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='An assistant with retriever', api_key='your-openai-api-key', retriever=retriever )) ```
**8. Combining Multiple Options** ```typescript const agent = new OpenAIAgent({ name: 'OpenAI Assistant', description: 'An assistant with multiple options', apiKey: 'your-openai-api-key', model: 'gpt-4', streaming: true, inferenceConfig: { maxTokens: 500, temperature: 0.7 }, customSystemPrompt: { template: 'You are an AI assistant specialized in {{DOMAIN}}.', variables: { DOMAIN: 'technical support' } } }); ``` ```python agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Assistant', description='An assistant with multiple options', api_key='your-openai-api-key', model='gpt-4', streaming=True, inference_config={ 'maxTokens': 500, 'temperature': 0.7 }, custom_system_prompt={ 'template': 'You are an AI assistant specialized in {{DOMAIN}}.', 'variables': { 'DOMAIN': 'technical support' } } )) ```
**9. Complete Example with All Options** Here's a comprehensive example showing all available configuration options: ```typescript import { OpenAIAgent } from 'agent-squad'; const agent = new OpenAIAgent({ // Required fields name: 'Advanced OpenAI Assistant', description: 'A fully configured AI assistant powered by OpenAI models', apiKey: 'your-openai-api-key', // Optional fields model: 'gpt-4', // Choose OpenAI model streaming: true, // Enable streaming responses retriever: customRetriever, // Custom retriever for additional context // Inference configuration inferenceConfig: { maxTokens: 500, // Maximum tokens to generate temperature: 0.7, // Control randomness (0-1) topP: 0.9, // Control diversity via nucleus sampling stopSequences: ['Human:', 'AI:'] // Sequences that stop generation }, // Custom system prompt with variables customSystemPrompt: { template: `You are an AI assistant specialized in {{DOMAIN}}. Your core competencies: {{SKILLS}} Communication style: - Maintain a {{TONE}} tone - Focus on {{FOCUS}} - Prioritize {{PRIORITY}}`, variables: { DOMAIN: 'scientific research', SKILLS: [ '- Advanced data analysis', '- Statistical methodology', '- Research design', '- Technical writing' ], TONE: 'professional and academic', FOCUS: 'accuracy and clarity', PRIORITY: 'evidence-based insights' } } }); ``` ```python from agent_squad import OpenAIAgent, OpenAIAgentOptions agent = OpenAIAgent(OpenAIAgentOptions( # Required fields name='Advanced OpenAI Assistant', description='A fully configured AI assistant powered by OpenAI models', api_key='your-openai-api-key', # Optional fields model='gpt-4', # Choose OpenAI model streaming=True, # Enable streaming responses retriever=custom_retriever, # Custom retriever for additional context # Inference configuration inference_config={ 'maxTokens': 500, # Maximum tokens to generate 'temperature': 0.7, # Control randomness (0-1) 'topP': 0.9, # Control diversity via nucleus sampling 'stopSequences': ['Human:', 'AI:'] # Sequences that stop generation }, # Custom system prompt with variables custom_system_prompt={ 'template': """You are an AI assistant specialized in {{DOMAIN}}. Your core competencies: {{SKILLS}} Communication style: - Maintain a {{TONE}} tone - Focus on {{FOCUS}} - Prioritize {{PRIORITY}}""", 'variables': { 'DOMAIN': 'scientific research', 'SKILLS': [ '- Advanced data analysis', '- Statistical methodology', '- Research design', '- Technical writing' ], 'TONE': 'professional and academic', 'FOCUS': 'accuracy and clarity', 'PRIORITY': 'evidence-based insights' } } )) ``` ## Using the OpenAIAgent There are two ways to use the OpenAIAgent: directly or through the Agent Squad. ### Direct Usage Call the agent directly when you want to use a single agent without orchestrator routing: ```typescript const classifierResult = { selectedAgent: agent, confidence: 1.0 }; const response = await orchestrator.agentProcessRequest( "What is the capital of France?", "user123", "session456", classifierResult ); ``` ```python classifier_result = ClassifierResult(selected_agent=agent, confidence=1.0) response = await orchestrator.agent_process_request( "What is the capital of France?", "user123", "session456", classifier_result ) ``` ### Using with the Orchestrator Add the agent to Agent Squad for use in a multi-agent system: ```typescript const orchestrator = new AgentSquad(); orchestrator.addAgent(agent); const response = await orchestrator.routeRequest( "What is the capital of France?", "user123", "session456" ); ``` ```python orchestrator = AgentSquad() orchestrator.add_agent(agent) response = await orchestrator.route_request( "What is the capital of France?", "user123", "session456" ) ``` ================================================ FILE: docs/src/content/docs/agents/built-in/supervisor-agent.mdx ================================================ --- title: Supervisor Agent description: Documentation for the SupervisorAgent in the Agent Squad System --- import { Tabs, TabItem } from '@astrojs/starlight/components'; The `SupervisorAgent` is an advanced orchestration component that enables sophisticated multi-agent coordination within the Agent Squad framework. It implements a unique **"agent-as-tools"** architecture where team members are exposed to a supervisor agent as invocable tools, enabling parallel processing and contextual communication. The diagram below illustrates the **SupervisorAgent** architecture, featuring a Lead Agent that coordinates with a team of specialized agents (A, B, and C). Two memory components—User-Supervisor Memory and Supervisor-Team Memory—support the interactions, enabling efficient information flow and conversation history management throughout the system. ![Supervisor flow](/agent-squad/flow-supervisor.jpg) ## Usage Patterns The SupervisorAgent can be used in two primary ways: ### 1. Direct Usage You can use the SupervisorAgent directly, bypassing the classifier, when you want dedicated team coordination for specific tasks: ```typescript // Create and configure SupervisorAgent const supervisorAgent = new SupervisorAgent({ name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for travel purposes", leadAgent: new BedrockLLMAgent({ name: "Support Team Lead", description: "Coordinates support inquiries" }), team: [ new LexBotAgent({ name: "Booking Agent", description: "Handles travel bookings", botId: "travel-bot-id", botAliasId: "alias-id", localeId: "en_US" }), new AmazonBedrockAgent({ name: "Payment Support", description: "Handles payment issues", agentId: "payment-agent-id", agentAliasId: "alias-id" }) ] }); // Use directly const response = await supervisorAgent.processRequest( "I need to modify my flight and check my refund status", "user123", "session456" ); ``` ```python # Create and configure SupervisorAgent supervisor_agent = SupervisorAgent(SupervisorAgentOptions( name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for travel purposes", lead_agent=BedrockLLMAgent(BedrockLLMAgentOptions( name="Support Team Lead", description="Coordinates support inquiries" )), team=[ LexBotAgent(LexBotAgentOptions( name="Booking Agent", description="Handles travel bookings", bot_id="travel-bot-id", bot_alias_id="alias-id", locale_id="en_US" )), BedrockAgent(BedrockAgentOptions( name="Payment Support", description="Handles payment issues", agent_id="payment-agent-id", agent_alias_id="alias-id" )) ] )) # Use directly response = await supervisor_agent.process_request( "I need to modify my flight and check my refund status", "user123", "session456" ) ``` Here's a diagram illustrating the code implementation above, showing how the BedrockLLMAgent (Lead Agent) processes the user's flight modification request by coordinating with LexBotAgent and Amazon BedrockAgent, supported by dual memory systems for maintaining conversation context. ![Supervisor flow direct](/agent-squad/flow-supervisor-direct.jpg) ### 2. As Part of Classifier-Based Architecture The SupervisorAgent can also be integrated into a larger system using the classifier, enabling complex hierarchical architectures: ```typescript const orchestrator = new AgentSquad(); // Add individual agents orchestrator.addAgent(new BedrockLLMAgent({ name: "General Assistant", description: "Handles general inquiries" })); // Add a SupervisorAgent for complex support tasks orchestrator.addAgent(new SupervisorAgent({ name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for product development purposes", leadAgent: new BedrockLLMAgent({ name: "Support Team", description: "Coordinates support inquiries requiring multiple specialists" }), team: [techAgent, billingAgent, lexBookingBot] })); // Add another SupervisorAgent for product development orchestrator.addAgent(new SupervisorAgent({ leadAgent: new AnthropicAgent({ name: "Product Team", description: "Coordinates product development and feature requests" }), team: [designAgent, engineeringAgent, productManagerAgent] })); // Process through classifier const response = await orchestrator.routeRequest( userInput, userId, sessionId ); ``` ```python orchestrator = AgentSquad() # Add individual agents orchestrator.add_agent(BedrockLLMAgent(BedrockLLMAgentOptions( name="General Assistant", description="Handles general inquiries" ))) # Add a SupervisorAgent for complex support tasks orchestrator.add_agent(SupervisorAgent(SupervisorAgentOptions( name: "SupervisorAgent", description: "You are a supervisor agent that manages the team of agents for product development purposes", lead_agent=BedrockLLMAgent(BedrockLLMAgentOptions( name="Support Team", description="Coordinates support inquiries requiring multiple specialists" )), team=[tech_agent, billing_agent, lex_booking_bot] ))) # Add another SupervisorAgent for product development orchestrator.add_agent(SupervisorAgent(SupervisorAgentOptions( lead_agent=AnthropicAgent(AnthropicAgentOptions( name="Product Team", description="Coordinates product development and feature requests" )), team=[design_agent, engineering_agent, product_manager_agent] ))) # Process through classifier response = await orchestrator.route_request( user_input, user_id, session_id ) ``` Here's a diagram illustrating the code implementation above, showing a Classifier that routes user requests to appropriate teams. Three specialized units are shown: a General Assistant, a Support Team (handling tech, billing, and booking), and a Product Team (comprising design, engineering, and product management agents). Each team uses different agent types (BedrockLLMAgent, LexBotAgent, AnthropicAgent, AmazonBedrockAgent) based on their specific functions. ![Supervisor flow orchestrator](/agent-squad/flow-supervisor-orchestrator.jpg)
This flexibility allows you to: - Use SupervisorAgent directly for dedicated team coordination - Integrate it into classifier-based systems for dynamic routing - Create hierarchical structures with multiple specialized teams - Mix different types of agents (LexBot, Bedrock, Anthropic, etc.) in teams - Scale and adapt the architecture as needs evolve ## Core Components ### 1. Supervisor (Lead Agent) - Must be either a [BedrockLLMAgent](/agent-squad/agents/built-in/bedrock-llm-agent) or [AnthropicAgent](/agent-squad/agents/built-in/anthropic-agent) - Acts as the central coordinator - Communicates with team members through a tool interface - Maintains conversation context with both user and team members ### 2. Team Members - Collection of agents - each agent is wrapped as a tool for the supervisor - Can be any agent type supported by the framework - Operate independently and in parallel when possible ## Memory Architecture The SupervisorAgent implements a sophisticated three-tier memory system to maintain context across conversations: ### 1. User-Supervisor Memory This is like the main conversation between a customer and the team leader: ```text User: I'm having trouble with my billing and the mobile app isn't working Assistant: I understand you're having two issues. Let me help you with both your billing and app problems. User: Yes, the app crashes when I try to view my bill Assistant: I'll look into both issues. Let me check with our technical and billing teams. ``` ### 2. Supervisor-Team Memory Each team member maintains a private conversation with the supervisor: ```text # Tech Support Conversation Supervisor: User is experiencing app crashes when viewing bills. Can you investigate? Tech Support: Based on the symptoms, this might be a cache issue. I'll provide steps to clear it. # Billing Team Conversation Supervisor: Please check the user's billing status Billing Team: Account is active, last payment received Jan 15, next due Feb 15 ``` ### 3. Combined Memory The supervisor keeps track of all important information in an organized way: ```text User: I'm having trouble with the mobile app, what should I do? Assistant: [Tech Support] The app crash is likely due to corrupted cache. Please ask the user to clear the app cache. User: What is the status of my bills? Assistant: [Billing Team] Account status is good. Last payment: Jan 15, Next due: Feb 15 ``` ### Memory Processing Flow Here's a complete interaction showing how memory and communication work together: 1. **Initial Request** ```text User: I'm having trouble with my bill and the mobile app ``` 2. **Parallel Processing** ```text # Supervisor communicates simultaneously with both teams Supervisor → Tech Support: What could cause app issues when viewing bills? Supervisor → Billing: Please verify account status ``` 3. **Team Responses** ```text Tech Support → Supervisor: Likely a cache issue. Common after recent updates. Billing → Supervisor: Account in good standing, no payment issues. ``` 4. **Unified Response** ```text Supervisor → User: I've checked both issues. Your billing account is in good standing. For the app problem, it appears to be a cache issue. Would you like me to guide you through clearing your app's cache? ``` ## Configuration ### Configuration Options ```typescript interface SupervisorAgentOptions extends AgentOptions { leadAgent: BedrockLLMAgent | AnthropicAgent; // The agent that leads the team coordination team: Agent[]; // Team of agents to coordinate storage?: ChatStorage; // Memory storage implementation trace?: boolean; // Enable detailed logging extraTools?: AgentTools | AgentTool[]; // Additional tools for supervisor } ``` ```python @dataclass class SupervisorAgentOptions(AgentOptions): lead_agent: Agent # The agent that leads the team coordination team: list[Agent] # Team of agents that can help in resolving tasks storage: Optional[ChatStorage] # Memory storage for the team trace: Optional[bool] # Enable tracing/logging extra_tools: Optional[Union[AgentTools, list[AgentTool]]] # Additional tools for supervisor ``` ### Required Parameters - `leadAgent`/`lead_agent`: Must be either a BedrockLLMAgent or AnthropicAgent instance - `team`: List of agents that will be coordinated by the supervisor ### Optional Parameters - `storage`: Custom storage implementation for conversation history (defaults to InMemoryChatStorage) - `trace`: Enable detailed logging of agent interactions - `extraTools`/`extra_tools`: Additional tools to be made available to the supervisor ### Built-in Tools #### send_messages Tool The SupervisorAgent includes a built-in tool for parallel message processing: ```json { "name": "send_messages", "description": "Send messages to multiple agents in parallel.", "properties": { "messages": { "type": "array", "items": { "type": "object", "properties": { "recipient": { "type": "string", "description": "Agent name to send message to." }, "content": { "type": "string", "description": "Message content." } }, "required": ["recipient", "content"] }, "description": "Array of messages for different agents.", "minItems": 1 } } } ``` ### Adding Custom Tools ```typescript const customTools = [ new AgentTool({ name: "analyze_sentiment", description: "Analyze message sentiment", properties: { text: { type: "string", description: "Text to analyze" } }, required: ["text"], func: analyzeSentiment }) ]; const supervisorAgent = new SupervisorAgent({ leadAgent: supervisor, team: [techAgent, billingAgent], extraTools: customTools }); ``` ```python custom_tools = [ AgentTool( name="analyze_sentiment", description="Analyze message sentiment", properties={ "text": { "type": "string", "description": "Text to analyze" } }, required=["text"], func=analyze_sentiment ) ] supervisor_agent = SupervisorAgent(SupervisorAgentOptions( lead_agent=supervisor, team=[tech_agent, billing_agent], extra_tools=custom_tools )) ``` ## Communication Guidelines 1. **Response Handling** - Aggregates responses from all relevant agents - Maintains original agent responses without summarization - Provides final answers only when all necessary responses are received 2. **Agent Interaction** - Optimizes for parallel processing when possible - Maintains agent isolation (agents are unaware of each other) - Keeps inter-agent communications concise 3. **Context Management** - Provides full context when necessary - Reuses previous responses when appropriate - Maintains efficient conversation history 4. **Input Processing** - Forwards simple inputs directly to relevant agents - Extracts all relevant data before creating action plans - Never assumes parameter values ## Best Practices 1. **Agent Team Composition** - Choose specialized agents with clear, distinct roles - Ensure agent descriptions are detailed and non-overlapping - Consider communication patterns when selecting team size 2. **Storage Configuration** - Use persistent storage (e.g., DynamoDBChatStorage) for production - Consider memory usage with large conversation histories - Implement appropriate cleanup strategies 3. **Tool Management** - Add custom tools through extraTools/extra_tools parameter - Keep tool functions focused and well-documented - Consider performance impact of tool complexity 4. **Performance Optimization** 4. **Performance Optimization** - Enable parallel processing where appropriate - Monitor and adjust team size based on requirements - Use tracing to identify bottlenecks - Configure memory storage based on expected conversation volumes ## Complete Example Here's a complete example showing how to use the SupervisorAgent in a typical scenario: ```typescript import { AgentSquad, BedrockLLMAgent, SupervisorAgent, DynamoDBChatStorage, AgentTool, AgentTools } from 'agent-squad'; // Function to analyze sentiment (implementation would go here) async function analyzeSentiment(text: string): Promise<{ sentiment: string; score: number }> { return { sentiment: "positive", score: 0.8 }; } async function main() { // Create orchestrator const orchestrator = new AgentSquad(); // Create supervisor (lead agent) const supervisor = new BedrockLLMAgent({ name: "Team Lead", description: "Coordinates specialized team members", modelId: "anthropic.claude-3-sonnet-20240229-v1:0" }); // Create team members const techAgent = new BedrockLLMAgent({ name: "Tech Support", description: "Handles technical issues", modelId: "anthropic.claude-3-sonnet-20240229-v1:0" }); const billingAgent = new BedrockLLMAgent({ name: "Billing Expert", description: "Handles billing and payment queries", modelId: "anthropic.claude-3-sonnet-20240229-v1:0" }); // Create custom tools const customTools = [ new AgentTool({ name: "analyze_sentiment", description: "Analyze message sentiment", properties: { text: { type: "string", description: "Text to analyze" } }, required: ["text"], func: analyzeSentiment }) ]; // Create SupervisorAgent const supervisorAgent = new SupervisorAgent({ leadAgent: supervisor, team: [techAgent, billingAgent], storage: new DynamoDBChatStorage("conversation-table", "us-east-1"), trace: true, extraTools: new AgentTools(customTools) }); // Add supervisor agent to orchestrator orchestrator.addAgent(supervisorAgent); try { // Process request const response = await orchestrator.routeRequest( "I'm having issues with my bill and the mobile app", "user123", "session456" ); // Handle the response (streaming or non-streaming) if (response.streaming) { console.log("\n** STREAMING RESPONSE **"); console.log(`Agent: ${response.metadata.agentName}`); // Handle streaming response for await (const chunk of response.output) { process.stdout.write(chunk); } } else { console.log("\n** RESPONSE **"); console.log(`Agent: ${response.metadata.agentName}`); console.log(`Response: ${response.output}`); } } catch (error) { console.error("Error processing request:", error); } } // Run the example main().catch(console.error); ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.agents import ( SupervisorAgent, BedrockLLMAgent, SupervisorAgentOptions, BedrockLLMAgentOptions ) from agent_squad.storage import DynamoDBChatStorage from agent_squad.utils import AgentTool, AgentTools # Create orchestrator orchestrator = AgentSquad() # Create supervisor and team supervisor = BedrockLLMAgent(BedrockLLMAgentOptions( name="Team Lead", description="Coordinates specialized team members" )) tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Support", description="Handles technical issues" )) billing_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Billing Expert", description="Handles billing and payment queries" )) # Create custom tools custom_tools = [ AgentTool( name="analyze_sentiment", description="Analyze message sentiment", properties={ "text": { "type": "string", "description": "Text to analyze" } }, required=["text"], func=analyze_sentiment ) ] # Create and add supervisor agent supervisor_agent = SupervisorAgent(SupervisorAgentOptions( lead_agent=supervisor, team=[tech_agent, billing_agent], storage=DynamoDBChatStorage(), trace=True, extra_tools=custom_tools )) orchestrator.add_agent(supervisor_agent) # Process request async def main(): response = await orchestrator.route_request( "I'm having issues with my bill and the mobile app", "user123", "session456" ) # Handle response based on whether it's streaming or not if response.streaming: print("\n** STREAMING RESPONSE **") print(f"Agent: {response.metadata.agent_name}") async for chunk in response.output: print(chunk, end='', flush=True) else: print("\n** RESPONSE **") print(f"Agent: {response.metadata.agent_name}") print(f"Response: {response.output}") # Run the example if __name__ == "__main__": import asyncio asyncio.run(main()) ``` ## Limitations - LeadAgent must be either BedrockLLMAgent or AnthropicAgent - May require significant memory for large conversation histories - Performance depends on slowest agent in parallel operations By leveraging the SupervisorAgent, you can create sophisticated multi-agent systems with coordinated responses, maintained context, and efficient parallel processing. The agent's flexible architecture allows for customization while providing robust built-in capabilities for common coordination tasks. ================================================ FILE: docs/src/content/docs/agents/custom-agents.mdx ================================================ --- title: Custom Agents description: A guide to creating custom agents in the Agent Squad System, including an OpenAI agent example --- The `Agent` abstract class provides a flexible foundation for creating various types of agents. When implementing a custom agent, you can: 1. **Call Language Models**: Integrate with LLMs like GPT-3, BERT, or custom models. 2. **API Integration**: Make calls to external APIs or services. 3. **Data Processing**: Implement data analysis, transformation, or generation logic. 4. **Rule-Based Systems**: Create agents with predefined rules and responses. 5. **Hybrid Approaches**: Combine multiple techniques for more complex behaviors. Example of a simple custom agent: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript class SimpleGreetingAgent extends Agent { async processRequest( inputText: string, userId: string, sessionId: string, chatHistory: Message[] ): Promise { return { role: "assistant", content: [{ text: `Hello! You said: ${inputText}` }] }; } } ``` ```python from agent_squad.agents import Agent from agent_squad.types import ConversationMessage, ParticipantRole class SimpleGreetingAgent(Agent): async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage] ) -> ConversationMessage: return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": f"Hello! You said: {input_text}"}] ) ``` ## Basic Structure of a Custom Agent To create a custom agent, you need to extend the base `Agent` class or one of its subclasses. Here's the basic structure: ```typescript import { Agent, AgentOptions, Message } from './path-to-agent-module'; class CustomAgent extends Agent { constructor(options: AgentOptions) { super(options); // Additional initialization if needed } async processRequest( inputText: string, userId: string, sessionId: string, chatHistory: Message[], additionalParams?: Record ): Promise { // Implement your custom logic here } } ``` ```python from typing import List, Optional, Dict from agent_squad.agents import Agent, AgentOptions from agent_squad.types import ConversationMessage class CustomAgent(Agent): def __init__(self, options: AgentOptions): super().__init__(options) # Additional initialization if needed async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage], additional_params: Optional[Dict[str, str]] = None ) -> ConversationMessage: # Implement your custom logic here pass ``` ## Example: OpenAI Agent Here's an example of a custom agent that uses the OpenAI API: ```typescript import { Agent, AgentOptions, Message } from './path-to-agent-module'; import { Configuration, OpenAIApi } from 'openai'; class OpenAIAgent extends Agent { private openai: OpenAIApi; constructor(options: AgentOptions & { apiKey: string }) { super(options); const configuration = new Configuration({ apiKey: options.apiKey }); this.openai = new OpenAIApi(configuration); } async processRequest( inputText: string, userId: string, sessionId: string, chatHistory: Message[] ): Promise { const response = await this.openai.createCompletion({ model: 'text-davinci-002', prompt: inputText, max_tokens: 150 }); return { role: 'assistant', content: [{ text: response.data.choices[0].text || 'No response' }] }; } } ``` ```python from typing import List, Optional, Dict import openai from agent_squad.agents import Agent, AgentOptions from agent_squad.types import ConversationMessage, ParticipantRole class OpenAIAgentOptions(AgentOptions): api_key: str class OpenAIAgent(Agent): def __init__(self, options: OpenAIAgentOptions): super().__init__(options) openai.api_key = options.api_key async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage], additional_params: Optional[Dict[str, str]] = None ) -> ConversationMessage: response = openai.Completion.create( engine="text-davinci-002", prompt=input_text, max_tokens=150 ) return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": response.choices[0].text.strip()}] ) ``` To use this OpenAI agent: ```typescript const openAIAgent = new OpenAIAgent({ name: 'OpenAI Agent', description: 'An agent that uses OpenAI API for responses', apiKey: 'your-openai-api-key' }); orchestrator.addAgent(openAIAgent); ``` ```python openai_agent = OpenAIAgent(OpenAIAgentOptions( name='OpenAI Agent', description='An agent that uses OpenAI API for responses', api_key='your-openai-api-key' )) orchestrator.add_agent(openai_agent) ``` --- By creating custom agents, you can extend the capabilities of the Agent Squad to meet your specific needs, whether that's integrating with external AI services like OpenAI, implementing specialized business logic, or interfacing with other systems and APIs. ================================================ FILE: docs/src/content/docs/agents/overview.mdx ================================================ --- title: Agents overview description: An overview of agents --- In the Agent Squad, an agent is a fundamental building block designed to process user requests and generate a response. The `Agent` abstract class serves as the foundation for all specific agent implementations, providing a common structure and interface. ## Agent selection process The Agent Squad uses a [Classifier](/agent-squad/classifiers/overview), typically an LLM, to select the most appropriate agent for each user request. At the heart of this process are the **agent descriptions**. These descriptions are critical and should be as detailed and comprehensive as possible. A well-crafted agent description: - Clearly outlines the agent's capabilities and expertise - Provides specific examples of tasks it can handle - Distinguishes it from other agents in the system The more detailed and precise these descriptions are, the more accurately the Classifier can route requests to the right agent. This is especially important in complex systems with multiple specialized agents. For a more detailed explanation of the agent selection process, please refer to the [How it works section](/agent-squad/general/how-it-works) section in our documentation. To optimize agent selection: - Invest time in crafting thorough, specific agent descriptions - Regularly review and refine these descriptions - Use the framework's [agent overlap analysis](/agent-squad/advanced-features/agent-overlap) to ensure clear differentiation between agents By prioritizing detailed agent descriptions and fine-tuning the selection process, you can significantly enhance the efficiency and accuracy of your Agent Squad implementation. ## The Agent Abstract Class The `Agent` class is an abstract base class that defines the essential properties and methods that all agents in the system must have. It's designed to be flexible, allowing for a wide range of implementations from simple API callers to complex LLM-powered conversational agents. ### Key Properties - `name`: A string representing the name of the agent. - `id`: A unique identifier for the agent, automatically generated from the name. - `description`: A string describing the agent's capabilities and expertise. - `save_chat`: A boolean indicating whether to save the chat history for this agent. - `callbacks`: An optional `AgentCallbacks` object for handling events like new tokens in streaming responses. ### Abstract Method: process_request The core functionality of any agent is encapsulated in the `process_request` method. This method must be implemented by all concrete agent classes: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript abstract processRequest( inputText: string, userId: string, sessionId: string, chatHistory: Message[], additionalParams?: Record ): Promise>; ``` ```python from abc import abstractmethod from typing import Union, AsyncIterable, Optional, Dict, List from agent_squad.types import ConversationMessage class Agent(ABC): @abstractmethod async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage], additional_params: Optional[Dict[str, str]] = None ) -> Union[ConversationMessage, AsyncIterable[any]]: pass ``` - `input_text`: The user's input or query. - `user_id`: A unique identifier for the user. - `session_id`: An identifier for the current conversation session. - `chat_history`: A list of previous messages in the conversation. - `additional_params`: Optional parameters for additional context or configuration. This is a powerful feature that allows for dynamic customization of agent behavior - It's an optional dictionary of key-value pairs that can be passed when calling `route_request` on the orchestrator. - These parameters are then forwarded to the appropriate agent's `process_request` method. - Custom agents can use these parameters to adjust their behavior or provide additional context for processing the request. The method returns either a `ConversationMessage` for single responses or an `AsyncIterable` for streaming responses. Example usage: ```typescript // When calling routeRequest const response = await orchestrator.routeRequest( userInput, userId, sessionId, { location: "New York", units: "metric" } ); // In a custom agent's processRequest method class WeatherAgent extends Agent { async processRequest( inputText: string, userId: string, sessionId: string, chatHistory: Message[], additionalParams?: Record ): Promise { const location = additionalParams?.location || "default location"; const units = additionalParams?.units || "metric"; // Use location and units to fetch weather data // ... } } ``` ```python # When calling route_request response = await orchestrator.route_request( user_input, user_id, session_id, additional_params={"location": "New York", "units": "metric"} ) # In a custom agent's process_request method class WeatherAgent(Agent): async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage], additional_params: Optional[Dict[str, str]] = None ) -> ConversationMessage: location = additional_params.get('location', 'default location') units = additional_params.get('units', 'metric') # Use location and units to fetch weather data # ... ``` ### Agent Options When creating a new agent, you can specify various options using the `AgentOptions` class: ```typescript interface AgentOptions { name: string; description: string; modelId?: string; region?: string; saveChat?: boolean; callbacks?: AgentCallbacks; } ``` ```python @dataclass class AgentOptions: name: str description: str model_id: Optional[str] = None region: Optional[str] = None save_chat: bool = True callbacks: Optional[AgentCallbacks] = None ``` ### Direct Agent Usage When you have a single agent use case, you can bypass the orchestrator and call the agent directly. This approach leverages the power of the Agent Squad framework while focusing on a single agent scenario: ```typescript // Initialize the agent const agent = new BedrockLLMAgent({ name: "custom-agent", description: "Handles specific tasks" }); // Call the agent directly const response = await agent.agentProcessRequest( userInput, userId, sessionId, chatHistory, { param1: "value1" } ); ``` ```python # Initialize the agent agent = BedrockLLMAgent( name="custom-agent", description="Handles specific tasks" ) # Call the agent directly response = await agent.agent_process_request( input_text=user_input, user_id=user_id, session_id=session_id, chat_history=chat_history, additional_params={"param1": "value1"} ) ``` This approach is useful for single agent scenarios where you don't need orchestration but want to leverage the powerful capabilities of the Agent Squad framework. These options allow you to customize various aspects of the agent's behavior and configuration. ================================================ FILE: docs/src/content/docs/agents/tools.mdx ================================================ --- title: AgentTools System description: Documentation for the AgentTools system in the Agent Squad --- The AgentTools system in the Agent Squad provides a flexible framework for defining, building, and managing tools that agents can use. It consists of two main classes: `AgentTool` and `AgentTools`, which work together to enable tool-based interactions in the orchestrator. ## Key Features - Support for multiple AI provider formats: Claude, Bedrock, OpenAI (coming soon) - Automatic function signature parsing - Type hint conversion to JSON schema - Flexible tool definition methods - Async/sync function handling - Built-in tool result formatting ## AgentTool Class The `AgentTool` class is the core component that represents a single tool definition. It can be created in multiple ways and supports various formats for different AI providers. ### Creating an AgentTool There are several ways to create a tool: 1. **Using the Constructor**: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript // TypeScript implementation coming soon ``` ```python from agent_squad.utils import AgentTool def get_weather(location: str, units: str = "celsius") -> str: """Get weather information for a location. :param location: The city name to get weather for :param units: Temperature units (celsius/fahrenheit) """ return f'It is sunny in {city} with 30 {units}!' tool = AgentTool( name="weather_tool", description="Get current weather information", properties = { "location": { "type": "string", "description": "The city name to get weather for" }, "units": { "type": "string", "description": "the units of the weather data", } }, func=get_weather, enum_values={"units": ["celsius", "fahrenheit"]} ) ``` 2. **Using the docstring**: ```typescript // TypeScript implementation coming soon ``` ```python from agent_squad.utils import AgentTool def get_weather(location: str, units: str = "celsius") -> str: """Get weather information for a location. :param location: The city name to get weather for :param units: Temperature units (celsius/fahrenheit) """ return f'It is sunny in {city} with 30 {units}!' tool = AgentTool( name="weather_tool", func=get_weather, enum_values={"units": ["celsius", "fahrenheit"]} ) ``` ### Format Conversion The AgentTool class can output its definition in different formats for various AI providers: ```python tool = AgentTool( name="weather_tool", description="Get current weather information", func=get_weather, enum_values={"units": ["celsius", "fahrenheit"]} ) # For Claude claude_format = tool.to_claude_format() # For Bedrock bedrock_format = tool.to_bedrock_format() # For OpenAI openai_format = tool.to_openai_format() ``` ## AgentTools Class The `AgentTools` class manages multiple tool definitions and handles tool execution during agent interactions. It provides a unified interface for tool processing across different AI providers. ### Creating and Using AgentTools ```python from agent_squad.utils import AgentTools, AgentTool # Define your tools weather_tool = AgentTool("weather", "Get weather info", get_weather) # Create AgentTools instance tools = AgentTools([weather_tool]) # Format tool with an agent weather_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Weather Agent", streaming=True, description="Specialized agent for giving weather condition from a city.", tool_config={ 'tool': tools, 'toolMaxRecursions': 5, }, )) # Use AgentTools class with an agent weather_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Weather Agent", streaming=True, description="Specialized agent for giving weather condition from a city.", tool_config={ 'tool': AgentTools([weather_tool]), 'toolMaxRecursions': 5, }, )) ``` By using AgentTools, the logic of parsing the tool response from the Agent is handled directly by the class. ## Using AgentTools with an Agent ### 1. **Definition** ```typescript // TypeScript implementation coming soon ``` ```python from agent_squad.utils import AgentTools, AgentTool def get_weather(city:str): """ Fetches weather data for the given city using the Open-Meteo API. Returns the weather data or an error message if the request fails. :param city: The name of the city to get weather for :return: A formatted weather report for the specified city """ return f'It is sunny in {city}!' # Create a tool definition with name and description weather_tools:AgentTools = AgentTools(tools=[AgentTool( name='get_weather', func=get_weather )]) ``` ### 2. **Adding AgentTool to Agent** Here is an example of how you can add AgentTools to your Agent ```typescript // TypeScript implementation coming soon ``` ```python from agent_squad.utils import AgentTools, AgentTool from agent_squad.agents import (BedrockLLMAgent, BedrockLLMAgentOptions) # Configure and create the agent with our weather tool weather_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='weather-agent', description='Agent specialized in providing weather information for cities', tool_config={ 'tool': weather_tools, 'toolMaxRecursions': 5, # Maximum number of tool calls in one conversation } )) ``` ### 3. **Overriding the tool handler** When you need more control over tool execution, you can implement a custom handler using the useToolHandler option in your tool_config. This handler lets you: - Intercept and process the tool invocation before execution - Parse the tool block directly from your Agent's output - Generate and format custom tool responses ```typescript // TypeScript implementation coming soon ``` ```python from agent_squad.utils import AgentTools, AgentTool from agent_squad.agents import (BedrockLLMAgent, BedrockLLMAgentOptions) async def bedrock_weather_tool_handler( response: ConversationMessage, conversation: list[dict[str, Any]] ) -> ConversationMessage: """ Handles tool execution requests from the agent and processes the results. This handler: 1. Extracts tool use requests from the agent's response 2. Executes the requested tools with provided parameters 3. Formats the results for the agent to understand Parameters: response: The agent's response containing tool use requests conversation: The current conversation history Returns: A formatted message containing tool execution results """ response_content_blocks = response.content tool_results = [] if not response_content_blocks: raise ValueError("No content blocks in response") for content_block in response_content_blocks: # Handle regular text content if present if "text" in content_block: continue # Process tool use requests if "toolUse" in content_block: tool_use_block = content_block["toolUse"] tool_use_name = tool_use_block.get("name") if tool_use_name == "get_weather": tool_response = get_weather(tool_use_block["input"].get('city')) tool_results.append({ "toolResult": { "toolUseId": tool_use_block["toolUseId"], "content": [{"json": {"result": tool_response}}], } }) return ConversationMessage( role=ParticipantRole.USER.value, content=tool_results ) # Configure and create the agent with our weather tool weather_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='weather-agent', description='Agent specialized in providing weather information for cities', tool_config={ 'tool': weather_tools.to_bedrock_format(), 'toolMaxRecursions': 5, # Maximum number of tool calls in one conversation 'useToolHandler': bedrock_weather_tool_handler } )) ``` This approach provides flexibility when you need to extend the default tool behavior with custom logic, validation, or response formatting. The handler receives the raw tool block text and is responsible for all aspects of tool execution and response generation. ## Best Practices 1. **Function Documentation**: Always provide clear docstrings for functions used in tools. The system uses these for generating descriptions and parameter documentation. 2. **Type Hints**: Use Python type hints in your tool functions. These are automatically converted to appropriate JSON schema types. 3. **Error Handling**: Implement proper error handling in your tool functions. AgentTool execution errors are automatically captured and formatted appropriately. 4. **Provider Compatibility**: When creating tools, consider the formatting requirements of different AI providers if you plan to use the tools across multiple provider types. 5. **AgentTool Naming**: Use clear, descriptive names for your tools and maintain consistency in naming conventions across your application. By following these guidelines and leveraging the AgentTools system effectively, you can create powerful and flexible tool-based interactions in your Agent Squad implementation. ## Next Steps To continue learning about AgentTools in the Agent Squad System, head over to our [examples](https://github.com/awslabs/agent-squad/tree/main/examples/tools) in Github ================================================ FILE: docs/src/content/docs/classifiers/built-in/anthropic-classifier.mdx ================================================ --- title: Anthropic Classifier description: How to configure the Anthropic classifier --- The Anthropic Classifier is an alternative classifier for the Agent Squad that leverages Anthropic's AI models for intent classification. It provides powerful classification capabilities using Anthropic's state-of-the-art language models. The Anthropic Classifier extends the abstract `Classifier` class and uses the Anthropic API client to process requests and classify user intents. ## Features - Utilizes Anthropic's AI models (e.g., Claude) for intent classification - Configurable model selection and inference parameters - Supports custom system prompts and variables - Handles conversation history for context-aware classification ### Default Model The classifier uses Claude 3.5 Sonnet as its default model: ```typescript ANTHROPIC_MODEL_ID_CLAUDE_3_5_SONNET = "claude-3-5-sonnet-20240620" ``` ### Python Package If you haven't already installed the Anthropic-related dependencies, make sure to install them: ```bash pip install "agent-squad[anthropic]" ``` ### Basic Usage To use the AnthropicClassifier, you need to create an instance with your Anthropic API key and pass it to the Agent Squad: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { AnthropicClassifier } from "agent-squad"; import { AgentSquad } from "agent-squad"; const anthropicClassifier = new AnthropicClassifier({ apiKey: 'your-anthropic-api-key' }); const orchestrator = new AgentSquad({ classifier: anthropicClassifier }); ``` ```python from agent_squad.classifiers import AnthropicClassifier, AnthropicClassifierOptions from agent_squad.orchestrator import AgentSquad anthropic_classifier = AnthropicClassifier(AnthropicClassifierOptions( api_key='your-anthropic-api-key' )) orchestrator = AgentSquad(classifier=anthropic_classifier) ``` ## System Prompt and Variables ### Full Default System Prompt The default system prompt used by the classifier is comprehensive and includes examples of both simple and complex interactions: ``` You are AgentMatcher, an intelligent assistant designed to analyze user queries and match them with the most suitable agent or department. Your task is to understand the user's request, identify key entities and intents, and determine which agent or department would be best equipped to handle the query. Important: The user's input may be a follow-up response to a previous interaction. The conversation history, including the name of the previously selected agent, is provided. If the user's input appears to be a continuation of the previous conversation (e.g., "yes", "ok", "I want to know more", "1"), select the same agent as before. Analyze the user's input and categorize it into one of the following agent types: {{AGENT_DESCRIPTIONS}} If you are unable to select an agent put "unknown" Guidelines for classification: Agent Type: Choose the most appropriate agent type based on the nature of the query. For follow-up responses, use the same agent type as the previous interaction. Priority: Assign based on urgency and impact. High: Issues affecting service, billing problems, or urgent technical issues Medium: Non-urgent product inquiries, sales questions Low: General information requests, feedback Key Entities: Extract important nouns, product names, or specific issues mentioned. For follow-up responses, include relevant entities from the previous interaction if applicable. For follow-ups, relate the intent to the ongoing conversation. Confidence: Indicate how confident you are in the classification. High: Clear, straightforward requests or clear follow-ups Medium: Requests with some ambiguity but likely classification Low: Vague or multi-faceted requests that could fit multiple categories Is Followup: Indicate whether the input is a follow-up to a previous interaction. Handle variations in user input, including different phrasings, synonyms, and potential spelling errors. For short responses like "yes", "ok", "I want to know more", or numerical answers, treat them as follow-ups and maintain the previous agent selection. Here is the conversation history that you need to take into account before answering: {{HISTORY}} Skip any preamble and provide only the response in the specified format. ``` ### Variable Replacements #### AGENT_DESCRIPTIONS Example ``` tech-support-agent:Specializes in resolving technical issues, software problems, and system configurations billing-agent:Handles all billing-related queries, payment processing, and subscription management customer-service-agent:Manages general inquiries, account questions, and product information requests sales-agent:Assists with product recommendations, pricing inquiries, and purchase decisions ``` ### Extended HISTORY Examples The conversation history is formatted to include agent names in the responses, allowing the classifier to track which agent handled each interaction. Each assistant response is prefixed with `[agent-name]` in the history, making it clear who provided each response: ``` user: I need help with my subscription assistant: [billing-agent] I can help you with your subscription. What specific information do you need? user: The premium features aren't working assistant: [tech-support-agent] I'll help you troubleshoot the premium features. Could you tell me which specific features aren't working? user: The cloud storage says I only have 5GB but I'm supposed to have 100GB assistant: [tech-support-agent] Let's verify your subscription status and refresh your storage allocation. When did you last see the correct storage amount? user: How much am I paying for this subscription? assistant: [billing-agent] I'll check your subscription details. Your current plan is $29.99/month for the Premium tier with 100GB storage. Would you like me to review your billing history? user: Yes please ``` Here, the history shows the conversation moving between `billing-agent` and `tech-support-agent` as the topic shifts between billing and technical issues. The agent prefixing (e.g., `[agent-name]`) is automatically handled by the Agent Squad when formatting the conversation history. This helps the classifier understand: - Which agent handled each part of the conversation - The context of previous interactions - When agent transitions occurred - How to maintain continuity for follow-up responses ## Tool-Based Response Structure The AnthropicClassifier uses a tool specification to enforce structured output from the model. This is a design pattern that ensures consistent and properly formatted responses. ### The Tool Specification ```json { "name": "analyzePrompt", "description": "Analyze the user input and provide structured output", "input_schema": { "type": "object", "properties": { "userinput": {"type": "string"}, "selected_agent": {"type": "string"}, "confidence": {"type": "number"} }, "required": ["userinput", "selected_agent", "confidence"] } } ``` ### Why Use Tools? 1. **Structured Output**: Instead of free-form text, the model must provide exactly the data structure we need. 2. **Guaranteed Format**: The tool schema ensures we always get: - A valid agent identifier - A properly formatted confidence score - All required fields 3. **Implementation Note**: The tool isn't actually executed - it's a pattern to force the model to structure its response in a specific way that maps directly to our `ClassifierResult` type. Example Response: ```json { "userinput": "I need to reset my password", "selected_agent": "tech-support-agent", "confidence": 0.95 } ``` ### Customizing the System Prompt You can override the default system prompt while maintaining the required agent descriptions and history variables. Here's how to do it: ```typescript orchestrator.classifier.setSystemPrompt( `You are a specialized routing expert with deep knowledge of {{INDUSTRY}} operations. Your available agents are: {{AGENT_DESCRIPTIONS}} Consider these key factors for {{INDUSTRY}} when routing: {{INDUSTRY_RULES}} Recent conversation context: {{HISTORY}} Route based on industry best practices and conversation history.`, { INDUSTRY: "healthcare", INDUSTRY_RULES: [ "- HIPAA compliance requirements", "- Patient data privacy protocols", "- Emergency request prioritization", "- Insurance verification processes" ] } ); ``` ```python orchestrator.classifier.set_system_prompt( """You are a specialized routing expert with deep knowledge of {{INDUSTRY}} operations. Your available agents are: {{AGENT_DESCRIPTIONS}} Consider these key factors for {{INDUSTRY}} when routing: {{INDUSTRY_RULES}} Recent conversation context: {{HISTORY}} Route based on industry best practices and conversation history.""", { "INDUSTRY": "healthcare", "INDUSTRY_RULES": [ "- HIPAA compliance requirements", "- Patient data privacy protocols", "- Emergency request prioritization", "- Insurance verification processes" ] } ) ``` Note: When customizing the prompt, you must include: - The `{{AGENT_DESCRIPTIONS}}` variable to list available agents - The `{{HISTORY}}` variable for conversation context - Clear instructions for agent selection - Response format expectations ## Configuration Options The AnthropicClassifier accepts the following configuration options: - `api_key` (required): Your Anthropic API key. - `model_id` (optional): The ID of the Anthropic model to use. Defaults to Claude 3.5 Sonnet. - `inference_config` (optional): A dictionary containing inference configuration parameters: - `max_tokens` (optional): The maximum number of tokens to generate. Defaults to 1000. - `temperature` (optional): Controls randomness in output generation. - `top_p` (optional): Controls diversity of output generation. - `stop_sequences` (optional): A list of sequences that will stop generation. ## Best Practices 1. **API Key Security**: Keep your Anthropic API key secure and never expose it in your code. 2. **Model Selection**: Choose appropriate models based on your needs and performance requirements. 3. **Inference Configuration**: Experiment with different parameters to optimize classification accuracy. 4. **System Prompt**: Consider customizing the system prompt for your specific use case, while maintaining the core classification structure. ## Limitations - Requires an active Anthropic API key - Subject to Anthropic's API pricing and rate limits - Classification quality depends on the quality of agent descriptions and system prompt For more information, see the [Classifier Overview](/agent-squad/classifier/overview) and [Agents](/agent-squad/agents/overview) documentation. ================================================ FILE: docs/src/content/docs/classifiers/built-in/bedrock-classifier.mdx ================================================ --- title: Bedrock Classifier description: How to configure the Bedrock classifier --- The Bedrock Classifier is the default classifier used in the Agent Squad. It leverages Amazon Bedrock's models through Converse API providing powerful and flexible classification capabilities. ## Features - Utilizes Amazon Bedrock's models through Converse API - Configurable model selection and inference parameters - Supports custom system prompts and variables - Handles conversation history for context-aware classification ### Default Model The classifier uses Claude 3.5 Sonnet as its default model: ```typescript BEDROCK_MODEL_ID_CLAUDE_3_5_SONNET = "anthropic.claude-3-5-sonnet-20240620-v1:0" ``` ### Model Support for Tool Choice The BedrockClassifier's toolChoice configuration for structured outputs is only available with specific models in Amazon Bedrock. As of January 2025, the following models support tool use: - **Anthropic Models**: - Claude 3 models (all variants except Haiku) - Claude 3.5 Sonnet (`anthropic.claude-3-5-sonnet-20240620-v1:0`) - Claude 3.5 Sonnet v2 - **AI21 Labs Models**: - Jamba 1.5 Large - Jamba 1.5 Mini - **Amazon Models**: - Nova Pro - Nova Lite - Nova Micro - **Meta Models**: - Llama 3.2 11b - Llama 3.2 90b - **Mistral AI Models**: - Mistral Large - Mistral Large 2 (24.07) - Mistral Small - **Cohere Models**: - Command R - Command R+ When using other models: - The tool configuration will still be included in the request - The model won't be explicitly directed to use the `analyzePrompt` tool - Response formats may be less consistent For the most up-to-date list of supported models and their features, please refer to the [Amazon Bedrock Converse API documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html). import { Aside } from '@astrojs/starlight/components'; ### Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` ### Basic Usage By default, the Agent Squad uses the Bedrock Classifier: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() ``` ## System Prompt and Variables ### Full Default System Prompt The default system prompt used by the classifier is comprehensive and includes examples of both simple and complex interactions: ``` You are AgentMatcher, an intelligent assistant designed to analyze user queries and match them with the most suitable agent or department. Your task is to understand the user's request, identify key entities and intents, and determine which agent or department would be best equipped to handle the query. Important: The user's input may be a follow-up response to a previous interaction. The conversation history, including the name of the previously selected agent, is provided. If the user's input appears to be a continuation of the previous conversation (e.g., "yes", "ok", "I want to know more", "1"), select the same agent as before. Analyze the user's input and categorize it into one of the following agent types: {{AGENT_DESCRIPTIONS}} If you are unable to select an agent put "unknown" Guidelines for classification: Agent Type: Choose the most appropriate agent type based on the nature of the query. For follow-up responses, use the same agent type as the previous interaction. Priority: Assign based on urgency and impact. High: Issues affecting service, billing problems, or urgent technical issues Medium: Non-urgent product inquiries, sales questions Low: General information requests, feedback Key Entities: Extract important nouns, product names, or specific issues mentioned. For follow-up responses, include relevant entities from the previous interaction if applicable. For follow-ups, relate the intent to the ongoing conversation. Confidence: Indicate how confident you are in the classification. High: Clear, straightforward requests or clear follow-ups Medium: Requests with some ambiguity but likely classification Low: Vague or multi-faceted requests that could fit multiple categories Is Followup: Indicate whether the input is a follow-up to a previous interaction. Handle variations in user input, including different phrasings, synonyms, and potential spelling errors. For short responses like "yes", "ok", "I want to know more", or numerical answers, treat them as follow-ups and maintain the previous agent selection. Here is the conversation history that you need to take into account before answering: {{HISTORY}} Skip any preamble and provide only the response in the specified format. ``` ### Example Variable Replacements #### AGENT_DESCRIPTIONS Example ``` aws-services-agent:Expert in AWS services configuration, troubleshooting, and best practices security-agent:Handles security-related inquiries, compliance questions, and access management billing-agent:Manages AWS billing inquiries, cost optimization, and pricing details developer-support:Assists with code, SDK issues, and AWS service implementation ``` ### Extended HISTORY Examples The conversation history is formatted to include agent names in the responses, allowing the classifier to track which agent handled each interaction. Each assistant response is prefixed with `[agent-name]` in the history, making it clear who provided each response: #### Example 1: AWS Services to Billing Transition ``` user: How do I set up an S3 bucket with versioning? assistant: [aws-services-agent] I'll help you set up S3 versioning. First, do you already have an S3 bucket created? user: No, but how much will it cost? assistant: [billing-agent] I can help explain S3 pricing. The cost depends on storage usage, versioning, and access patterns. What's your expected storage volume? user: Around 500GB of files that change frequently ``` In this example, you can see how the history shows the transition from `aws-services-agent` to `billing-agent`, with each response clearly marked. #### Example 2: Multiple Department Interaction ``` user: I can't access my EC2 instance assistant: [aws-services-agent] Let's troubleshoot your EC2 access. Can you see the instance in your console? user: Yes, but SSH keeps saying permission denied assistant: [security-agent] This sounds like a security group or key pair issue. When was the last time you successfully connected to this instance? user: Yesterday, but we rotated some keys assistant: [security-agent] I'll help you verify your key configuration and permissions. Which key pair are you currently using to connect? ``` Here, the history shows the conversation moving between `aws-services-agent` and `security-agent` as the topic shifts from general access to security configuration. The agent prefixing (e.g., `[agent-name]`) is automatically handled by the Agent Squad when formatting the conversation history. This helps the classifier understand: - Which agent handled each part of the conversation - The context of previous interactions - When agent transitions occurred - How to maintain continuity for follow-up responses ## Tool-Based Response Structure Like the Anthropic Classifier, the BedrockClassifier uses a tool specification to enforce structured output from the model. This is a design pattern that ensures consistent and properly formatted responses. ### The Tool Specification ```json { "toolSpec": { "name": "analyzePrompt", "description": "Analyze the user input and provide structured output", "inputSchema": { "json": { "type": "object", "properties": { "userinput": {"type": "string"}, "selected_agent": {"type": "string"}, "confidence": {"type": "number"} }, "required": ["userinput", "selected_agent", "confidence"] } } } } ``` ### Why Use Tools? 1. **Structured Output**: Instead of free-form text, the model must provide exactly the data structure we need. 2. **Guaranteed Format**: The tool schema ensures we always get: - A valid agent identifier - A properly formatted confidence score - All required fields 3. **Implementation Note**: The tool isn't actually executed - it's a pattern to force the model to structure its response in a specific way that maps directly to our `ClassifierResult` type. Example Response: ```json { "userinput": "How do I configure VPC endpoints?", "selected_agent": "aws-services-agent", "confidence": 0.95 } ``` ### Custom Configuration You can customize the BedrockClassifier by creating an instance with specific options: ```typescript import { BedrockClassifier, AgentSquad } from "agent-squad"; const customBedrockClassifier = new BedrockClassifier({ modelId: 'anthropic.claude-3-sonnet-20240229-v1:0', region: 'us-west-2', inferenceConfig: { maxTokens: 500, temperature: 0.7, topP: 0.9 } }); const orchestrator = new AgentSquad({ classifier: customBedrockClassifier }); ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.classifiers import BedrockClassifier, BedrockClassifierOptions custom_bedrock_classifier = BedrockClassifier(BedrockClassifierOptions( model_id='anthropic.claude-3-sonnet-20240229-v1:0', region='us-west-2', inference_config={ 'maxTokens': 500, 'temperature': 0.7, 'topP': 0.9 } )) orchestrator = AgentSquad(classifier=custom_bedrock_classifier) ``` The BedrockClassifier accepts the following configuration options: - `model_id` (optional): The ID of the Bedrock model to use. Defaults to Claude 3.5 Sonnet. - `region` (optional): The AWS region to use. If not provided, it will use the `REGION` environment variable. - `inference_config` (optional): A dictionary containing inference configuration parameters: - `maxTokens` (optional): The maximum number of tokens to generate. - `temperature` (optional): Controls randomness in output generation. - `topP` (optional): Controls diversity of output generation. - `stopSequences` (optional): A list of sequences that will stop generation. ## Best Practices 1. **AWS Configuration**: Ensure proper AWS credentials and Bedrock access are configured. 2. **Model Selection**: Choose appropriate models based on your use case requirements. 3. **Region Selection**: Consider using the region closest to your application for optimal latency. 4. **Inference Configuration**: Experiment with different parameters to optimize classification accuracy. 5. **System Prompt**: Consider customizing the system prompt for your specific use case, while maintaining the core classification structure. ## Limitations - Requires an active AWS account with access to Amazon Bedrock - Classification quality depends on the chosen model and the quality of agent descriptions - Subject to Amazon Bedrock service quotas and pricing For more information, see the [Classifier Overview](/agent-squad/classifier/overview) and [Agents](/agent-squad/agents/overview) documentation. ================================================ FILE: docs/src/content/docs/classifiers/built-in/openai-classifier.mdx ================================================ --- title: OpenAI Classifier description: How to configure the OpenAI classifier --- The OpenAI Classifier is a built-in classifier for the Agent Squad that leverages OpenAI's language models for intent classification. It provides robust classification capabilities using OpenAI's state-of-the-art models like GPT-4o. The OpenAI Classifier extends the abstract `Classifier` class and uses the OpenAI API client to process requests and classify user intents. ## Features - Utilizes OpenAI's advanced models (e.g., GPT-4o) for intent classification - Configurable model selection and inference parameters - Supports custom system prompts and variables - Handles conversation history for context-aware classification ## Basic Usage ### Python Package If you haven't already installed the OpenAI-related dependencies, make sure to install them: ```bash pip install "agent-squad[openai]" ``` To use the OpenAIClassifier, you need to create an instance with your OpenAI API key and pass it to the Agent Squad: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { OpenAIClassifier } from "agent-squad"; import { AgentSquad } from "agent-squad"; const openaiClassifier = new OpenAIClassifier({ apiKey: 'your-openai-api-key' }); const orchestrator = new AgentSquad({ classifier: openaiClassifier }); ``` ```python from agent_squad.classifiers import OpenAIClassifier, OpenAIClassifierOptions from agent_squad.orchestrator import AgentSquad openai_classifier = OpenAIClassifier(OpenAIClassifierOptions( api_key='your-openai-api-key' )) orchestrator = AgentSquad(classifier=openai_classifier) ``` ## Custom Configuration You can customize the OpenAIClassifier by providing additional options: ```typescript const customOpenAIClassifier = new OpenAIClassifier({ apiKey: 'your-openai-api-key', modelId: 'gpt-4o', inferenceConfig: { maxTokens: 500, temperature: 0.7, topP: 0.9, stopSequences: [''] } }); const orchestrator = new AgentSquad({ classifier: customOpenAIClassifier }); ``` ```python from agent_squad.classifiers import OpenAIClassifier, OpenAIClassifierOptions from agent_squad.orchestrator import AgentSquad custom_openai_classifier = OpenAIClassifier(OpenAIClassifierOptions( api_key='your-openai-api-key', model_id='gpt-4o', inference_config={ 'max_tokens': 500, 'temperature': 0.7, 'top_p': 0.9, 'stop_sequences': [''] } )) orchestrator = AgentSquad(classifier=custom_openai_classifier) ``` The OpenAIClassifier accepts the following configuration options: - `api_key` (required): Your OpenAI API key. - `model_id` (optional): The ID of the OpenAI model to use. Defaults to GPT-4 Turbo. - `inference_config` (optional): A dictionary containing inference configuration parameters: - `max_tokens` (optional): The maximum number of tokens to generate. Defaults to 1000 if not specified. - `temperature` (optional): Controls randomness in output generation. - `top_p` (optional): Controls diversity of output generation. - `stop_sequences` (optional): A list of sequences that, when generated, will stop the generation process. ## Customizing the System Prompt You can customize the system prompt used by the OpenAIClassifier: ```typescript orchestrator.classifier.setSystemPrompt( ` Custom prompt template with placeholders: {{AGENT_DESCRIPTIONS}} {{HISTORY}} {{CUSTOM_PLACEHOLDER}} `, { CUSTOM_PLACEHOLDER: "Value for custom placeholder" } ); ``` ```python orchestrator.classifier.set_system_prompt( """ Custom prompt template with placeholders: {{AGENT_DESCRIPTIONS}} {{HISTORY}} {{CUSTOM_PLACEHOLDER}} """, { "CUSTOM_PLACEHOLDER": "Value for custom placeholder" } ) ``` ## Processing Requests The OpenAIClassifier processes requests using the `process_request` method, which is called internally by the orchestrator. This method: 1. Prepares the user's message and conversation history. 2. Constructs a request for the OpenAI API, including the system prompt and function calling configurations. 3. Sends the request to the OpenAI API and processes the response. 4. Returns a `ClassifierResult` containing the selected agent and confidence score. ## Error Handling The OpenAIClassifier includes error handling to manage potential issues during the classification process. If an error occurs, it will log the error and raise an exception, which can be caught and handled by the orchestrator. ## Best Practices 1. **API Key Security**: Ensure your OpenAI API key is kept secure and not exposed in your codebase. 2. **Model Selection**: Choose an appropriate model based on your use case and performance requirements. 3. **Inference Configuration**: Experiment with different inference parameters to find the best balance between response quality and speed. 4. **System Prompt**: Craft a clear and comprehensive system prompt to guide the model's classification process effectively. ## Limitations - Requires an active OpenAI API key. - Classification quality depends on the chosen model and the quality of your system prompt and agent descriptions. - API usage is subject to OpenAI's pricing and rate limits. For more information on using and customizing the Agent Squad, refer to the [Classifier Overview](/agent-squad/classifier/overview) and [Agents](/agent-squad/agents/overview) documentation. ================================================ FILE: docs/src/content/docs/classifiers/custom-classifier.mdx ================================================ --- title: Custom classifier description: How to configure and customize the Classifier in the Agent Squad System --- This guide explains how to create a custom classifier for the Agent Squad by extending the abstract `Classifier` class. Custom classifiers allow you to implement your own logic for intent classification and agent selection. ## Overview To create a custom classifier, you need to: 1. Extend the abstract `Classifier` class 2. Implement the required `process_request` method 3. Optionally override other methods for additional customization ## Step-by-Step Guide ### 1. Extend the Classifier Class Create a new class that extends the abstract `Classifier` class: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { Classifier } from './path-to-classifier'; import { ClassifierResult, ConversationMessage } from './path-to-types'; export class MyCustomClassifier extends Classifier { // Implementation will go here } ``` ```python from agent_squad.classifiers import Classifier from agent_squad.types import ClassifierResult, ConversationMessage from typing import List class MyCustomClassifier(Classifier): # Implementation will go here pass ``` ### 2. Implement the process_request Method The `process_request` method is the core of your custom classifier. It should analyze the input and return a `ClassifierResult`: ```typescript export class MyCustomClassifier extends Classifier { async processRequest( inputText: string, chatHistory: ConversationMessage[] ): Promise { // Your custom classification logic goes here return { selectedAgent: firstAgent, confidence: 1.0 }; } } ``` ```python class MyCustomClassifier(Classifier): async def process_request( self, input_text: str, chat_history: List[ConversationMessage] ) -> ClassifierResult: # Your custom classification logic goes here first_agent = next(iter(self.agents.values())) return ClassifierResult( selected_agent=first_agent, confidence=1.0 ) ``` ## Using Your Custom Classifier To use your custom classifier with the Agent Squad: ```typescript import { AgentSquad } from './path-to-agent-squad'; import { MyCustomClassifier } from './path-to-my-custom-classifier'; const customClassifier = new MyCustomClassifier(); const orchestrator = new AgentSquad({ classifier: customClassifier }); ``` ```python from agent_squad.orchestrator import AgentSquad from path_to_my_custom_classifier import MyCustomClassifier custom_classifier = MyCustomClassifier() orchestrator = AgentSquad(classifier=custom_classifier) ``` ## Best Practices 1. **Robust Analysis**: Implement thorough analysis of the input text and chat history to make informed classification decisions. 2. **Error Handling**: Include proper error handling in your `process_request` method to gracefully handle unexpected inputs or processing errors. 3. **Extensibility**: Design your custom classifier to be easily extensible for future improvements or adaptations. 4. **Performance**: Consider the performance implications of your classification logic, especially for high-volume applications. ## Example: Keyword-Based Classifier Here's an example of a simple keyword-based classifier: ```typescript import { Classifier } from './path-to-classifier'; import { ClassifierResult, ConversationMessage, Agent } from './path-to-types'; export class KeywordClassifier extends Classifier { private keywordMap: { [keyword: string]: string }; constructor(keywordMap: { [keyword: string]: string }) { super(); this.keywordMap = keywordMap; } async processRequest( inputText: string, chatHistory: ConversationMessage[] ): Promise { const lowercaseInput = inputText.toLowerCase(); for (const [keyword, agentId] of Object.entries(this.keywordMap)) { if (lowercaseInput.includes(keyword)) { const selectedAgent = this.getAgentById(agentId); return { selectedAgent, confidence: 0.8 // Simple fixed confidence }; } } // Default to the first agent if no keyword matches const defaultAgent = Object.values(this.agents)[0]; return { selectedAgent: defaultAgent, confidence: 0.5 }; } } // Usage const keywordMap = { 'technical': 'tech-support-agent', 'billing': 'billing-agent', 'sales': 'sales-agent' }; const keywordClassifier = new KeywordClassifier(keywordMap); const orchestrator = new AgentSquad({ classifier: keywordClassifier }); ``` ```python from agent_squad.classifiers import Classifier from agent_squad.types import ClassifierResult, ConversationMessage from agent_squad.orchestrator import AgentSquad from typing import List, Dict class KeywordClassifier(Classifier): def __init__(self, keyword_map: Dict[str, str]): super().__init__() self.keyword_map = keyword_map async def process_request( self, input_text: str, chat_history: List[ConversationMessage] ) -> ClassifierResult: lowercase_input = input_text.lower() for keyword, agent_id in self.keyword_map.items(): if keyword in lowercase_input: selected_agent = self.get_agent_by_id(agent_id) return ClassifierResult( selected_agent=selected_agent, confidence=0.8 # Simple fixed confidence ) # Default to the first agent if no keyword matches default_agent = next(iter(self.agents.values())) return ClassifierResult( selected_agent=default_agent, confidence=0.5 ) # Usage keyword_map = { 'technical': 'tech-support-agent', 'billing': 'billing-agent', 'sales': 'sales-agent' } keyword_classifier = KeywordClassifier(keyword_map) orchestrator = AgentSquad(classifier=keyword_classifier) ``` This example demonstrates a basic keyword-based classification strategy. You can expand on this concept to create more sophisticated custom classifiers based on your specific needs. ## Conclusion Creating a custom classifier allows you to implement specialized logic for intent classification and agent selection in the Agent Squad. By extending the `Classifier` class and implementing the `process_request` method, you can tailor the classification process to your specific use case and requirements. Remember to thoroughly test your custom classifier to ensure it performs well across a wide range of inputs and scenarios. ================================================ FILE: docs/src/content/docs/classifiers/overview.mdx ================================================ --- title: Classifier overview description: An introduction to the Classifier in the Agent Squad --- The Classifier is a crucial component of the Agent Squad, responsible for analyzing user input and identifying the most appropriate agents. The orchestrator supports multiple classifier implementations, with Bedrock Classifier and Anthropic Classifier being the primary options. ## Available Classifiers - **[Bedrock Classifier](/agent-squad/classifiers/built-in/bedrock-classifier)** leverages Amazon Bedrock's AI models for intent classification. It is the default classifier used by the orchestrator. - **[Anthropic Classifier](/agent-squad/classifiers/built-in/anthropic-classifier)** uses Anthropic's AI models for intent classification. It provides an alternative option for users who prefer or have access to Anthropic's services. - **[OpenAI Classifier](/agent-squad/classifiers/built-in/openai-classifier)** uses OpenAI's AI models for intent classification. It provides an alternative option for users who prefer or have access to OpenAI's services. ### Process Flow Regardless of the classifier used, the general process remains the same: 1. User input is collected by the orchestrator. 2. The Classifier performs input analysis, considering: - Conversation history across all agents - Individual agent profiles and capabilities 3. The most suitable agent is determined. By default, if no agent is selected the orchestrator can be configured to: A. Use a default agent (a **[BedrockLLMAgent for example](/agent-squad/agents/built-in/bedrock-llm-agent)**) B. Return a message prompting the user for more information. This behavior can be customized using the `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED` and `NO_SELECTED_AGENT_MESSAGE` configuration options in the orchestrator. For a detailed explanation of these options and other orchestrator configurations, please refer to the [Orchestrator Overview](/agent-squad/orchestrator/overview#agent-selection-and-default-behavior) page. The classifier's decision-making process is crucial for the effective routing of user queries to the most appropriate agent, ensuring a seamless and efficient multi-agent interaction experience. ### Initialization When you create a new Orchestrator by initializing a `AgentSquad` the default Bedrock Classifier is initialized. import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript const orchestrator = new AgentSquad(); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() ``` To use the Anthropic Classifier, you can pass it as an option: ```typescript import { AnthropicClassifier } from "agent-squad"; const anthropicClassifier = new AnthropicClassifier({ apiKey: 'your-anthropic-api-key' }); const orchestrator = new AgentSquad({ classifier: anthropicClassifier }); ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.classifiers import AnthropicClassifier, AnthropicClassifierOptions anthropic_classifier = AnthropicClassifier(AnthropicClassifierOptions( api_key='your-anthropic-api-key' )) orchestrator = AgentSquad(classifier=anthropic_classifier) ``` ## Custom Classifier Implementation You can provide your own custom implementation of the classifier by extending the abstract `Classifier` class. For details on how to do this, please refer to the [Custom Classifier](/agent-squad/classifiers/custom-classifier) section. ## Testing You can test any Classifier directly using the `classify` method: ```typescript const response = await orchestrator.classifyIntent(userInput, userId, sessionId); console.log('\n** RESPONSE ** \n'); console.log(` > Agent ID: ${response.selectedAgent?.id}`); console.log(` > Agent Name: ${response.selectedAgent?.name}`); console.log(` > Confidence: ${response.confidence}\n`); ``` ```python import asyncio async def test_classifier(): user_input = "What are the symptoms of the flu?" user_id = "test_user" session_id = "test_session" # Fetch chat history (this might vary depending on your implementation) chat_history = await orchestrator.storage.fetch_all_chats(user_id, session_id) # Classify the input response = await orchestrator.classifier.classify(user_input, chat_history) print('\n** RESPONSE ** \n') print(f" > Agent ID: {response.selected_agent.id if response.selected_agent else 'None'}") print(f" > Agent Name: {response.selected_agent.name if response.selected_agent else 'None'}") print(f" > Confidence: {response.confidence}\n") # Run the async function asyncio.run(test_classifier()) ``` This allows you to: - Verify the Classifier's decision-making process - Test different inputs and conversation scenarios - Fine-tune the system prompt or agent descriptions ## Common Issues - **Misclassification**: If you notice frequent misclassifications, review and update agent descriptions or adjust the system prompt. - **API Key Issues**: For AnthropicClassifier, ensure your API key is valid and properly configured. - **Model Availability**: For BedrockClassifier, ensure you have access to the specified Amazon Bedrock model in your AWS account. ## Choosing the Right Classifier When deciding between different classifiers, consider: 1. **API Access**: Which service you have access to and prefer. 2. **Model Performance**: Test classifiers with your specific use case to determine which performs better for your needs. 3. **Cost**: Compare the pricing structures for your expected usage. By thoroughly testing and debugging your chosen Classifier, you can ensure accurate intent classification and efficient query routing in your Agent Squad. ## Direct Classifier Access ### With Context Management Test the classifier with full conversation history handling: ```typescript const response = await orchestrator.classifyRequest(userInput, userId, sessionId); ``` ```python response = await orchestrator.classify_request(user_input, user_id, session_id) ``` This method: - Retrieves conversation history automatically - Maintains context across test calls - Ideal for end-to-end testing ### Without Context Test raw classification without conversation history: ```typescript const response = await orchestrator.classifier.classify(userInput, []); ``` ```python response = await orchestrator.classifier.classify(user_input, []) ``` Best for: - Prompt tuning - Single-input validation - Classification unit tests --- For more detailed information on each classifier, refer to the [BedrockClassifier](/agent-squad/classifiers/built-in/bedrock-classifier) and [AnthropicClassifier](/classifiers/built-in/anthropic-classifier) documentation pages. ================================================ FILE: docs/src/content/docs/cookbook/examples/api-agent.mdx ================================================ --- title: Api Agent description: A guide to creating an API agent and integrating it into the Agent Squad System. --- This example will walk you through creating an Api agent and integrating it into your Agent Squad System. Let's dive in! ## 📚Prerequisites: - Basic knowledge of TypeScript or Python - Familiarity with the Agent Squad System ## 🧬 1. Create the Api Agent class: Let's create our `ApiAgent` class. This class extends the `Agent` abstract class from the Agent Squad. The [process_request](../overview#abstract-method-processrequest) method must be implemented by the `ApiAgent` import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { ConversationMessage, ParticipantRole, Agent, AgentOptions } from "agent-squad"; /** * Extended options for the ApiAgent class. */ export interface ApiAgentOptions extends AgentOptions { endpoint: string; method: string; streaming?: boolean; headersCallback?: () => Record; inputPayloadEncoder?: (inputText: string, ...additionalParams: any) => any; outputPayloadDecoder?: (response: any) => any; } /** * ApiAgent class for handling API-based agent interactions. */ export class ApiAgent extends Agent { private options: ApiAgentOptions; constructor(options: ApiAgentOptions) { super(options); this.options = options; this.options.inputPayloadEncoder = options.inputPayloadEncoder ?? this.defaultInputPayloadEncoder; this.options.outputPayloadDecoder = options.outputPayloadDecoder ?? this.defaultOutputPayloadDecoder; } /** * Default input payload encoder. */ private defaultInputPayloadEncoder(inputText: string, chatHistory: ConversationMessage[]): any { return { input: inputText, history: chatHistory }; } /** * Default output payload decoder. */ private defaultOutputPayloadDecoder(response: any): any { return response.output; } /** * Fetch data from the API. * @param payload - The payload to send to the API. * @param streaming - Whether to use streaming or not. */ private async *fetch(payload: any, streaming: boolean = false): AsyncGenerator { const headers = this.getHeaders(); const response = await this.sendRequest(payload, headers); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } if (!response.body) { throw new Error('Response body is null'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); try { if (streaming) { yield* this.handleStreamingResponse(reader, decoder); } else { return yield* this.handleNonStreamingResponse(reader, decoder); } } finally { reader.releaseLock(); } } /** * Get headers for the API request. */ private getHeaders(): Record { const defaultHeaders = { 'Content-Type': 'application/json', }; return this.options.headersCallback ? { ...defaultHeaders, ...this.options.headersCallback() } : defaultHeaders; } /** * Send the API request. */ private async sendRequest(payload: any, headers: Record): Promise { return fetch(this.options.endpoint, { method: this.options.method, headers: headers, body: JSON.stringify(payload), }); } /** * Handle streaming response. */ private async *handleStreamingResponse(reader: any, decoder: any): AsyncGenerator { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const message = this.options.outputPayloadDecoder!(chunk); yield message; } } /** * Handle non-streaming response. */ private async *handleNonStreamingResponse(reader: any, decoder: any): AsyncGenerator { let result = ''; while (true) { const { done, value } = await reader.read(); if (done) break; result += decoder.decode(value, { stream: false }); } return result; } /** * Process the request and return the response. */ async processRequest( inputText: string, userId: string, sessionId: string, chatHistory: ConversationMessage[], additionalParams?: Record ): Promise> { const payload = this.options.inputPayloadEncoder!(inputText, chatHistory, userId, sessionId, additionalParams); if (this.options.streaming) { return this.fetch(payload, true); } else { const result = await this.fetch(payload, false).next(); return { role: ParticipantRole.ASSISTANT, content: [{ text: this.options.outputPayloadDecoder!(result.value) }] }; } } } ``` ```python from typing import List, Dict, Optional, AsyncIterable, Any, Callable from dataclasses import dataclass, field from agent_squad.agents import Agent, AgentOptions from agent_squad.types import ConversationMessage, ParticipantRole import aiohttp import json @dataclass class ApiAgentOptions(AgentOptions): endpoint: str method: str streaming: bool = False headers_callback: Optional[Callable[[], Dict[str, str]]] = None input_payload_encoder: Optional[Callable[[str, List[ConversationMessage], str, str, Optional[Dict[str, str]]], Any]] = None output_payload_decoder: Optional[Callable[[Any], Any]] = None class ApiAgent(Agent): def __init__(self, options: ApiAgentOptions): super().__init__(options) self.options = options self.options.input_payload_encoder = options.input_payload_encoder or self.default_input_payload_encoder self.options.output_payload_decoder = options.output_payload_decoder or self.default_output_payload_decoder @staticmethod def default_input_payload_encoder(input_text: str, chat_history: List[ConversationMessage], user_id: str, session_id: str, additional_params: Optional[Dict[str, str]] = None) -> Dict: return {"input": input_text, "history": chat_history} @staticmethod def default_output_payload_decoder(response: Any) -> Any: return response.get('output') async def fetch(self, payload: Any, streaming: bool = False) -> AsyncIterable[Any]: headers = self.get_headers() async with aiohttp.ClientSession() as session: async with session.request(self.options.method, self.options.endpoint, headers=headers, json=payload) as response: if response.status != 200: raise Exception(f"HTTP error! status: {response.status}") if streaming: async for chunk in response.content.iter_any(): yield self.options.output_payload_decoder(chunk.decode()) else: content = await response.text() yield self.options.output_payload_decoder(content) def get_headers(self) -> Dict[str, str]: default_headers = {'Content-Type': 'application/json'} if self.options.headers_callback: return {**default_headers, **self.options.headers_callback()} return default_headers async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage], additional_params: Optional[Dict[str, str]] = None ) -> ConversationMessage | AsyncIterable[Any]: payload = self.options.input_payload_encoder(input_text, chat_history, user_id, session_id, additional_params) if self.options.streaming: return self.fetch(payload, True) else: result = await self.fetch(payload, False).__anext__() return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": result}] ) ``` This ApiAgent class provides flexibility for users to customize how input is encoded before sending to the API, how output is decoded after receiving from the API, and how headers are generated. This is done through three optional callbacks in the ApiAgentOptions interface: - input_payload_encoder - output_payload_decoder - headers_callback Let's break these down: **1. input_payload_encoder:** This function allows users to customize how the input is formatted before sending it to the API. - Default behavior: If not provided, it uses the default_input_payload_encoder, which creates a payload with `input` and `history` fields. - Custom behavior: Users can provide their own function to format the input however their API expects it. This function receives the input text, chat history, and other parameters, allowing for flexible payload creation. **Example usage:** ```typescript const customInputEncoder = (inputText, chatHistory, userId, sessionId, additionalParams) => { return { message: inputText, context: chatHistory, user: userId, session: sessionId, ...additionalParams }; }; ``` ```python def custom_input_encoder(input_text, chat_history, user_id, session_id, additional_params): return { "message": input_text, "context": chat_history, "user": user_id, "session": session_id, **(additional_params or {}) } ``` **2. output_payload_decoder:** This function allows users to customize how the API response is processed. - Default behavior: If not provided, it uses the default_output_payload_decoder, which simply returns the `output` field from the response. - Custom behavior: Users can provide their own function to extract and process the relevant data from the API response. **Example usage:** ```typescript const customOutputDecoder = (response) => { return { text: response.generated_text, customAttribute: response.customAttribute }; }; ``` ```python def custom_output_decoder(response): return { "text": response.get("generated_text"), "customAttribute": response.get("customAttribute") } ``` **3. headers_callback:** This function allows users to add custom headers to the API request. - Default behavior: If not provided, it only sets the 'Content-Type' header to 'application/json'. - Custom behavior: Users can provide their own function to return additional headers, which will be merged with the default headers. **Example usage:** ```typescript const customHeadersCallback = () => { return { 'Authorization': 'Bearer ' + getApiKey(), 'X-Custom-Header': 'SomeValue' }; }; ``` ```python def custom_headers_callback(): return { 'Authorization': f'Bearer {get_api_key()}', 'X-Custom-Header': 'SomeValue' } ``` To use these custom functions, you would include them in the options when creating a new ApiAgent. This design allows users to adapt the ApiAgent to work with a wide variety of APIs without having to modify the core ApiAgent class. It provides a flexible way to handle different API specifications and requirements. Now that we have our `ApiAgent`, let's add it to the Agent Squad: ## 🔗 2. Add ApiAgent to the orchestrator: If you have used the quickstarter sample program, you can add the Api agent and run it: ```typescript import { ApiAgent } from "./apiAgent"; import { AgentSquad } from "agent-squad" const orchestrator = new AgentSquad(); orchestrator.addAgent( new ApiAgent({ name: "Text Summarization Agent", description: "This is a very simple text summarization agent.", endpoint:"http://127.0.0.1:11434/api/chat", method:"POST", streaming: true, inputPayloadEncoder: customInputEncoder, outputPayloadDecoder: customOutputDecoder, })) ``` ```python from api_agent import ApiAgent, ApiAgentOptions from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() orchestrator.add_agent( ApiAgent(ApiAgentOptions( name="Text Summarization Agent", description="This is a very simple text summarization agent.", endpoint="http://127.0.0.1:11434/api/chat", method="POST", streaming=True, input_payload_encoder=custom_input_encoder, output_payload_decoder=custom_output_decoder, )) ) ``` 🎉**And You're All Set!** ## 3.💡 **Next Steps:** - Experiment with different Api endpoints - Create specialized agents for various tasks using ApiAgent - Include your existing Api with the Agent Squad Happy coding! 🚀 ================================================ FILE: docs/src/content/docs/cookbook/examples/chat-chainlit-app.md ================================================ --- title: Chat Chainlit App with Agent Squad description: How to set up a Chainlit App using Agent Squad --- This example demonstrates how to build a chat application using Chainlit and the Agent Squad. It showcases a system with three specialized agents (Tech, Travel, and Health) working together through a streaming-enabled chat interface. ## Key Features - Streaming responses using Chainlit's real-time capabilities - Integration with multiple agent types (Bedrock and Ollama) - Custom classifier configuration using Claude 3 Haiku - Session management for user interactions - Complete chat history handling ## Quick Start ```bash # Clone the repository git clone https://github.com/awslabs/agent-squad.git cd agent-squad/examples/chat-chainlit-app # Install dependencies pip install -r requirements.txt # Run the application chainlit run app.py -w ``` ## Implementation Details ### Components 1. **Main Application** (`app.py`) - Orchestrator setup with custom Bedrock classifier - Chainlit event handlers for chat management - Streaming response handling 2. **Agent Configuration** (`agents.py`) - Tech Agent: Uses Claude 3 Sonnet via Bedrock - Travel Agent: Uses Claude 3 Sonnet via Bedrock - Health Agent: Uses Ollama with Llama 3.1 3. **Custom Integration** (`ollamaAgent.py`) - Custom implementation for Ollama integration - Streaming support for real-time responses ## Usage Notes - The application creates unique user and session IDs for each chat session - Responses are streamed in real-time using Chainlit's streaming capabilities - The system automatically routes queries to the most appropriate agent - Complete chat history is maintained throughout the session ## Example Interaction ```plaintext User: "What are the latest trends in AI?" → Routed to Tech Agent User: "Plan a trip to Paris" → Routed to Travel Agent User: "Recommend a workout routine" → Routed to Health Agent ``` Ready to build your own multi-agent chat application? Check out the complete [source code](https://github.com/awslabs/agent-squad/tree/main/examples/chat-chainlit-app) in our GitHub repository. ================================================ FILE: docs/src/content/docs/cookbook/examples/chat-demo-app.md ================================================ --- title: Demo Web App Deployment description: How to deploy the demo chat web application for the Agent Squad System --- ## 📘 Overview The Agent Squad framework includes a demo chat web application that showcases the capabilities of the system. This application is built using AWS CDK (Cloud Development Kit) and can be easily deployed to your AWS account. In the screen recording below, we demonstrate an extended version of the demo app that uses 6 specialized agents: - **Travel Agent**: Powered by an Amazon Lex Bot - **Weather Agent**: Utilizes a Bedrock LLM Agent with a tool to query the open-meteo API - **Math Agent**: Utilizes a Bedrock LLM Agent with two tools for executing mathematical operations - **Tech Agent**: A Bedrock LLM Agent designed to offer technical support and documentation assistance with direct access to **Agent Squad framework source code** - **Health Agent**: A Bedrock LLM Agent focused on addressing health-related queries Watch as the system seamlessly switches context between diverse topics, from booking flights to checking weather, solving math problems, and providing health information. Notice how the appropriate agent is selected for each query, maintaining coherence even with brief follow-up inputs. The demo highlights the system's ability to handle complex, multi-turn conversations while preserving context and leveraging specialized agents across various domains. ## 📋 Prerequisites Before deploying the demo web app, ensure you have the following: 1. An AWS account with appropriate permissions 2. AWS CLI installed and configured with your credentials 3. Node.js and npm installed on your local machine 4. AWS CDK CLI installed (`npm install -g aws-cdk`) ## 🚀 Deployment Steps Follow these steps to deploy the demo chat web application: 1. **Clone the Repository** (if you haven't already): ``` git clone https://github.com/awslabs/agent-squad.git cd agent-squad ``` 2. **Navigate to the Demo Web App Directory**: ``` cd examples/chat-demo-app ``` 3. **Install Dependencies**: ``` npm install ``` 4. **Bootstrap AWS CDK** (if you haven't used CDK in this AWS account/region before): ``` cdk bootstrap aws://123456789012/us-east-1 ``` replace `123456789012` with your account id. This chat-demo application is using the default [BedrockClassifier](http://localhost:4321/agent-squad/classifiers/built-in/bedrock-classifier#basic-usage) with Claude 3.5 Sonnet v1. Make sure to use a region where this model is available. If you plan on using a region different from us-east-1 (e.g us-west-2), make sure to also bootstrap us-east-1 region as well as the CDK stack also deploys infra in this region (lambda@edge function). 5. **Review and Customize the Stack** (optional): Open `chat-demo-app/cdk.json` and review the configuration. You can customize aspects of the deployment by enabling or disabling additional agents. ``` "context": { "enableLexAgent": true, ... ``` **enableLexAgent:** Enable the sample Airlines Bot (See AWS Blogpost [here](https://aws.amazon.com/blogs/machine-learning/automate-the-customer-service-experience-for-flight-reservations-using-amazon-lex/)) 6. **Deploy the Application**: ``` export AWS_DEFAULT_REGION=us-east-1 cdk deploy --all ``` 7. **Confirm Deployment**: CDK will show you a summary of the changes and ask for confirmation. Type 'y' and press Enter to proceed. 8. **Note the Outputs**: After deployment, CDK will display outputs including the URL of your deployed web app and the user pool ID. Save the URL and the user pool Id. 9. **Create a user in Amazon Cognito user pool**: ``` aws cognito-idp admin-create-user \ --user-pool-id your-region_xxxxxxx \ --username your@email.com \ --user-attributes Name=email,Value=your@email.com \ --temporary-password "MyChallengingPassword" \ --message-action SUPPRESS \ --region your-region ```` ## 🌐 Accessing the Demo Web App Once deployment is complete, you can access the demo chat web application by: 1. Opening the URL provided in the CDK outputs in your web browser. 2. Logging in with the temporary credentials provided (if applicable). ## ✅ Testing the Deployment To ensure the deployment was successful: 1. Open the web app URL in your browser. 2. Try sending a few messages to test the multi-agent system. 3. Verify that you receive appropriate responses from different agents. ## 🧹 Cleaning Up To avoid incurring unnecessary AWS charges, remember to tear down the deployment when you're done: ``` cdk destroy ``` Confirm the deletion when prompted. ## 🛠️ Troubleshooting If you encounter issues during deployment: 1. Ensure your AWS credentials are correctly configured. 2. Check that you have the necessary permissions in your AWS account. 3. Verify that all dependencies are correctly installed. 4. Review the AWS CloudFormation console for detailed error messages if the deployment fails. ## ➡️ Next Steps After successfully deploying the demo web app, you can: 1. Customize the web interface in the source code. 2. Modify the agent configurations to test different scenarios. 3. Integrate additional AWS services to enhance the application's capabilities. By deploying this demo web app, you can interact with your Agent Squad System in a user-friendly interface, showcasing its capabilities and helping you understand how it performs in a real-world scenario. ## ⚠️ Disclamer This demo application is intended solely for demonstration purposes. It is not designed for handling, storing, or processing any kind of Personally Identifiable Information (PII) or personal data. Users are strongly advised not to enter, upload, or use any PII or personal data within this application. Any use of PII or personal data is at the user's own risk and the developers of this application shall not be held responsible for any data breaches, misuse, or any other related issues. Please ensure that all data used in this demo is non-sensitive and anonymized. For production usage, it is crucial to implement proper security measures to protect PII and personal data. This includes obtaining proper permissions from users, utilizing encryption for data both in transit and at rest, and adhering to industry standards and regulations to maximize security. Failure to do so may result in data breaches and other serious security issues. Ready to build your own multi-agent chat application? Check out the complete [source code](https://github.com/awslabs/agent-squad/tree/main/examples/chat-demo-app) in our GitHub repository. ================================================ FILE: docs/src/content/docs/cookbook/examples/ecommerce-support-simulator.md ================================================ --- title: AI-Powered E-commerce Support Simulator description: How to deploy the demo AI-Powered E-commerce Support Simulator --- This project demonstrates the practical application of AI agents and human-in-the-loop interactions in an e-commerce support context. It showcases how AI can handle customer queries efficiently while seamlessly integrating human support when needed. ## Overview The AI-Powered E-commerce Support Simulator is designed to showcase a sophisticated customer support system that combines AI agents with human support. It demonstrates how AI can handle routine queries automatically while routing complex issues to human agents, providing a comprehensive support experience. ## Features - AI-powered response generation for common queries - Intelligent routing of complex issues to human support - Real-time chat functionality - Email-style communication option ## UI Modes ### Chat Mode The Chat Mode provides a real-time conversation interface, simulating instant messaging between customers and the support system. It features: - Separate chat windows for customer and support perspectives - Real-time message updates - Automatic scrolling to the latest message ![Chat Mode Screenshot](/agent-squad/chat_mode.png) ### Email Mode The Email Mode simulates asynchronous email communication. It includes: - Email composition interfaces for both customer and support - Pre-defined email templates for common scenarios - Response viewing areas for both parties ![Chat Mode Screenshot](/agent-squad/email_mode.png) ## Mock Data The project includes a `mock_data.json` file for testing and demonstration purposes. This file contains sample data that simulates various customer scenarios, product information, and order details. To view and use the mock data: 1. Navigate to the `public` directory in the project. 2. Open the `mock_data.json` file to view its contents. 3. Use the provided data to test different support scenarios and observe how the system handles various queries. ## AI and Human Interaction This simulator demonstrates the seamless integration of AI agents and human support: - Automated Handling: AI agents automatically process and respond to common or straightforward queries. - Human Routing: Complex or sensitive issues are identified and routed to human support agents. - Customer Notification: When a query is routed to human support, the customer receives an automatic confirmation. - Support Interface: The support side of the interface allows human agents to see which messages require their attention and respond accordingly. - Handoff Visibility: Users can observe when a query is handled by AI and when it's transferred to a human agent. This simulator serves as a practical example of how AI and human support can be integrated effectively in a customer service environment. It demonstrates the potential for enhancing efficiency while maintaining the ability to provide personalized, human touch when necessary.# AI-Powered E-commerce Support Simulator A demonstration of how AI agents and human support can work together in an e-commerce customer service environment. This project showcases intelligent query routing, multi-agent collaboration, and seamless human integration for complex support scenarios. ## 🎯 Key Features - Multi-agent AI orchestration - Real-time and asynchronous communication modes - Integration with human support workflow - Tool-augmented AI interactions - Production-ready AWS architecture - Mock data for realistic scenarios ## 🏗️ Architecture ### Agent Architecture ![Agents](/agent-squad/ai_e-commerce_support_system.jpg) The system employs three specialized agents: #### 1. Order Management Agent (Claude 3 Sonnet) - 🎯 **Purpose**: Handles order-related inquiries - 🛠️ **Tools**: - `orderlookup`: Retrieves order details - `shipmenttracker`: Tracks shipping status - `returnprocessor`: Manages returns - ✨ **Capabilities**: - Real-time order tracking - Return processing - Refund handling #### 2. Product Information Agent (Claude 3 Haiku) - 🎯 **Purpose**: Product information and specifications - 🧠 **Knowledge Base**: Integrated product database - ✨ **Capabilities**: - Product specifications - Compatibility checking - Availability information #### 3. Human Agent - 🎯 **Purpose**: Complex case handling and oversight - ✨ **Capabilities**: - Complex complaint resolution - Critical decision oversight - AI response verification ### AWS Infrastructure ![Infrastructure](/agent-squad/ai-powered_e-commerce_support_simulator.png) #### Core Components - 🌐 **Frontend**: React + CloudFront - 🔌 **API**: AppSync GraphQL - 📨 **Messaging**: SQS queues - ⚡ **Processing**: Lambda functions - 💾 **Storage**: DynamoDB + S3 - 🔐 **Auth**: Cognito ## 💬 Communication Modes ### Real-Time Chat ![Chat Mode](/agent-squad/chat_mode.png) - Instant messaging interface - Real-time response streaming - Automatic routing ### Email-Style ![Email Mode](/agent-squad/email_mode.png) - Asynchronous communication - Template-based responses - Structured conversations ## 🛠️ Mock System Integration ### Mock Data Structure The `mock_data.json` provides realistic test data: ```json { "orders": {...}, "products": {...}, "shipping": {...} } ``` ### Tool Integration - Order management tools use mock database - Shipment tracking simulates real-time updates - Return processing demonstrates workflow ## 🚀 Deployment Guide ### Prerequisites - AWS account with permissions - AWS CLI configured - Node.js and npm - AWS CDK CLI ### Quick Start ```bash # Clone repository git clone https://github.com/awslabs/agent-squad.git cd agent-squad/examples/ecommerce-support-simulator # Install and deploy npm install cdk bootstrap cdk deploy # Create user aws cognito-idp admin-create-user \ --user-pool-id your-region_xxxxxxx \ --username your@email.com \ --user-attributes Name=email,Value=your@email.com \ --temporary-password "MyChallengingPassword" \ --message-action SUPPRESS \ --region your-region ``` ## 🔍 Demo Scenarios 1. **Order Management** - Order status inquiries - Shipment tracking - Return requests 2. **Product Support** - Product specifications - Compatibility checks - Availability queries 3. **Complex Cases** - Multi-step resolutions - Human escalation - Critical decisions ## 🧹 Cleanup ```bash cdk destroy ``` ## 🔧 Troubleshooting Common issues and solutions: 1. **Deployment Failures** - Verify AWS credentials - Check permissions - Review CloudFormation logs 2. **Runtime Issues** - Validate mock data format - Check queue configurations - Verify Lambda logs ## ⚠️ Disclaimer This demo application is intended solely for demonstration purposes. It is not designed for handling, storing, or processing any kind of Personally Identifiable Information (PII) or personal data. Users are strongly advised not to enter, upload, or use any PII or personal data within this application. Any use of PII or personal data is at the user's own risk and the developers of this application shall not be held responsible for any data breaches, misuse, or any other related issues. Please ensure that all data used in this demo is non-sensitive and anonymized. For production usage, it is crucial to implement proper security measures to protect PII and personal data. This includes obtaining proper permissions from users, utilizing encryption for data both in transit and at rest, and adhering to industry standards and regulations to maximize security. Failure to do so may result in data breaches and other serious security issues. ## 📚 Additional Resources - [Agent Squad Documentation](https://github.com/awslabs/agent-squad) - [AWS AppSync Documentation](https://docs.aws.amazon.com/appsync) - [Claude API Documentation](https://docs.anthropic.com/en/api/getting-started) Ready to build your own multi-agent chat application? Check out the complete [source code](https://github.com/awslabs/agent-squad/tree/main/examples/ecommerce-support-simulator) in our GitHub repository. ================================================ FILE: docs/src/content/docs/cookbook/examples/fast-api-streaming.md ================================================ --- title: FastAPI Streaming description: How to deploy use FastAPI Streaming with Agent Squad --- This example demonstrates how to implement streaming responses with the Agent Squad using FastAPI. It shows how to build a simple API that streams responses from multiple AI agents in real-time. ## Features - Real-time streaming responses using FastAPI's `StreamingResponse` - Custom streaming handler implementation - Multiple agent support (Tech and Health agents) - Queue-based token streaming - CORS-enabled API endpoint ## Quick Start ```bash # Install dependencies pip install "fastapi[all]" agent-squad # Run the server uvicorn app:app --reload ``` ## API Endpoint ```bash POST /stream_chat/ ``` Request body: ```json { "content": "your question here", "user_id": "user123", "session_id": "session456" } ``` ## Implementation Highlights - Uses FastAPI's event streaming capabilities - Custom callback handler for real-time token streaming - Thread-safe queue implementation for token management - Configurable orchestrator with multiple specialized agents - Error handling and proper stream closure ## Example Usage ```python import requests response = requests.post( 'http://localhost:8000/stream_chat/', json={ 'content': 'What are the latest AI trends?', 'user_id': 'user123', 'session_id': 'session456' }, stream=True ) for chunk in response.iter_content(): print(chunk.decode(), end='', flush=True) ``` Ready to build your own multi-agent chat application? Check out the complete [source code](https://github.com/awslabs/agent-squad/tree/main/examples/fast-api-streaming) in our GitHub repository. ================================================ FILE: docs/src/content/docs/cookbook/examples/ollama-agent.mdx ================================================ --- title: Ollama Agent description: A guide to creating an Ollama agent and integrating it into the Agent Squad System. --- Welcome to the Ollama Agent guide! This example will walk you through creating an Ollama agent and integrating it into your Agent Squad System. Let's dive in! ## 📚Prerequisites: - Basic knowledge of TypeScript or Python - Familiarity with the Agent Squad System - [Ollama installed](https://ollama.com/download) on your machine ## 💾 1. Ollama installation: import { Tabs, TabItem } from '@astrojs/starlight/components'; First, let's install the Ollama JavaScript library: ```bash npm install ollama ``` First, let's install the Ollama Python package: ```bash pip install ollama ``` ## 🧬 2. Create the Ollama Agent class: Now, let's create our `OllamaAgent` class. This class extends the `Agent` abstract class from the Agent Squad. The [process_request](../overview#abstract-method-processrequest) method must be implemented by the `OllamaAgent` ```typescript import { Agent, AgentOptions, ConversationMessage, ParticipantRole, Logger } from "agent-squad"; import ollama from 'ollama' export interface OllamaAgentOptions extends AgentOptions { streaming?: boolean; // Add other Ollama-specific options here (e.g., temperature, top_k, top_p) } export class OllamaAgent extends Agent { private options: OllamaAgentOptions; constructor(options: OllamaAgentOptions) { super(options); this.options = { name: options.name, description: options.description, modelId: options.modelId ?? "llama2", streaming: options.streaming ?? false }; } private async *handleStreamingResponse(messages: any[]): AsyncIterable { try { const response = await ollama.chat({ model: this.options.modelId ?? "llama2", messages: messages, stream: true, }); for await (const part of response) { yield part.message.content; } } catch (error) { Logger.logger.error("Error getting stream from Ollama model:", error); throw error; } } async processRequest( inputText: string, userId: string, sessionId: string, chatHistory: ConversationMessage[], additionalParams?: Record ): Promise> { const messages = chatHistory.map(item => ({ role: item.role, content: item.content![0].text })); messages.push({role: ParticipantRole.USER, content: inputText}); if (this.options.streaming) { return this.handleStreamingResponse(messages); } else { const response = await ollama.chat({ model: this.options.modelId!, messages: messages, }); const message: ConversationMessage = { role: ParticipantRole.ASSISTANT, content: [{text: response.message.content}] }; return message; } } } ``` ```python from typing import List, Dict, Optional, AsyncIterable, Any from agent_squad.agents import Agent, AgentOptions from agent_squad.types import ConversationMessage, ParticipantRole from agent_squad.utils import Logger import ollama from dataclasses import dataclass @dataclass class OllamaAgentOptions(AgentOptions): streaming: bool = False model_id: str = "llama2" # Add other Ollama-specific options here (e.g., temperature, top_k, top_p) class OllamaAgent(Agent): def __init__(self, options: OllamaAgentOptions): super().__init__(options) self.model_id = options.model_id self.streaming = options.streaming async def handle_streaming_response(self, messages: List[Dict[str, str]]) -> ConversationMessage: text = '' try: response = ollama.chat( model=self.model_id, messages=messages, stream=self.streaming ) for part in response: text += part['message']['content'] self.callbacks.on_llm_new_token(part['message']['content']) return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": text}] ) except Exception as error: Logger.get_logger().error("Error getting stream from Ollama model:", error) raise error async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage], additional_params: Optional[Dict[str, str]] = None ) -> ConversationMessage | AsyncIterable[Any]: messages = [ {"role": msg.role, "content": msg.content[0]['text']} for msg in chat_history ] messages.append({"role": ParticipantRole.USER.value, "content": input_text}) if self.streaming: return await self.handle_streaming_response(messages) else: response = ollama.chat( model=self.model_id, messages=messages ) return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": response['message']['content']}] ) ``` Now that we have our `OllamaAgent`, let's add it to the Agent Squad: ## 🔗 3. Add OllamaAgent to the orchestrator: If you have used the quickstarter sample program, you can add the Ollama agent and run it: ```typescript import { OllamaAgent } from "./ollamaAgent"; import { AgentSquad } from "agent-squad" const orchestrator = new AgentSquad(); // Add a text summarization agent using Ollama and Llama 2 orchestrator.addAgent( new OllamaAgent({ name: "Text Summarization Wizard", modelId: "llama2", description: "I'm your go-to agent for concise and accurate text summaries!", streaming: true }) ); ``` ```python from ollamaAgent import OllamaAgent, OllamaAgentOptions from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() # Add a text summarization agent using Ollama and Llama 2 orchestrator.add_agent( OllamaAgent(OllamaAgentOptions( name="Text Summarization Wizard", model_id="llama2", description="I'm your go-to agent for concise and accurate text summaries!", streaming=True )) ) ``` And you are done! ## 🏃 4. Run Your Ollama Model Locally: Before running your program, make sure to start the Ollama model locally: ```bash ollama run llama2 ``` If you haven't downloaded the Llama 2 model yet, it will be downloaded automatically before running. 🎉 **You're All Set!** Congratulations! You've successfully integrated an Ollama agent into your Agent Squad System. Now you can start summarizing text and leveraging the power of Llama 2 in your applications! ## 5.🔗 **Useful Links:** - [Ollama](https://ollama.com/) - [Ollama.js Documentation](https://github.com/ollama/ollama-js) - [Ollama Python](https://github.com/ollama/ollama-python) - [Ollama GitHub Repository](https://github.com/ollama) ## 6.💡 **Next Steps:** - Experiment with different Ollama models - Customize the agent's behavior by adjusting parameters - Create specialized agents for various tasks using Ollama Happy coding! 🚀 ================================================ FILE: docs/src/content/docs/cookbook/examples/ollama-classifier.mdx ================================================ --- title: Ollama classifier with llama3.1 description: Example of an Ollama classifier --- Welcome to the Ollama Classifier guide! This example will walk you through creating an Ollama classifier and integrating it into your Agent Squad System. Let’s dive in! ## 📚 Prerequisites: - Basic knowledge of Python - Familiarity with the Agent Squad System - [Ollama installed](https://ollama.com/download) on your machine ## 💾 1. Ollama installation: import { Tabs, TabItem } from '@astrojs/starlight/components'; First, let's install the Ollama Python package: ```bash pip install ollama ``` ## 🧬 2. Create the Ollama Classifier class: Now, let's create our `OllamaClassifier` class. This class extends the `Classifier` abstract class from the Agent Squad. The [process_request](../overview#abstract-method-processrequest) method must be implemented by the `OllamaClassifier` ```python from typing import List, Dict, Optional, Any from agent_squad.classifiers import Classifier, ClassifierResult from agent_squad.types import ConversationMessage, ParticipantRole from agent_squad.utils import Logger import ollama class OllamaClassifierOptions: def __init__(self, model_id: Optional[str] = None, inference_config: Optional[Dict[str, Any]] = None, host: Optional[str] = None ): self.model_id = model_id self.inference_config = inference_config or {} self.host = host class OllamaClassifier(Classifier): def __init__(self, options: OllamaClassifierOptions): super().__init__() self.model_id = options.model_id or 'llama3.1' self.inference_config = options.inference_config self.streaming = False self.temperature = options.inference_config.get('temperature', 0.0) self.client = ollama.Client(host=options.host or None) async def process_request(self, input_text: str, chat_history: List[ConversationMessage]) -> ClassifierResult: messages = [ {"role": msg.role, "content": msg.content[0]['text']} for msg in chat_history ] self.system_prompt = self.system_prompt + f'\n question: {input_text}' messages.append({"role": ParticipantRole.USER.value, "content": self.system_prompt}) try: response = self.client.chat( model=self.model_id, messages=messages, options={'temperature':self.temperature}, tools=[{ 'type': 'function', 'function': { 'name': 'analyzePrompt', 'description': 'Analyze the user input and provide structured output', 'parameters': { 'type': 'object', 'properties': { 'userinput': { 'type': 'string', 'description': 'The original user input', }, 'selected_agent': { 'type': 'string', 'description': 'The name of the selected agent', }, 'confidence': { 'type': 'number', 'description': 'Confidence level between 0 and 1', }, }, 'required': ['userinput', 'selected_agent', 'confidence'], }, } }] ) # Check if the model decided to use the provided function if not response['message'].get('tool_calls'): Logger.get_logger().info(f"The model didn't use the function. Its response was:{response['message']['content']}") raise Exception(f'Ollama model {self.model_id} did not use tools') else: tool_result = response['message'].get('tool_calls')[0].get('function', {}).get('arguments', {}) return ClassifierResult( selected_agent=self.get_agent_by_id(tool_result.get('selected_agent', None)), confidence=float(tool_result.get('confidence', 0.0)) ) except Exception as e: Logger.get_logger().error(f'Error in Ollama Classifier :{str(e)}') raise e ``` Now that we have our `OllamaClassifier`, let's use it in the Agent Squad: ## 🔗 3. Use OllamaClassifier in the orchestrator: If you have used the quickstarter sample program, you can use the Ollama classifier and run it like this: ```python from ollamaClassifier import OllamaClassifier, OllamaClassifierOptions from agent_squad.orchestrator import AgentSquad classifier = OllamaClassifier(OllamaClassifierOptions( model_id='llama3.1', inference_config={'temperature':0.0} )) # Use our newly created classifier within the orchestrator orchestrator = AgentSquad(classifier=classifier) ``` And you are done! ## 🏃 4. Run Your Ollama Model Locally: Before running your program, make sure to start the Ollama model locally: ```bash ollama run llama3.1 ``` If you haven't downloaded the Llama3.1 model yet, it will be downloaded automatically before running. 🎉 **You're All Set!** Congratulations! You've successfully integrated an Ollama classifier into your Agent Squad System. Now you can start classifiying user requests and leveraging the power of Llama3.1 in your applications! ## 5.🔗 **Useful Links:** - [Ollama](https://ollama.com/) - [Ollama Python](https://github.com/ollama/ollama-python) - [Ollama GitHub Repository](https://github.com/ollama) ## 6.💡 **Next Steps:** - Experiment with different Ollama models - Build a complete multi agent system in an offline environment Happy coding! 🚀 ================================================ FILE: docs/src/content/docs/cookbook/examples/python-local-demo.md ================================================ --- title: Python Local Demo description: How to run the Agent Squad System locally using Python --- ## Prerequisites - Python 3.12 or later - AWS account with appropriate permissions - Basic familiarity with Python async/await patterns ## Quick Setup 1. Create a new project: ```bash mkdir test_agent_squad cd test_agent_squad python -m venv venv source venv/bin/activate # On Windows use `venv\Scripts\activate` ``` 2. Install dependencies: ```bash pip install agent-squad ``` ## Implementation 1. Create a new file named `quickstart.py`: 2. Initialize the orchestrator: ```python from agent_squad.orchestrator import AgentSquad, AgentSquadConfig from agent_squad.agents import (BedrockLLMAgent, BedrockLLMAgentOptions, AgentResponse, AgentCallbacks) from agent_squad.types import ConversationMessage, ParticipantRole orchestrator = AgentSquad(options=AgentSquadConfig( LOG_AGENT_CHAT=True, LOG_CLASSIFIER_CHAT=True, LOG_CLASSIFIER_RAW_OUTPUT=True, LOG_CLASSIFIER_OUTPUT=True, LOG_EXECUTION_TIMES=True, MAX_RETRIES=3, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=True, MAX_MESSAGE_PAIRS_PER_AGENT=10 )) ``` 3. Set up agent callbacks and add an agent: ```python class BedrockLLMAgentCallbacks(AgentCallbacks): async def on_llm_new_token(self, token: str) -> None: # handle response streaming here print(token, end='', flush=True) tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", streaming=True, description="Specializes in technology areas including software development, hardware, AI, \ cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs \ related to technology products and services.", model_id="anthropic.claude-3-sonnet-20240229-v1:0", callbacks=BedrockLLMAgentCallbacks() )) orchestrator.add_agent(tech_agent) ``` 4. Implement the main logic: ```python async def handle_request(_orchestrator: AgentSquad, _user_input: str, _user_id: str, _session_id: str): response: AgentResponse = await _orchestrator.route_request(_user_input, _user_id, _session_id) print("\nMetadata:") print(f"Selected Agent: {response.metadata.agent_name}") if response.streaming: print('Response:', response.output.content[0]['text']) else: print('Response:', response.output.content[0]['text']) if __name__ == "__main__": USER_ID = "user123" SESSION_ID = str(uuid.uuid4()) print("Welcome to the interactive Multi-Agent system. Type 'quit' to exit.") while True: user_input = input("\nYou: ").strip() if user_input.lower() == 'quit': print("Exiting the program. Goodbye!") sys.exit() asyncio.run(handle_request(orchestrator, user_input, USER_ID, SESSION_ID)) ``` 5. Run the application: ```bash python quickstart.py ``` ## Implementation Notes - Implements streaming responses by default - Uses default Bedrock Classifier with `anthropic.claude-3-5-sonnet-20240620-v1:0` - Includes interactive command-line interface - Handles session management with UUID generation ## Next Steps - Add additional specialized agents - Implement persistent storage - Customize the classifier configuration - Add error handling and retry logic Ready to build your own multi-agent chat application? Check out the complete [source code](https://github.com/awslabs/agent-squad/tree/main/examples/python-demo) in our GitHub repository. ================================================ FILE: docs/src/content/docs/cookbook/examples/typescript-local-demo.md ================================================ --- title: TypeScript Local Demo description: How to run the Agent Squad System locally using TypeScript --- ## Prerequisites - Node.js and npm installed - AWS account with appropriate permissions - Basic familiarity with TypeScript and async/await patterns ## Quick Setup 1. Create a new project: ```bash mkdir test_agent_squad cd test_agent_squad npm init ``` 2. Install dependencies: ```bash npm install agent-squad ``` ## Implementation 1. Create a new file named `quickstart.ts`: 2. Initialize the orchestrator: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad({ config: { LOG_AGENT_CHAT: true, LOG_CLASSIFIER_CHAT: true, LOG_CLASSIFIER_RAW_OUTPUT: false, LOG_CLASSIFIER_OUTPUT: true, LOG_EXECUTION_TIMES: true, } }); ``` 3. Add specialized agents: ```typescript import { BedrockLLMAgent } from "agent-squad"; orchestrator.addAgent( new BedrockLLMAgent({ name: "Tech Agent", description: "Specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.", }) ); orchestrator.addAgent( new BedrockLLMAgent({ name: "Health Agent", description: "Focuses on health and medical topics such as general wellness, nutrition, diseases, treatments, mental health, fitness, healthcare systems, and medical terminology or concepts.", }) ); ``` 4. Implement the main logic: ```typescript const userId = "quickstart-user"; const sessionId = "quickstart-session"; const query = "What are the latest trends in AI?"; console.log(`\nUser Query: ${query}`); async function main() { try { const response = await orchestrator.routeRequest(query, userId, sessionId); console.log("\n** RESPONSE ** \n"); console.log(`> Agent ID: ${response.metadata.agentId}`); console.log(`> Agent Name: ${response.metadata.agentName}`); console.log(`> User Input: ${response.metadata.userInput}`); console.log(`> User ID: ${response.metadata.userId}`); console.log(`> Session ID: ${response.metadata.sessionId}`); console.log(`> Additional Parameters:`, response.metadata.additionalParams); console.log(`\n> Response: ${response.output}`); } catch (error) { console.error("An error occurred:", error); } } main(); ``` 5. Run the application: ```bash npx ts-node quickstart.ts ``` ## Implementation Notes - Uses default Bedrock Classifier with `anthropic.claude-3-5-sonnet-20240620-v1:0` - Utilizes Bedrock LLM Agent with `anthropic.claude-3-haiku-20240307-v1:0` - Implements in-memory storage by default ## Next Steps - Add additional specialized agents - Implement persistent storage with DynamoDB - Add custom error handling - Implement streaming responses Ready to build your own multi-agent chat application? Check out the complete [source code](https://github.com/awslabs/agent-squad/tree/main/examples/local-demo) in our GitHub repository. ================================================ FILE: docs/src/content/docs/cookbook/lambda/aws-lambda-nodejs.md ================================================ --- title: AWS Lambda NodeJs with Agent Squad description: How to set up the Agent Squad System for AWS Lambda using JavaScript --- The Agent Squad framework can be used inside an AWS Lambda function like any other library. This guide outlines the process of setting up the Agent Squad System for use with AWS Lambda using JavaScript. ## Prerequisites - AWS account with appropriate permissions - Node.js and npm installed - Basic familiarity with AWS Lambda and JavaScript ## Installation and Setup 1. **Create a New Project Directory** ```bash mkdir multi-agent-lambda && cd multi-agent-lambda ``` 2. **Initialize a New Node.js Project** ```bash npm init -y ``` 3. **Install the Agent Squad framework** ```bash npm install agent-squad ``` ## Lambda Function Structure Create a new file named `lambda.js` in your project directory. Here's a high-level overview of what your Lambda function should include: ```javascript const { AgentSquad, BedrockLLMAgent } = require("agent-squad"); // Initialize the orchestrator const orchestrator = new AgentSquad({ // Configuration options }); // Add agents to the orchestrator orchestrator.addAgent(new BedrockLLMAgent({ // Agent configuration })); // Lambda handler function exports.handler = async (event, context) => { try { const { query, userId, sessionId } = event; const response = await orchestrator.routeRequest(query, userId, sessionId); return response; } catch (error) { console.error('Error:', error); return { statusCode: 500, body: JSON.stringify({ error: "Internal Server Error" }) }; } }; ``` Customize the orchestrator configuration and agent setup according to your specific requirements. ## Deployment Use your preferred method to deploy the Lambda function (e.g., AWS CDK, Terraform, Serverless Framework, AWS SAM, or manual deployment through AWS Console). ## IAM Permissions Ensure your Lambda function's execution role has permissions to: - Invoke Amazon Bedrock models - Write to CloudWatch Logs ================================================ FILE: docs/src/content/docs/cookbook/lambda/aws-lambda-python.md ================================================ --- title: AWS Lambda Python with Agent Squad description: How to set up the Agent Squad System for AWS Lambda using Python --- The Agent Squad framework can be used inside an AWS Lambda function like any other library. This guide outlines the process of setting up the Agent Squad System for use with AWS Lambda using Python. ## Prerequisites - AWS account with appropriate permissions - Python 3.12 or later installed - Basic familiarity with AWS Lambda and Python ## Installation and Setup 1. **Create a New Project Directory** ```bash mkdir multi-agent-lambda && cd multi-agent-lambda ``` 2. **Create and Activate a Virtual Environment** ```bash python -m venv venv source venv/bin/activate # On Windows, use `venv\Scripts\activate` ``` 3. **Install the Agent Squad framework** ```bash pip install agent-squad boto3 ``` 4. **Create Requirements File** ```bash pip freeze > requirements.txt ``` ## Lambda Function Structure Create a new file named `lambda_function.py` in your project directory. Here's a high-level overview of what your Lambda function should include: ```python import json import asyncio from typing import Dict, Any from agent_squad.orchestrator import AgentSquad, AgentSquadConfig from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions, AgentResponse from agent_squad.types import ConversationMessage # Initialize orchestrator orchestrator = AgentSquad(AgentSquadConfig( # Configuration options )) # Add agents e.g Tech Agent tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", streaming=False, description="Specializes in technology areas including software development, hardware, AI, \ cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs \ related to technology products and services.", model_id="anthropic.claude-3-sonnet-20240229-v1:0", )) orchestrator.add_agent(tech_agent) def serialize_agent_response(response: Any) -> Dict[str, Any]: text_response = '' if isinstance(response, AgentResponse) and response.streaming is False: # Handle regular response if isinstance(response.output, str): text_response = response.output elif isinstance(response.output, ConversationMessage): text_response = response.output.content[0].get('text') """Convert AgentResponse into a JSON-serializable dictionary.""" return { "metadata": { "agent_id": response.metadata.agent_id, "agent_name": response.metadata.agent_name, "user_input": response.metadata.user_input, "session_id": response.metadata.session_id, }, "output": text_response, "streaming": response.streaming, } def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]: try: user_input = event.get('query') user_id = event.get('userId') session_id = event.get('sessionId') response = asyncio.run(orchestrator.route_request(user_input, user_id, session_id)) # Serialize the AgentResponse to a JSON-compatible format serialized_response = serialize_agent_response(response) return { "statusCode": 200, "body": json.dumps(serialized_response) } except Exception as e: print(f"Error: {str(e)}") return { "statusCode": 500, "body": json.dumps({"error": "Internal Server Error"}) } ``` Customize the orchestrator configuration and agent setup according to your specific requirements. ## Deployment Use your preferred method to deploy the Lambda function (e.g., AWS CDK, Terraform, Serverless Framework, AWS SAM, or manual deployment through AWS Console). ## IAM Permissions Ensure your Lambda function's execution role has permissions to: - Invoke Amazon Bedrock models - Write to CloudWatch Logs ================================================ FILE: docs/src/content/docs/cookbook/monitoring/agent-overlap.md ================================================ --- title: Agent Overlap Analysis description: Understanding and using the Agent Overlap Analysis feature in the Agent Squad framework --- Agent Overlap Analysis is a feature of the Agent Squad framework designed to optimize agent configurations by analyzing the descriptions of your agents. This tool helps identify similarities, potential conflicts, and the uniqueness of each agent's role within the system. The core idea behind Agent Overlap Analysis is to quantitatively assess how similar or different your agents are based on their descriptions. This analysis helps in: 1. Identifying redundancies in agent roles 2. Detecting potential conflicts where agents might have overlapping responsibilities 3. Ensuring each agent has a distinct purpose within the system 4. Optimizing the overall efficiency of your multi-agent setup ## How It Works The Agent Overlap Analysis uses natural language processing and information retrieval techniques to compare agent descriptions: 1. **Text Preprocessing**: Agent descriptions are tokenized and stopwords are removed to focus on meaningful content. 2. **TF-IDF Calculation**: Term Frequency-Inverse Document Frequency (TF-IDF) is [computed](https://naturalnode.github.io/natural/tfidf.html) for each agent's description. This weighs the importance of words in the context of all agent descriptions. 3. **Pairwise Comparison**: Each agent's description is compared with every other agent's description using cosine similarity of their TF-IDF vectors. This provides a measure of how similar any two agents are. 4. **Uniqueness Scoring**: A uniqueness score is calculated for each agent based on its average dissimilarity from all other agents. ## Implementation Details The `AgentOverlapAnalyzer` class is the core of this feature. Here's a breakdown of its main components: - `constructor(agents)`: Initializes the analyzer with a dictionary of agents, where each agent has a name and description. - `analyzeOverlap()`: The main method that performs the analysis and outputs the results. - `calculateCosineSimilarity(terms1, terms2)`: A helper method that calculates the cosine similarity between two sets of TF-IDF terms. ## Using Agent Overlap Analysis Install the framework ```bash npm install agent-squad ``` To use the Agent Overlap Analysis feature: ```typescript import { AgentOverlapAnalyzer } from "agent-squad"; const agents = { finance: { name: "Finance Agent", description: "Handles financial queries and calculations" }, tech: { name: "Tech Support", description: "Provides technical support and troubleshooting" }, hr: { name: "HR Assistant", description: "Assists with human resources tasks and queries" } }; const analyzer = new AgentOverlapAnalyzer(agents); analyzer.analyzeOverlap(); ``` ## Understanding the Results The analysis provides two main types of results: ### 1. Pairwise Overlap Results For each pair of agents, you'll see: - **Overlap Percentage**: How similar their descriptions are (higher percentage means more overlap). - **Potential Conflict**: Categorized as "High", "Medium", or "Low" based on the overlap percentage. ### 2. Uniqueness Scores For each agent, you'll see a uniqueness score indicating how distinct its role is within the system. ## Example Output Here's an example of what the output might look like: ``` Pairwise Overlap Results: _________________________ finance - tech: - Overlap Percentage - 15.23% - Potential Conflict - Medium finance - hr: - Overlap Percentage - 8.75% - Potential Conflict - Low tech - hr: - Overlap Percentage - 12.10% - Potential Conflict - Medium Uniqueness Scores: _________________ Agent: finance, Uniqueness Score: 89.55% Agent: tech, Uniqueness Score: 86.32% Agent: hr, Uniqueness Score: 91.20% ``` ## Interpreting and Acting on Results - **High Overlap (>30%) / Low Uniqueness**: Consider refining agent descriptions to create clearer distinctions between their roles. - **Medium Overlap (10-30%)**: Some overlap can be acceptable, especially for related domains. Use your judgment to decide if refinement is needed. - **Low Overlap (<10%) / High Uniqueness**: This generally indicates well-differentiated agents, but ensure the agents still cover all necessary domains. ## Best Practices 1. **Run Analysis Regularly**: Perform this analysis whenever you add new agents or modify existing agent descriptions. 2. **Iterative Refinement**: Use the results to refine your agent descriptions, then re-run the analysis to see the impact of your changes. 3. **Balance Specificity and Coverage**: Aim for agent descriptions that are specific enough to be unique but broad enough to cover their intended domain. 4. **Consider Context**: Remember that some overlap might be necessary or beneficial, depending on your use case. ## Limitations - The analysis is based solely on textual descriptions. It doesn't account for the actual functionality or implementation of the agents. - Very short or overly generic descriptions may lead to less meaningful results. - The effectiveness of the analysis depends on the quality and specificity of the agent descriptions. By leveraging the Agent Overlap Analysis feature, you can continuously refine and optimize your agents, ensuring each agent has a clear, distinct purpose while collectively covering all necessary domains of expertise. ================================================ FILE: docs/src/content/docs/cookbook/monitoring/logging.mdx ================================================ --- title: Logging in Agent Squad description: Understanding how to use a custom logger in Agent Squad --- The Agent Squad provides flexible logging capabilities that can be customized to suit your needs. This document explains how logging works in the orchestrator and how you can configure it. ## Default Logging Behavior By default, the orchestrator uses `console.log` for logging. This means that all log messages will be printed to the console without any additional configuration. ## Customizing the Logger The orchestrator allows you to override the default logger with a custom logging solution. This is done through the `OrchestratorOptions` interface: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript export interface OrchestratorOptions { storage?: ChatStorage; config?: Partial; logger?: any; } ``` ```python // TODO: Add python code here ``` You can provide your own logger implementation to the `logger` property when initializing the `AgentSquad`. ## Example: Using AWS Lambda Powertools for Logging Here's an example of how to use AWS Lambda Powertools for logging with the Agent Squad: 1. First, install the AWS Lambda Powertools package: ```bash npm install @aws-lambda-powertools/logger ``` ```python // TODO: Add python code here ``` 2. Import and initialize the Logger from AWS Lambda Powertools: ```typescript import { Logger } from "@aws-lambda-powertools/logger"; const logger = new Logger({ logLevel: "INFO", serviceName: "MyOrchestratorService" }); ``` ```python // TODO: Add python code here ``` 3. Create the orchestrator instance with the custom logger: ```typescript const orchestrator = new AgentSquad({ storage: storage, config: { LOG_AGENT_CHAT: true, LOG_CLASSIFIER_CHAT: true, LOG_CLASSIFIER_RAW_OUTPUT: true, LOG_CLASSIFIER_OUTPUT: true, LOG_EXECUTION_TIMES: true, }, logger: logger, }); ``` ```python // TODO: Add python code here ``` In this example, we're using the AWS Lambda Powertools Logger and configuring various logging options for the orchestrator. ## Logging Configuration Options The `config` object in `OrchestratorOptions` allows you to fine-tune what information is logged: - `LOG_AGENT_CHAT`: Logs the chat interactions with agents - `LOG_CLASSIFIER_CHAT`: Logs the chat interactions with the classifier - `LOG_CLASSIFIER_RAW_OUTPUT`: Logs the raw output from the classifier - `LOG_CLASSIFIER_OUTPUT`: Logs the processed output from the classifier - `LOG_EXECUTION_TIMES`: Logs the execution times of various operations By setting these options to `true` or `false`, you can control the verbosity of the logging to suit your needs. ## Best Practices 1. In production environments, consider using a robust logging solution like AWS CloudWatch Logs or a centralized logging service. 2. Be mindful of sensitive information in logs, especially when logging chat contents. 3. Use appropriate log levels (e.g., INFO, DEBUG, ERROR) to categorize your log messages. 4. Monitor your logs regularly to track the performance and behavior of your orchestrator. By leveraging these logging capabilities, you can gain valuable insights into the operation of your Agent Squad and more easily diagnose any issues that may arise. ================================================ FILE: docs/src/content/docs/cookbook/monitoring/observability.mdx ================================================ --- title: Observability with Callbacks description: Learn how to implement comprehensive observability for Agent Squad using callbacks --- import { Tabs, TabItem } from '@astrojs/starlight/components'; Agent Squad provides powerful observability capabilities through a comprehensive callback system that allows you to track, monitor, and analyze the behavior of your multi-agent system. This guide covers the callback system and demonstrates how to integrate with Langfuse for advanced observability. ## Callbacks System Overview The Agent Squad framework implements three main types of callbacks to provide complete visibility into your system: - **Agent Callbacks**: Track agent lifecycle, execution, and LLM interactions - **Classifier Callbacks**: Monitor request classification and routing decisions - **Tool Callbacks**: Observe tool usage and execution ### Agent Callbacks Agent callbacks provide hooks into the agent execution lifecycle: ```python from agent_squad.agents import AgentCallbacks from typing import Optional, Any, UUID class CustomAgentCallbacks(AgentCallbacks): async def on_agent_start( self, agent_name: str, input: Any, messages: list[Any], run_id: Optional[UUID] = None, tags: Optional[list[str]] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> dict: """Called when an agent starts processing""" print(f"Agent {agent_name} starting with input: {input}") return {"start_time": time.time()} async def on_agent_end( self, agent_name: str, response: Any, messages: list[Any], run_id: Optional[UUID] = None, tags: Optional[list[str]] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: """Called when an agent completes processing""" print(f"Agent {agent_name} completed") async def on_llm_start( self, name: str, input: Any, run_id: Optional[UUID] = None, tags: Optional[list[str]] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: """Called when LLM processing starts""" print(f"LLM {name} starting") async def on_llm_end( self, name: str, output: Any, run_id: Optional[UUID] = None, tags: Optional[list[str]] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: """Called when LLM processing ends""" print(f"LLM {name} completed") async def on_llm_new_token( self, token: str, **kwargs: Any ) -> None: """Called for each new token in streaming responses""" print(f"New token: {token}") ``` ```typescript // TypeScript callback implementation coming soon ``` ### Classifier Callbacks Monitor request classification and routing decisions: ```python from agent_squad.classifiers import ClassifierCallbacks, ClassifierResult from typing import Optional, Any, UUID class CustomClassifierCallbacks(ClassifierCallbacks): async def on_classifier_start( self, name: str, input: Any, run_id: Optional[UUID] = None, tags: Optional[list[str]] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: """Called when classification starts""" print(f"Classifier {name} analyzing: {input}") async def on_classifier_stop( self, name: str, output: ClassifierResult, run_id: Optional[UUID] = None, tags: Optional[list[str]] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: """Called when classification completes""" selected_agent = output.selected_agent.name if output.selected_agent else "None" print(f"Classifier selected: {selected_agent} with confidence: {output.confidence}") ``` ```typescript // TypeScript callback implementation coming soon ``` ### Tool Callbacks Track tool execution and performance: ```python from agent_squad.utils import AgentToolCallbacks from typing import Optional, Any, UUID class CustomToolCallbacks(AgentToolCallbacks): async def on_tool_start( self, tool_name: str, input: Any, run_id: Optional[UUID] = None, tags: Optional[list[str]] = None, metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: """Called when tool execution starts""" print(f"Tool {tool_name} executing with input: {input}") async def on_tool_end( self, tool_name: str, input: Any, output: dict, run_id: Optional[UUID] = None, **kwargs: Any, ) -> Any: """Called when tool execution completes""" print(f"Tool {tool_name} completed with output: {output}") ``` ```typescript // TypeScript callback implementation coming soon ``` ## Langfuse Integration Demo The [Langfuse demo](https://github.com/awslabs/agent-squad/tree/main/examples/langfuse-demo) provides a comprehensive example of implementing observability with Langfuse, a powerful open-source observability platform for LLM applications. ### Features Demonstrated The Langfuse demo showcases: - **Complete conversation tracing** - Track entire user sessions from start to finish - **Agent classification monitoring** - See which agents are selected and why - **LLM usage tracking** - Monitor token consumption, costs, and response times - **Tool execution visibility** - Observe tool calls and their outcomes - **Performance analytics** - Analyze bottlenecks and optimization opportunities ### Setup and Configuration 1. **Install Dependencies**: ```bash cd examples/langfuse-demo pip install -r requirements.txt ``` 2. **Configure Environment**: ```bash # .env file LANGFUSE_PUBLIC_KEY=your_langfuse_public_key LANGFUSE_SECRET_KEY=your_langfuse_secret_key LANGFUSE_HOST=https://cloud.langfuse.com AWS_ACCESS_KEY_ID=your_aws_access_key AWS_SECRET_ACCESS_KEY=your_aws_secret_key AWS_DEFAULT_REGION=your_aws_region ``` ### Implementation Example Here's how the Langfuse demo implements comprehensive observability: ```python from langfuse.decorators import observe, langfuse_context from langfuse import Langfuse from datetime import datetime, timezone # Initialize Langfuse langfuse = Langfuse() class LangfuseAgentCallbacks(AgentCallbacks): async def on_agent_start(self, agent_name, payload_input, messages, **kwargs): """Track agent execution start""" langfuse_context.update_current_observation( input=payload_input, start_time=datetime.now(timezone.utc), name=agent_name, tags=kwargs.get('tags'), metadata=kwargs.get('metadata') ) async def on_agent_end(self, agent_name, response, messages, **kwargs): """Track agent execution completion""" langfuse_context.update_current_observation( end_time=datetime.now(timezone.utc), name=agent_name, output=response, user_id=kwargs.get('user_id'), session_id=kwargs.get('session_id') ) @observe(as_type='generation', capture_input=False) async def on_llm_end(self, name, output, **kwargs): """Track LLM generation with detailed metrics""" input_data = kwargs.get('payload_input', {}) messages = [{'role': 'system', 'content': input_data.get('system')}] messages.extend(input_data.get('messages', [])) langfuse_context.update_current_observation( name=name, input=messages, output=output, model=input_data.get('modelId'), model_parameters=kwargs.get('inferenceConfig'), usage={ 'input': kwargs.get('usage', {}).get('inputTokens'), 'output': kwargs.get('usage', {}).get('outputTokens'), 'total': kwargs.get('usage', {}).get('totalTokens') } ) class LangfuseClassifierCallbacks(ClassifierCallbacks): async def on_classifier_start(self, name, payload_input, **kwargs): """Track classification start""" inputs = [ {'role': 'system', 'content': kwargs.get('system')}, {'role': 'user', 'content': payload_input} ] langfuse_context.update_current_observation( name=name, start_time=datetime.now(timezone.utc), input=inputs, model=kwargs.get('modelId'), model_parameters=kwargs.get('inferenceConfig') ) async def on_classifier_stop(self, name, output, **kwargs): """Track classification results""" langfuse_context.update_current_observation( output={ 'role': 'assistant', 'content': { 'selected_agent': output.selected_agent.name if output.selected_agent else 'None', 'confidence': output.confidence } }, end_time=datetime.now(timezone.utc), usage={ 'input': kwargs.get('usage', {}).get('inputTokens'), 'output': kwargs.get('usage', {}).get('outputTokens'), 'total': kwargs.get('usage', {}).get('totalTokens') } ) @observe(as_type="generation", name="conversation") def run_conversation(): """Main conversation loop with full tracing""" # Your orchestrator setup and conversation handling pass ``` ```typescript // TypeScript Langfuse integration coming soon ``` ### Tracing Structure The Langfuse integration creates a hierarchical trace structure: ``` Conversation (Generation) ├── Classification (Generation) │ └── Classifier LLM Call (Generation) ├── Agent Processing (Span) │ ├── Agent LLM Call (Generation) │ └── Tool Calls (Spans) │ ├── Tool Execution (Span) │ └── Tool Results (Span) └── Response Assembly (Span) ``` ### Trace Visualization Here's what a complete trace looks like in the Langfuse dashboard: ![Langfuse Trace Example](/agent-squad/langfuse-trace.png) This trace shows the complete flow of a user request, including classification, agent selection, LLM calls, and tool execution, with detailed timing and token usage information. ### Analytics and Insights With Langfuse integration, you can analyze: - **Agent Usage Patterns**: Which agents are most frequently selected - **Classification Accuracy**: How well the classifier routes requests - **Performance Metrics**: Response times, token usage, and costs - **Error Tracking**: Failed requests and their causes - **User Behavior**: Session patterns and conversation flows ## Best Practices ### 1. Implement Comprehensive Callbacks ```python class ProductionCallbacks(AgentCallbacks): def __init__(self, logger, metrics_client): self.logger = logger self.metrics = metrics_client async def on_agent_start(self, agent_name, **kwargs): # Log structured data self.logger.info("agent_start", extra={ "agent_name": agent_name, "user_id": kwargs.get("user_id"), "session_id": kwargs.get("session_id") }) # Send metrics self.metrics.increment("agent.invocations", tags=[f"agent:{agent_name}"]) async def on_llm_end(self, name, output, **kwargs): # Track token usage usage = kwargs.get('usage', {}) self.metrics.gauge("llm.tokens.input", usage.get('inputTokens', 0)) self.metrics.gauge("llm.tokens.output", usage.get('outputTokens', 0)) ``` ### 2. Handle Errors Gracefully ```python class RobustCallbacks(AgentCallbacks): async def on_agent_start(self, **kwargs): try: # Your observability logic pass except Exception as e: # Never let observability break your application logging.error(f"Callback error: {e}") ``` ### 3. Use Sampling for High-Volume Applications ```python import random class SampledCallbacks(AgentCallbacks): def __init__(self, sample_rate=0.1): self.sample_rate = sample_rate async def on_agent_start(self, **kwargs): if random.random() < self.sample_rate: # Only trace a percentage of requests await self.full_trace(**kwargs) ``` ### 4. Correlate Across Services ```python class CorrelatedCallbacks(AgentCallbacks): async def on_agent_start(self, **kwargs): # Propagate trace context across service boundaries trace_id = kwargs.get('trace_id') or generate_trace_id() self.set_trace_context(trace_id) ``` ## Integration with Other Observability Tools The callback system is designed to work with various observability platforms: ### OpenTelemetry ```python from opentelemetry import trace class OTelCallbacks(AgentCallbacks): def __init__(self): self.tracer = trace.get_tracer(__name__) async def on_agent_start(self, agent_name, **kwargs): with self.tracer.start_as_current_span(f"agent_{agent_name}") as span: span.set_attribute("agent.name", agent_name) span.set_attribute("user.id", kwargs.get("user_id")) ``` ### DataDog ```python from ddtrace import tracer class DataDogCallbacks(AgentCallbacks): async def on_agent_start(self, agent_name, **kwargs): with tracer.trace("agent.process", service="agent-squad") as span: span.set_tag("agent.name", agent_name) span.set_tag("user.id", kwargs.get("user_id")) ``` ### Custom Metrics ```python import time from prometheus_client import Counter, Histogram AGENT_INVOCATIONS = Counter('agent_invocations_total', 'Agent invocations', ['agent_name']) AGENT_DURATION = Histogram('agent_duration_seconds', 'Agent processing time', ['agent_name']) class MetricsCallbacks(AgentCallbacks): async def on_agent_start(self, agent_name, **kwargs): AGENT_INVOCATIONS.labels(agent_name=agent_name).inc() kwargs['start_time'] = time.time() async def on_agent_end(self, agent_name, **kwargs): duration = time.time() - kwargs.get('start_time', 0) AGENT_DURATION.labels(agent_name=agent_name).observe(duration) ``` ## Running the Langfuse Demo To see the observability system in action: 1. **Start the demo**: ```bash cd examples/langfuse-demo python main.py ``` 2. **Interact with the system**: ``` You: What's the weather in San Francisco? You: Tell me about AI trends You: How to improve my sleep? ``` 3. **View traces in Langfuse**: - Navigate to your Langfuse dashboard - Explore conversation traces - Analyze agent selection patterns - Monitor performance metrics The demo provides a complete template for implementing production-ready observability in your Agent Squad applications. ================================================ FILE: docs/src/content/docs/cookbook/patterns/cost-efficient.md ================================================ --- title: Cost-Efficient Routing Pattern description: Cost-Efficient Routing Pattern using the Agent Squad framework --- The Agent Squad can intelligently route queries to the most cost-effective agent based on task complexity, optimizing resource utilization and reducing operational costs. ## How It Works 1. **Task Complexity Analysis** - The classifier assesses incoming query complexity - Considers factors like required expertise, computational intensity, and expected response time - Makes routing decisions based on task requirements 2. **Agent Cost Tiers** - Agents are categorized into different cost tiers: - Low-cost: General-purpose models for simple tasks - Mid-tier: Balanced performance and cost - High-cost: Specialized expert models for complex tasks 3. **Dynamic Routing** - Simple queries route to cheaper models - Complex tasks route to specialized agents - Automatic routing based on query analysis ## Implementation Example ```typescript // Configure low-cost agent for simple queries const basicAgent = new BedrockLLMAgent({ name: "Basic Agent", modelId: "mistral.mistral-small-2402-v1:0", description: "Handles simple queries and basic information retrieval", streaming: true, inferenceConfig: { temperature: 0.0 } }); // Configure expert agent for complex tasks const expertAgent = new BedrockLLMAgent({ name: "Expert Agent", modelId: "anthropic.claude-3-sonnet-20240229-v1:0", description: "Handles complex analysis and specialized tasks", streaming: true, inferenceConfig: { temperature: 0.0 } }); // Add agents to orchestrator orchestrator.addAgent(basicAgent); orchestrator.addAgent(expertAgent); ``` ## Benefits - Optimal resource utilization - Cost reduction for simple tasks - Improved response quality for complex queries - Efficient scaling based on query complexity ================================================ FILE: docs/src/content/docs/cookbook/patterns/multi-lingual.md ================================================ --- title: Multi-lingual Routing Pattern description: Multi-lingual Routing Pattern using the Agent Squad framework --- By integrating language-specific agents, the Agent Squad can provide multi-lingual support, enabling users to interact with the system in their preferred language while maintaining consistent experiences. ## Key Components 1. **Language Detection** - Classifier identifies query language - Routes to appropriate language-specific agent - Maintains context across languages 2. **Language-Specific Agents** - Dedicated agents for each supported language - Specialized in language-specific responses - Consistent response quality across languages 3. **Dynamic Language Routing** - Automatic routing based on detected language - Seamless language switching - Maintains conversation context ## Implementation Example ```typescript // French language agent orchestrator.addAgent( new BedrockLLMAgent({ name: "Text Summarization Agent for French Language", modelId: "anthropic.claude-3-haiku-20240307-v1:0", description: "This is a very simple text summarization agent for french language.", streaming: true, inferenceConfig: { temperature: 0.0, }, }) ); // English language agent orchestrator.addAgent( new BedrockLLMAgent({ name: "Text Summarization Agent English Language", modelId: "mistral.mistral-small-2402-v1:0", description: "This is a very simple text summarization agent for english language.", streaming: true, inferenceConfig: { temperature: 0.0, } }) ); ``` ## Implementation Notes - Models shown are for illustration - Any suitable LLM can be substituted - Principle remains consistent across different models - Configure based on language-specific requirements ## Benefits - Native language support - Consistent user experience - Scalable language coverage - Maintainable language-specific logic ================================================ FILE: docs/src/content/docs/cookbook/tools/math-operations.md ================================================ --- title: Creating a Math Agent with BedrockLLMAgent and Custom Tools description: Understanding Tool use in Bedrock LLM Agent --- This guide demonstrates how to create a specialized math agent using BedrockLLMAgent and custom math tools. We'll walk through the process of defining the tools, setting up the agent, and integrating it into your Agent Squad system.
1. **Define the Math Tools** Let's break down the math tool definition into its key components: A. Tool Descriptions ```typescript export const mathAgentToolDefinition = [ { toolSpec: { name: "perform_math_operation", description: "Perform a mathematical operation. This tool supports basic arithmetic and various mathematical functions.", inputSchema: { json: { type: "object", properties: { operation: { type: "string", description: "The mathematical operation to perform. Supported operations include:\n" + "- Basic arithmetic: 'add', 'subtract', 'multiply', 'divide'\n" + "- Exponentiation: 'power'\n" + "- Trigonometric: 'sin', 'cos', 'tan'\n" + "- Logarithmic and exponential: 'log', 'exp'\n" + "- Rounding: 'round', 'floor', 'ceil'\n" + "- Other: 'sqrt', 'abs'", }, args: { type: "array", items: { type: "number" }, description: "The arguments for the operation.", }, }, required: ["operation", "args"], }, }, }, }, { toolSpec: { name: "perform_statistical_calculation", description: "Perform statistical calculations on a set of numbers.", inputSchema: { json: { type: "object", properties: { operation: { type: "string", description: "The statistical operation to perform. Supported operations include:\n" + "'mean', 'median', 'mode', 'variance', 'stddev'", }, args: { type: "array", items: { type: "number" }, description: "The set of numbers to perform the statistical operation on.", }, }, required: ["operation", "args"], }, }, }, }, ]; ``` **Explanation:** - This defines two tools: `perform_math_operation` and `perform_statistical_calculation`. - Each tool has a name, description, and input schema. - The input schema specifies the required parameters (operation and arguments) for each tool.

B. Tool Handler ```typescript import { ConversationMessage, ParticipantRole } from "agent-squad"; export async function mathToolHandler(response, conversation: ConversationMessage[]): Promise { const responseContentBlocks = response.content as any[]; let toolResults: any = []; if (!responseContentBlocks) { throw new Error("No content blocks in response"); } for (const contentBlock of response.content) { if ("toolUse" in contentBlock) { const toolUseBlock = contentBlock.toolUse; const toolUseName = toolUseBlock.name; if (toolUseName === "perform_math_operation") { const result = executeMathOperation(toolUseBlock.input.operation, toolUseBlock.input.args); // Process and add result to toolResults } else if (toolUseName === "perform_statistical_calculation") { const result = calculateStatistics(toolUseBlock.input.operation, toolUseBlock.input.args); // Process and add result to toolResults } } } const message: ConversationMessage = { role: ParticipantRole.USER, content: toolResults }; return messages; } ``` **Explanation:** - This handler processes the LLM's requests to use the math tools. - It iterates through the response content, looking for tool use blocks. - When it finds a tool use, it calls the appropriate function (`executeMathOperation` or `calculateStatistics`). - It formats the results and adds them to the conversation as a new user message.

C. Math Operation and Statistical Calculation Functions ```typescript /** * Executes a mathematical operation using JavaScript's Math library. * @param operation - The mathematical operation to perform. * @param args - Array of numbers representing the arguments for the operation. * @returns An object containing either the result of the operation or an error message. */ function executeMathOperation( operation: string, args: number[] ): { result: number } | { error: string } { const safeEval = (code: string) => { return Function('"use strict";return (' + code + ")")(); }; try { let result: number; switch (operation.toLowerCase()) { case 'add': case 'addition': result = args.reduce((sum, current) => sum + current, 0); break; case 'subtract': case 'subtraction': if (args.length !== 2) { throw new Error('Subtraction requires exactly two arguments'); } result = args[0] - args[1]; break; case 'multiply': case 'multiplication': result = args.reduce((product, current) => product * current, 1); break; case 'divide': case 'division': if (args.length !== 2) { throw new Error('Division requires exactly two arguments'); } if (args[1] === 0) { throw new Error('Division by zero'); } result = args[0] / args[1]; break; case 'power': case 'exponent': if (args.length !== 2) { throw new Error('Power operation requires exactly two arguments'); } result = Math.pow(args[0], args[1]); break; default: // For other operations, use the Math object if the function exists if (typeof Math[operation] === 'function') { result = safeEval(`Math.${operation}(${args.join(",")})`); } else { throw new Error(`Unsupported operation: ${operation}`); } } return { result }; } catch (error) { return { error: `Error executing ${operation}: ${(error as Error).message}`, }; } } function calculateStatistics(operation: string, args: number[]): { result: number } | { error: string } { try { switch (operation.toLowerCase()) { case 'mean': return { result: args.reduce((sum, num) => sum + num, 0) / args.length }; case 'median': { const sorted = args.slice().sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return { result: sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2, }; } case 'mode': { const counts = args.reduce((acc, num) => { acc[num] = (acc[num] || 0) + 1; return acc; }, {} as Record); const maxCount = Math.max(...Object.values(counts)); const modes = Object.keys(counts).filter(key => counts[Number(key)] === maxCount); return { result: Number(modes[0]) }; // Return first mode if there are multiple } case 'variance': { const mean = args.reduce((sum, num) => sum + num, 0) / args.length; const squareDiffs = args.map(num => Math.pow(num - mean, 2)); return { result: squareDiffs.reduce((sum, square) => sum + square, 0) / args.length }; } case 'stddev': { const mean = args.reduce((sum, num) => sum + num, 0) / args.length; const squareDiffs = args.map(num => Math.pow(num - mean, 2)); const variance = squareDiffs.reduce((sum, square) => sum + square, 0) / args.length; return { result: Math.sqrt(variance) }; } default: throw new Error(`Unsupported statistical operation: ${operation}`); } } catch (error) { return { error: `Error executing ${operation}: ${(error as Error).message}` }; } } ``` **Explanation:** - These functions perform the actual mathematical and statistical operations. - They handle various operations like addition, subtraction, trigonometry, mean, median, etc. - They return either a result or an error message if the operation fails.

2. **Create the Math Agent** Now that we have our math tool defined and the code above in a file called `weatherTool.ts`, let's create a BedrockLLMAgent that uses this tool. ```typescript import { BedrockLLMAgent } from 'agent-squad'; import { mathAgentToolDefinition, mathToolHandler } from './mathTools'; const MATH_PROMPT = ` You are a mathematical assistant capable of performing various mathematical operations and statistical calculations. Use the provided tools to perform calculations. Always show your work and explain each step and provide the final result of the operation. If a calculation involves multiple steps, use the tools sequentially and explain the process. Only respond to mathematical queries. For non-math questions, politely redirect the conversation to mathematics. `; const mathAgent = new BedrockLLMAgent({ name: "Math Agent", description: "Specialized agent for performing mathematical operations and statistical calculations.", streaming: false, inferenceConfig: { temperature: 0.1, }, toolConfig: { useToolHandler: mathToolHandler, tool: mathAgentToolDefinition, toolMaxRecursions: 5 } }); mathAgent.setSystemPrompt(MATH_PROMPT); ``` 3. **Add the Math Agent to the Orchestrator** Now we can add our math agent to the Agent Squad: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); orchestrator.addAgent(mathAgent); ``` ## 4. Using the Math Agent Now that our math agent is set up and added to the orchestrator, we can use it to perform mathematical operations: ```typescript const response = await orchestrator.routeRequest( "What is the square root of 16 plus the cosine of 45 degrees?", "user123", "session456" ); ``` ### How It Works 1. When a mathematical query is received, the orchestrator routes it to the Math Agent. 2. The Math Agent processes the query using the custom system prompt (MATH_PROMPT). 3. The agent uses the appropriate math tool (`perform_math_operation` or `perform_statistical_calculation`) to perform the required calculations. 4. The mathToolHandler processes the tool use, performs the calculations, and adds the results to the conversation. 5. The agent then formulates a response based on the calculation results and the original query, showing the work and explaining each step. This setup allows for a specialized math agent that can handle various mathematical and statistical queries while performing real-time calculations. --- By following this guide, you can create a powerful, context-aware math agent using BedrockLLMAgent and custom tools within your Agent Squad system. ================================================ FILE: docs/src/content/docs/cookbook/tools/weather-api.mdx ================================================ --- title: Creating a Weather Agent with BedrockLLMAgent and Custom Tools description: Understanding Tool use in Bedrock LLM Agent --- This guide demonstrates how to create a specialized weather agent using BedrockLLMAgent and a custom weather tool. We'll walk through the process of defining the tool, setting up the agent, and integrating it into your Agent Squad system. 1. **Define the Weather Tool** Let's break down the weather tool definition into its key components: **A. Tool Description** import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript export const weatherToolDescription = [ { toolSpec: { name: "Weather_Tool", description: "Get the current weather for a given location, based on its WGS84 coordinates.", inputSchema: { json: { type: "object", properties: { latitude: { type: "string", description: "Geographical WGS84 latitude of the location.", }, longitude: { type: "string", description: "Geographical WGS84 longitude of the location.", }, }, required: ["latitude", "longitude"], } }, } } ]; ``` ```python weather_tool_description = [{ "toolSpec": { "name": "Weather_Tool", "description": "Get the current weather for a given location, based on its WGS84 coordinates.", "inputSchema": { "json": { "type": "object", "properties": { "latitude": { "type": "string", "description": "Geographical WGS84 latitude of the location.", }, "longitude": { "type": "string", "description": "Geographical WGS84 longitude of the location.", }, }, "required": ["latitude", "longitude"], } }, } }] ``` **Explanation:** - This describes the tool's interface to the LLM. - `name`: Identifies the tool to the LLM. - `description`: Explains the tool's purpose to the LLM. - `inputSchema`: Defines the expected input format. - Requires `latitude` and `longitude` as strings. - This schema helps the LLM understand how to use the tool correctly. **B. Custom Prompt** ```typescript export const WEATHER_PROMPT = ` You are a weather assistant that provides current weather data for user-specified locations using only the Weather_Tool, which expects latitude and longitude. Infer the coordinates from the location yourself. If the user provides coordinates, infer the approximate location and refer to it in your response. To use the tool, you strictly apply the provided tool specification. - Explain your step-by-step process, and give brief updates before each step. - Only use the Weather_Tool for data. Never guess or make up information. - Repeat the tool use for subsequent requests if necessary. - If the tool errors, apologize, explain weather is unavailable, and suggest other options. - Report temperatures in °C (°F) and wind in km/h (mph). Keep weather reports concise. Sparingly use emojis where appropriate. - Only respond to weather queries. Remind off-topic users of your purpose. - Never claim to search online, access external data, or use tools besides Weather_Tool. - Complete the entire process until you have all required data before sending the complete response. `; ``` ```python weather_tool_prompt = """ You are a weather assistant that provides current weather data for user-specified locations using only the Weather_Tool, which expects latitude and longitude. Infer the coordinates from the location yourself. If the user provides coordinates, infer the approximate location and refer to it in your response. To use the tool, you strictly apply the provided tool specification. - Explain your step-by-step process, and give brief updates before each step. - Only use the Weather_Tool for data. Never guess or make up information. - Repeat the tool use for subsequent requests if necessary. - If the tool errors, apologize, explain weather is unavailable, and suggest other options. - Report temperatures in °C (°F) and wind in km/h (mph). Keep weather reports concise. Sparingly use emojis where appropriate. - Only respond to weather queries. Remind off-topic users of your purpose. - Never claim to search online, access external data, or use tools besides Weather_Tool. - Complete the entire process until you have all required data before sending the complete response. """ ``` **Explanation:** - This prompt sets the behavior and limitations for the LLM. - It instructs the LLM to: - Use only the Weather_Tool for data. - Infer coordinates from location names. - Provide step-by-step explanations. - Handle errors gracefully. - Format responses consistently (units, conciseness). - Stay on topic and use only the provided tool. **C. Tool Handler** ```typescript import { ConversationMessage, ParticipantRole } from "agent-squad"; export async function weatherToolHandler(response, conversation: ConversationMessage[]):Promse { const responseContentBlocks = response.content as any[]; let toolResults: any = []; if (!responseContentBlocks) { throw new Error("No content blocks in response"); } for (const contentBlock of response.content) { if ("toolUse" in contentBlock) { const toolUseBlock = contentBlock.toolUse; if (toolUseBlock.name === "Weather_Tool") { const response = await fetchWeatherData({ latitude: toolUseBlock.input.latitude, longitude: toolUseBlock.input.longitude }); toolResults.push({ "toolResult": { "toolUseId": toolUseBlock.toolUseId, "content": [{ json: { result: response } }], } }); } } } const message: ConversationMessage = { role: ParticipantRole.USER, content: toolResults }; return message; } ``` ```python import requests from requests.exceptions import RequestException from typing import List, Dict, Any from agent_squad.types import ConversationMessage, ParticipantRole async def weather_tool_handler(response: ConversationMessage, conversation: List[Dict[str, Any]]) -> ConversationMessage: response_content_blocks = response.content # Initialize an empty list of tool results tool_results = [] if not response_content_blocks: raise ValueError("No content blocks in response") for content_block in response_content_blocks: if "text" in content_block: # Handle text content if needed pass if "toolUse" in content_block: tool_use_block = content_block["toolUse"] tool_use_name = tool_use_block.get("name") if tool_use_name == "Weather_Tool": tool_response = await fetch_weather_data(tool_use_block["input"]) tool_results.append({ "toolResult": { "toolUseId": tool_use_block["toolUseId"], "content": [{"json": {"result": tool_response}}], } }) # Embed the tool results in a new user message message = ConversationMessage( role=ParticipantRole.USER.value, content=tool_results) return message ``` **Explanation:** - This handler processes the LLM's request to use the Weather_Tool. - It iterates through the response content, looking for tool use blocks. - When it finds a Weather_Tool use: - It calls `fetchWeatherData` with the provided coordinates. - It formats the result into a tool result object. - Finally, it returns the tool results to the caller as a new user message. **D. Data Fetching Function** ```typescript async function fetchWeatherData(inputData: { latitude: number; longitude: number }) { const endpoint = "https://api.open-meteo.com/v1/forecast"; const params = new URLSearchParams({ latitude: inputData.latitude.toString(), longitude: inputData.longitude.toString(), current_weather: "true", }); try { const response = await fetch(`${endpoint}?${params}`); const data = await response.json(); if (!response.ok) { return { error: 'Request failed', message: data.message || 'An error occurred' }; } return { weather_data: data }; } catch (error: any) { return { error: error.name, message: error.message }; } } ``` ```python async def fetch_weather_data(input_data): """ Fetches weather data for the given latitude and longitude using the Open-Meteo API. Returns the weather data or an error message if the request fails. :param input_data: The input data containing the latitude and longitude. :return: The weather data or an error message. """ endpoint = "https://api.open-meteo.com/v1/forecast" latitude = input_data.get("latitude") longitude = input_data.get("longitude", "") params = {"latitude": latitude, "longitude": longitude, "current_weather": True} try: response = requests.get(endpoint, params=params) weather_data = {"weather_data": response.json()} response.raise_for_status() return weather_data except RequestException as e: return e.response.json() except Exception as e: return {"error": type(e), "message": str(e)} ``` **Explanation:** - This function makes the actual API call to get weather data. - It uses the Open-Meteo API (a free weather API service). - It constructs the API URL with the provided latitude and longitude. - It handles both successful responses and errors: - On success, it returns the weather data. - On failure, it returns an error object. These components work together to create a functional weather tool: 1. The tool description tells the LLM how to use the tool. 2. The prompt guides the LLM's behavior and response format. 3. The handler processes the LLM's tool use requests. 4. The fetch function retrieves real weather data based on the LLM's input. This setup allows the BedrockLLMAgent to provide weather information by seamlessly integrating external data into its responses. 2. **Create the Weather Agent** Now that we have our weather tool defined and the code above in a file called `weatherTool.ts`, let's create a BedrockLLMAgent that uses this tool. ```typescript // weatherAgent.ts import { BedrockLLMAgent } from 'agent-squad'; import { weatherToolDescription, weatherToolHandler, WEATHER_PROMPT } from './weatherTool'; const weatherAgent = new BedrockLLMAgent({ name: "Weather Agent", description:`Specialized agent for providing comprehensive weather information and forecasts for specific cities worldwide. This agent can deliver current conditions, temperature ranges, precipitation probabilities, wind speeds, humidity levels, UV indexes, and extended forecasts. It can also offer insights on severe weather alerts, air quality indexes, and seasonal climate patterns. The agent is capable of interpreting user queries related to weather, including natural language requests like 'Do I need an umbrella today?' or 'What's the best day for outdoor activities this week?'. It can handle location-specific queries and time-based weather predictions, making it ideal for travel planning, event scheduling, and daily decision-making based on weather conditions.`, streaming: false, inferenceConfig: { temperature: 0.1, }, toolConfig: { useToolHandler: weatherToolHandler, tool: weatherToolDescription, toolMaxRecursions: 5 } }); weatherAgent.setSystemPrompt(WEATHER_PROMPT); ``` ```python from tools import weather_tool from agent_squad.agents import (BedrockLLMAgent, BedrockLLMAgentOptions) weather_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Weather Agent", streaming=False, description="Specialized agent for giving weather condition from a city.", tool_config={ 'tool':weather_tool.weather_tool_description, 'toolMaxRecursions': 5, 'useToolHandler': weather_tool.weather_tool_handler } )) weather_agent.set_system_prompt(weather_tool.weather_tool_prompt) ``` 3. **Add the Weather Agent to the Orchestrator** Now we can add our weather agent to the Agent Squad: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(); orchestrator.addAgent(weatherAgent); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad() orchestrator.add_agent(weather_agent) ``` ## 4. Using the Weather Agent Now that our weather agent is set up and added to the orchestrator, we can use it to get weather information: ```typescript const response = await orchestrator.routeRequest( "What's the weather like in New York City?", "user123", "session456" ); ``` ```python response = await orchestrator.route_request("What's the weather like in New York City?", "user123", "session456") ``` ### How It Works 1. When a weather query is received, the orchestrator routes it to the Weather Agent. 2. The Weather Agent processes the query using the custom system prompt (WEATHER_PROMPT). 3. The agent uses the Weather_Tool to fetch weather data for the specified location. 4. The weatherToolHandler processes the tool use, fetches real weather data, and adds it to the conversation. 5. The agent then formulates a response based on the weather data and the original query. This setup allows for a specialized weather agent that can handle various weather-related queries while using real-time data from an external API. --- By following this guide, you can create a powerful, context-aware weather agent using BedrockLLMAgent and custom tools within your Agent Squad system. ================================================ FILE: docs/src/content/docs/general/faq.md ================================================ --- title: FAQ --- ##### What is the Agent Squad framework? The Agent Squad System is a flexible and powerful framework designed for managing multiple AI agents, intelligently routing user queries, and handling complex conversations. It allows developers to create scalable AI systems that can maintain coherent dialogues across multiple domains, efficiently delegating tasks to specialized agents while preserving context throughout the interaction.
--- ##### Who is the Agent Squad framework for? The Agent Squad System is primarily designed to build advanced, scalable AI conversation systems. It's particularly useful for those working on projects that require handling complex, multi-domain conversations or integrating multiple specialized AI agents into a cohesive system.
--- ##### What types of agents are supported? The framework is designed to accommodate essentially any type of agent you can envision. It comes with several built-in agents, including: - [Bedrock LLM Agent](/agent-squad/agents/built-in/bedrock-llm-agent): Leverages Amazon Bedrock's API. - [Amazon Bedrock Agent](/agent-squad/agents/built-in/amazon-bedrock-agent): Leverages existing Amazon Bedrock Agents. - [Amazon Lex Bot](/agent-squad/agents/built-in/lex-bot-agent): Implements logic to call Amazon Lex chatbots. - [Lambda Agent](/agent-squad/agents/built-in/lambda-agent): Implements logic to invoke an AWS Lambda function. - [OpenAI Agent](/agent-squad/agents/built-in/openai-agent): Leverages OpenAI's language models, such as GPT-3.5 and GPT-4 Additionally, you have the flexibility to easily create your own [custom agents](/agent-squad/agents/custom-agents) or customize existing ones to suit your specific needs.
--- ##### How does the framework handle conversation context? The Agent Squad framework uses a flexible storage mechanism to save and retrieve conversations. Each conversation is associated with a unique combination of `userId`, `sessionId`, and `agentId`. This allows the system to maintain separate conversation threads for each agent within a user's session, ensuring coherent and contextually relevant interactions over time.
--- ##### Is DynamoDB supported for conversation storage? Yes, the framework includes a built-in DynamoDB storage option. For detailed instructions on how to implement and configure this storage solution, please refer to the [DynamoDB storage](/agent-squad/storage/dynamodb) section in the documentation.
--- ##### Can I deploy the Agent Squad on AWS Lambda? Yes, the system is designed for seamless deployment as an AWS Lambda function. For step-by-step guidance on integrating the orchestrator with Lambda, processing incoming requests, and handling responses, please consult the [AWS Lambda Integration](/agent-squad/deployment/aws-lambda) section in our documentation.
--- ##### What storage options are available for conversation history? The Agent Squad framework supports multiple storage options: - [In-Memory storage](/agent-squad/storage/in-memory): Default option, great for development and testing. - [DynamoDB storage](/agent-squad/storage/dynamodb): For persistent storage in production environments. - [Custom storage](/agent-squad/storage/custom): Developers can implement their own storage solutions by extending the `ChatStorage` class.
--- ##### Is there a way to check if the agents I've added to the orchestrator don't overlap? Agent overlapping can be an issue which may lead to incorrect routing. The framework provides a tool called [Agent Overlap Analysis](/agent-squad/cookbook/monitoring/agent-overlap) that allows you to gain insights about potential overlapping between agents. It's important to understand that routing to agents is done using a combination of user input, agent descriptions, and the conversation history of all agents. Therefore, crafting precise and distinct agent descriptions is crucial for optimal performance. The Agent Overlap Analysis tool helps you understand the similarities and differences between your agents' descriptions. It performs pairwise overlap analysis and calculates uniqueness scores for each agent. This analysis is vital for optimizing your agent setup and ensuring clear separation of responsibilities, ultimately leading to more accurate query routing.
--- ##### Is the Agent Squad framework open for contributions? Yes, contributions are warmly welcomed! You can contribute by creating a Pull Request to add new agents or features to the repository. Alternatively, you can clone the project and utilize the source files directly in your project, customizing them according to your specific requirements.
--- ##### I have an Agent written in Python in AWS Lambda. How can I integrate it with Agent Squad? You can achieve this integration by using a [Lambda Agent](/agent-squad/agents/built-in/lambda-agent) within the orchestrator. This Lambda Agent is able to invoke AWS Lambda functions, including your Python-based Agent. This approach allows you to incorporate your Python-based Lambda function into the multi-agent system without needing to rewrite it in TypeScript.
--- ##### I have a vector store in OpenSearch. How can I use it as a retriever? Today there is a [built-in retriever available](/agent-squad/retrievers/built-in/bedrock-kb-retriever) that is able to query an Amazon Knowledge Base. This retriever extends the generic `Retriever` class. You can easily [build your own retriever](/agent-squad/retrievers/custom-retriever) to work with OpenSearch and pass it to the agent in the initialization phase.
--- ##### Can I use Tools with agents? Yes, [Bedrock LLM Agent](/agent-squad/agents/built-in/bedrock-llm-agent) supports the use of custom tools, allowing you to extend your agents' capabilities. Tools enable agents to perform specific tasks or access external data sources, enhancing their functionality for specialized applications. For practical examples of implementing tools with agents, refer to our documentation on: - [Creating a Weather Agent with Custom Tools](/agent-squad/advanced-features/weather-tool-use) - [Building a Math Agent using Tools](/agent-squad/advanced-features/math-tool-use) These guides demonstrate how to define tool specifications, implement handlers, and integrate tools into BedrockLLMAgent instances, helping you create powerful, domain-specific AI assistants.
--- ##### Is the Agent Squad framework using any frameworks for the underlying process? No, the orchestrator is not using any external frameworks for its underlying process. It is built using only the code specifically created for the orchestrator. This custom implementation was chosen because we wanted to have complete control over the orchestrator's processes and optimize its performance. By avoiding external frameworks, we can minimize additional latency and ensure that every component of the orchestrator is tailored to the unique requirements of managing and coordinating multiple AI agents efficiently.
--- ##### Can logging be customized in the Agent Squad? Yes, logging can be fully customized. While the orchestrator uses `console.log` by default, you can provide your own logger when initializing the orchestrator. For detailed instructions on customizing logging, see our [Logging documentation](/agent-squad/advanced-features/logging). ##### For a user intent, is there the possibility to execute multiple processing (so like multiple agents)? The current built-in agents are designed to execute a single task. However, you can easily create your own agent that handles multiple processing steps. To do this: - [Create a custom agent](/agent-squad/agents/custom-agents) by following our guide on creating custom agents. - In the `processRequest` method of your custom agent, implement your desired logic for multiple processing steps. - Add your new agent to the orchestrator. This approach allows you to create complex agents that can handle multiple tasks or processing steps in response to a single user intent, giving you full control over the agent's behavior and capabilities. ================================================ FILE: docs/src/content/docs/general/how-it-works.md ================================================ --- title: How it works --- The Agent Squad framework is a powerful tool for implementing sophisticated AI systems comprising multiple specialized agents. Its primary purpose is to intelligently route user queries to the most appropriate agents while maintaining contextual awareness throughout interactions.



## Orchestrator Logic The Agent Squad follows a specific process for each user request: 1. **Request Initiation**: The user sends a request to the orchestrator. 2. **Classification**: The [Classifier](/agent-squad/classifiers/overview) uses an LLM to analyze the user's request, agent descriptions, and conversation history from all agents for the current user ID and session ID. This comprehensive view allows the classifier to understand ongoing conversations and context across all agents. - The framework includes two [built-in classifier](/agent-squad/classifiers/overview) implementations, with one used by default. - Users can customize many options for these built-in classifiers. - There's also the option to create your own [custom classifier](/agent-squad/classifiers/custom-classifier), potentially using models different from those in the built-in implementations. The classifier determines the most appropriate agent for: - A new query requiring a specific agent (e.g., "I want to book a flight" or "What is the base rate interest for a 20-year loan?") - A follow-up to a previous interaction, where the user might provide a short answer like "Tell me more", "Again", or "12". In this case, the LLM identifies the last agent that responded and is waiting for this answer. 3. **Agent Selection**: The Classifier responds with the name of the selected agent. 4. **Request Routing**: The user's input is sent to the chosen agent. 5. **Agent Processing**: The selected [agent](/agent-squad/agents/overview) processes the request. It automatically retrieves its own conversation history for the current user ID and session ID. This ensures that each agent maintains its context without access to other agents' conversations. - The framework provides several built-in agents for common tasks. - Users have the option to customize a wide range of properties for these built-in agents. - There's also the flexibility to quickly create your own [custom agents](/agent-squad/agents/custom-agents) for specific needs. 6. **Response Generation**: The agent generates a response, which may be sent in a standard response mode or via streaming, depending on the agent's capabilities and initialization settings. 7. **Conversation Storage**: The orchestrator automatically handles saving the user's input and the agent's response into the [storage](/agent-squad/storage/overview) for that specific user ID and session ID. This step is crucial for maintaining context and enabling coherent multi-turn conversations. Key points about storage: - The framework provides two built-in storage options: in-memory and DynamoDB. - You have the flexibility to quickly create and implement your own custom storage solution and pass it to the orchestrator. - Conversation saving can be disabled for individual agents that don't require follow-up interactions. - The number of messages kept in the history can be configured for each agent. 8. **Response Delivery**: The orchestrator delivers the agent's response back to the user. This process ensures that each request is handled by the most appropriate agent while maintaining context across the entire conversation. The classifier has a global view of all agent conversations, while individual agents only have access to their own conversation history. This architecture allows for intelligent routing and context-aware responses while maintaining separation between agent functionalities. The orchestrator's automatic handling of conversation saving and fetching, combined with flexible storage options, provides a powerful and customizable system for managing conversation context in multi-agent scenarios. The ability to customize or replace classifiers and agents offers further flexibility to tailor the system to specific needs. --- The Agent Squad framework empowers you to leverage multiple agents for handling diverse tasks. In the framework context, an agent can be any of the following (or a combination of one or more): - LLMs (through Amazon Bedrock or any other cloud-hosted or on-premises LLM) - API calls - AWS Lambda functions - Local processing - Amazon Lex Bot - Amazon Bedrock Agent - Any other specific task or process This flexible architecture allows you to incorporate as many agents as your application requires, and combine them in ways that best suit your needs. Each agent needs a name and a description (plus other properties specific to the type of agent you use). The agent description plays a crucial role in the orchestration process. It should be detailed and comprehensive, as the orchestrator relies on this description, along with the current user input and the conversation history of all agents, to determine the most appropriate routing for each request. While the framework's flexibility is a strength, it's important to be mindful of potential overlaps between agents, which could lead to incorrect routing. To help you analyze and prevent such overlaps, we recommend reviewing our [agent overlap analysis](/agent-squad/cookbook/monitoring/agent-overlap) section for a deeper understanding. ### Agent abstraction: unified processing across platforms One of the key strengths of the Agent Squad framework lies in its **agents' standard implementation**. This standardization allows for remarkable flexibility and consistency across diverse environments. Whether you're working with different cloud providers, various LLM models, or a mix of cloud-based and local solutions, agents provide a uniform interface for task execution. This means you can seamlessly switch between, for example, an [Amazon Lex Bot Agent](/agent-squad/agents/built-in/lex-bot-agent) and a [Amazon Bedrock Agent](/agent-squad/agents/built-in/amazon-bedrock-agent) with tools, or transition from a cloud-hosted LLM to a locally running one, all while maintaining the same code structure. Also, if your application needs to use different models with a [Bedrock LLM Agent](/agent-squad/agents/built-in/bedrock-llm-agent) and/or a [Amazon Lex Bot Agent](/agent-squad/agents/built-in/lex-bot-agent) in sequence or in parallel, you can easily do so as the code implementation is already in place. This standardized approach means you don't need to write new code for each model; instead, you can simply use the agents as they are. To leverage this flexibility, simply install the framework and import the needed agents. You can then call them directly using the `processRequest` method, regardless of the underlying technology. This standardization not only simplifies development and maintenance but also facilitates easy experimentation and optimization across multiple platforms and technologies without the need for extensive code refactoring. This standardization empowers you to experiment with various agent types and configurations while maintaining the integrity of their core application code. ### Main Components of the Orchestrator The main components that are composing the orchestrator: - [Orchestrator](/agent-squad/orchestrator/overview) - Acts as the central coordinator for all other components - Manages the flow of information between Classifier, Agents, Storage, and Retrievers - Processes user input and orchestrates the generation of appropriate responses - Handles error scenarios and fallback mechanisms - [Classifier](/agent-squad/classifiers/overview) - Examines user input, agent descriptions, and conversation history - Identifies the most appropriate agent for each request - Custom Classifiers: Create entirely new classifiers for specific tasks or domains - [Agents](/agent-squad/agents/overview) - Prebuilt Agents: Ready-to-use agents for common tasks - Customizable Agents: Extend or override prebuilt agents to tailor functionality - Custom Agents: Create entirely new agents for specific tasks or domains - [Conversation Storage](/agent-squad/storage/overview) - Maintains conversation history - Supports flexible storage options (in-memory and DynamoDB) - Custom storage solutions - Operates on two levels: Classifier context and Agent context - [Retrievers](/agent-squad/retrievers/overview) - Enhance LLM-based agents performance by providing context and relevant information - Improve efficiency by pulling necessary information on-demand, rather than relying solely on the model's training data - Prebuilt Retrievers: Ready-to-use retrievers for common data sources - Custom Retrievers: Create specialized retrievers for specific data stores or formats --- Each component of the orchestrator can be customized or replaced with custom implementations, providing unparalleled flexibility and making the framework adaptable to a wide variety of scenarios and specific requirements. ================================================ FILE: docs/src/content/docs/general/introduction.md ================================================ --- title: Introduction description: Introduction to Agent Squad framework --- The emergence of both large and small language models, deployable in cloud environments or on local systems, offers the opportunity to utilize multiple specialized models for specific tasks. When configured to operate independently on designated tasks, these specialized models are typically referred to as **agents**. Building intelligent, context-aware AI applications faces a significant challenge in managing a diverse set of agents. This core difficulty is compounded by the need to unify operations across different domains, maintain contextual understanding, and implement scalable architectures. ## 🚀 Building flexible AI systems To address these challenges and empower developers to quickly experiment with and deploy advanced multi-agent AI systems, we've created the **Agent Squad** framework. The Agent Squad is a flexible and powerful framework designed for managing multiple AI agents, intelligently routing user queries, and handling complex conversations. Built with scalability and modularity in mind, it allows to create AI applications that can maintain coherent dialogues across multiple domains, efficiently delegating tasks to specialized agents while preserving context throughout the interaction. This project has been designed to address a wide array of use-cases, including but not limited to: - Complex customer support systems - Multi-domain virtual assistants - Smart home and IoT device management - Multi-lingual customer support ## 🔖 Features Below are some of the key features we've built into the Agent Squad framework: - 🧠 **Intelligent Intent Classification** — Dynamically route queries to the most suitable agent based on context and content. - 🌊 **Flexible Agent Responses** — Support for both **streaming** and **non-streaming** responses from different agents. - 📚 **Context Management** — Maintain and utilize conversation context across multiple agents for coherent interactions. - 🔧 **Extensible Architecture** — Easily integrate new agents or customize existing ones to fit your specific needs. - 🌐 **Universal Deployment** — Run anywhere - from AWS Lambda to your local environment or any cloud platform. - 🚀 **Scalable Design** — Handle multiple concurrent conversations efficiently, scaling from simple chatbots to complex AI systems. - 📊 **Agent Overlap Analysis** — Built-in tools to analyze and optimize your agent configurations. - 📦 **Pre-configured Agents** — Ready-to-use agents powered by Amazon Bedrock models. With the Agent Squad framework, developers can rapidly prototype and deploy sophisticated AI conversation systems that leverage the power of multiple specialized agents. The framework's extensibility and customization capabilities support the creation of a wide range of AI applications, from complex customer service systems to multi-domain virtual assistants and advanced collaborative AI tools, allowing for the implementation of diverse ideas. ================================================ FILE: docs/src/content/docs/general/quickstart.mdx ================================================ --- title: Quickstart --- import { Tabs, TabItem } from '@astrojs/starlight/components'; # Quickstart Guide for Agent Squad To help you kickstart with the Agent Squad framework, we'll walk you through the step-by-step process of setting up and running your first multi-agent conversation.
> 💁 Ensure you have Node.js and npm installed (for TypeScript) or Python installed (for Python) on your development environment before proceeding. ## Prerequisites 1. Create a new project: ```bash mkdir test_agent_squad cd test_agent_squad npm init ``` Follow the steps to generate a `package.json` file. ```bash mkdir test_agent_squad cd test_agent_squad # Optional: Set up a virtual environment python -m venv venv source venv/bin/activate # On Windows use `venv\Scripts\activate` ``` 2. Authenticate with your AWS account This quickstart demonstrates the use of Amazon Bedrock for both classification and agent responses. To authenticate with your AWS account, follow these steps: a. Install the AWS CLI if you haven't already. You can download it from the [official AWS CLI page](https://aws.amazon.com/cli/). b. Configure your AWS CLI with your credentials. For detailed instructions on how to set up your AWS CLI, please refer to the [AWS CLI Configuration Quickstart Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html). c. After configuring your AWS CLI, verify your authentication by running: ```bash aws sts get-caller-identity ``` If successful, this command will return your AWS account ID, user ID, and ARN. By default, the framework is configured as follows: - Classifier: Uses the **[Bedrock Classifier](/agent-squad/classifiers/built-in/bedrock-classifier/)** implementation with `anthropic.claude-3-5-sonnet-20240620-v1:0` - Agent: Utilizes the **[Bedrock LLM Agent](/agent-squad/agents/built-in/bedrock-llm-agent)** with `anthropic.claude-3-haiku-20240307-v1:0`
> **Important** > > These are merely default settings and can be easily changed to suit your needs or preferences.
You have the flexibility to: - Change the classifier model or implementation - Change the agent model or implementation - Use any other compatible models available through Amazon Bedrock Ensure you have [requested access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) to the models you intend to use through the AWS console.
> **To customize the model selection**: > - For the classifier, refer to [our guide](/agent-squad/classifiers/overview) on configuring the classifier. > - For the agent, refer to our guide on configuring [agents](/agent-squad/agents/overview). ## 🚀 Get Started! 1. Install the Agent Squad framework in your project: ```bash npm install agent-squad ``` ```bash pip install "agent-squad[anthropic]" # for Anthropic classifier and agent pip install "agent-squad[openai]" # for OpenAI classifier and agent pip install "agent-squad[all]" # for all packages including Anthropic and OpenAI ``` 2. Create a new file for your quickstart code: Create a file named `quickstart.ts`. Create a file named `quickstart.py`. 3. Create an Orchestrator: ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad({ config: { LOG_AGENT_CHAT: true, LOG_CLASSIFIER_CHAT: true, LOG_CLASSIFIER_RAW_OUTPUT: false, LOG_CLASSIFIER_OUTPUT: true, LOG_EXECUTION_TIMES: true, } }); ``` ```python import uuid import asyncio from typing import Optional, List, Dict, Any import json import sys from agent_squad.orchestrator import AgentSquad, AgentSquadConfig from agent_squad.agents import (BedrockLLMAgent, BedrockLLMAgentOptions, AgentResponse, AgentCallbacks) from agent_squad.types import ConversationMessage, ParticipantRole orchestrator = AgentSquad(options=AgentSquadConfig( LOG_AGENT_CHAT=True, LOG_CLASSIFIER_CHAT=True, LOG_CLASSIFIER_RAW_OUTPUT=True, LOG_CLASSIFIER_OUTPUT=True, LOG_EXECUTION_TIMES=True, MAX_RETRIES=3, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=True, MAX_MESSAGE_PAIRS_PER_AGENT=10 )) ``` 4. Add Agents: ```typescript import { BedrockLLMAgent } from "agent-squad"; orchestrator.addAgent( new BedrockLLMAgent({ name: "Tech Agent", description: "Specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.", streaming: true }) ); orchestrator.addAgent( new BedrockLLMAgent({ name: "Health Agent", description: "Focuses on health and medical topics such as general wellness, nutrition, diseases, treatments, mental health, fitness, healthcare systems, and medical terminology or concepts.", }) ); ``` ```python class BedrockLLMAgentCallbacks(AgentCallbacks): async def on_llm_new_token(self, token: str) -> None: # handle response streaming here print(token, end='', flush=True) tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", streaming=True, description="Specializes in technology areas including software development, hardware, AI, \ cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs \ related to technology products and services.", callbacks=BedrockLLMAgentCallbacks() )) orchestrator.add_agent(tech_agent) health_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Health Agent", description="Focuses on health and medical topics such as general wellness, nutrition, diseases, treatments, mental health, fitness, healthcare systems, and medical terminology or concepts.", callbacks=BedrockLLMAgentCallbacks() )) orchestrator.add_agent(health_agent) ``` 5. Send a Query: ```typescript const userId = "quickstart-user"; const sessionId = "quickstart-session"; const query = "What are the latest trends in AI?"; console.log(`\nUser Query: ${query}`); async function main() { try { const response = await orchestrator.routeRequest(query, userId, sessionId); console.log("\n** RESPONSE ** \n"); console.log(`> Agent ID: ${response.metadata.agentId}`); console.log(`> Agent Name: ${response.metadata.agentName}`); console.log(`> User Input: ${response.metadata.userInput}`); console.log(`> User ID: ${response.metadata.userId}`); console.log(`> Session ID: ${response.metadata.sessionId}`); console.log( `> Additional Parameters:`, response.metadata.additionalParams ); console.log(`\n> Response: `); // Stream the content for await (const chunk of response.output) { if (typeof chunk === "string") { process.stdout.write(chunk); } else { console.error("Received unexpected chunk type:", typeof chunk); } } console.log(); } catch (error) { console.error("An error occurred:", error); // Here you could also add more specific error handling if needed } } main(); ``` ```python async def handle_request(_orchestrator: AgentSquad, _user_input: str, _user_id: str, _session_id: str): response: AgentResponse = await _orchestrator.route_request(_user_input, _user_id, _session_id) # Print metadata print("\nMetadata:") print(f"Selected Agent: {response.metadata.agent_name}") if response.streaming: print('Response:', response.output.content[0]['text']) else: print('Response:', response.output.content[0]['text']) if __name__ == "__main__": USER_ID = "user123" SESSION_ID = str(uuid.uuid4()) print("Welcome to the interactive Multi-Agent system. Type 'quit' to exit.") while True: # Get user input user_input = input("\nYou: ").strip() if user_input.lower() == 'quit': print("Exiting the program. Goodbye!") sys.exit() # Run the async function asyncio.run(handle_request(orchestrator, user_input, USER_ID, SESSION_ID)) ``` Now, let's run the quickstart script: ```bash npx ts-node quickstart.ts ``` ```bash python quickstart.py ``` Congratulations! 🎉 You've successfully set up and run your first multi-agent conversation using the Agent Squad System. ## 👨‍💻 Next Steps Now that you've seen the basic functionality, here are some next steps to explore: 1. Try adding other agents from those built-in in the framework ([Bedrock LLM Agent](/agent-squad/agents/built-in/bedrock-llm-agent), [Amazon Lex Bot](/agent-squad/agents/built-in/lex-bot-agent), [Amazon Bedrock Agent](/agent-squad/agents/built-in/amazon-bedrock-agent), [Lambda Agent](/agent-squad/agents/built-in/lambda-agent), [OpenAI Agent](/agent-squad/agents/built-in/openai-agent)). 2. Experiment with different storage options, such as [Amazon DynamoDB](/agent-squad/storage/dynamodb) for persistent storage. 3. Explore the [Agent Overlap Analysis](/agent-squad/cookbook/monitoring/agent-overlap/) feature to optimize your agent configurations. 4. Integrate the system into a web application or deploy it as an [AWS Lambda function](/agent-squad/deployment/aws-lambda). 5. Try adding your own [custom agents](/agent-squad/agents/custom-agents) by extending the `Agent` class. For more detailed information on these advanced features, check out our full documentation. ## 🧹 Cleanup As this quickstart uses in-memory storage and local resources, there's no cleanup required. Simply stop the script when you're done experimenting. ================================================ FILE: docs/src/content/docs/index.mdx ================================================ --- title: Agent Squad framework description: Manage multiple AI agents and handle complex conversations template: splash hero: tagline: Flexible and powerful framework for managing multiple AI agents and handling complex conversations 🤖🚀 actions: - text: How it works link: /agent-squad/general/how-it-works icon: right-arrow variant: primary # Visual break - Next line starts here - text: GitHub Repository link: https://github.com/awslabs/agent-squad icon: external variant: minimal - text: NPM Repository link: https://www.npmjs.com/package/agent-squad icon: external variant: minimal - text: PyPI Repository link: https://pypi.org/project/agent-squad/ icon: external variant: minimal --- Visit the [Amazon Bedrock Agents](https://aws.amazon.com/bedrock/agents/) page to explore how multi-agent collaboration enables developers to build, deploy, and manage specialized agents designed for tackling complex workflows efficiently and accurately. import { Card, CardGrid } from '@astrojs/starlight/components'; import { Badge } from '@astrojs/starlight/components'; ## Key Features - **Multi-Agent Orchestration**: Seamlessly coordinate and leverage multiple AI agents in a single system - **Dual language support**: Fully implemented in both **Python** and **TypeScript** - **Intelligent intent classification**: Dynamically route queries to the most suitable agent based on context and content - **Flexible agent responses**: Support for both streaming and non-streaming responses from different agents - **Context management**: Maintain and utilize conversation context across multiple agents for coherent interactions - **Extensible architecture**: Easily integrate new agents or customize existing ones to fit your specific needs - **Universal deployment**: Run anywhere - from AWS Lambda to your local environment or any cloud platform Get up and running in minutes: See our [Quick Start Guide](/agent-squad/general/quickstart) for more details. See our [How it works](/agent-squad/general/how-it-works) for more details. Explore our code samples and deployment options: - [Local Development Guide](/agent-squad/cookbook/examples/typescript-local-demo) - [AWS Lambda Deployment (TypeScript)](/agent-squad/cookbook/lambda/aws-lambda-nodejs) - [AWS Lambda Deployment (Python)](/agent-squad/cookbook/lambda/aws-lambda-python) - [Chainlit (Python)](/agent-squad/cookbook/examples/chat-chainlit-app) - [FastAPI streaming (Python)](/agent-squad/cookbook/examples/fast-api-streaming) Discover our built-in agents: - [Supervisor Agent](/agent-squad/agents/built-in/supervisor-agent) - [Open AI Agent](/agent-squad/agents/built-in/openai-agent) - [Bedrock Inline Agent](/agent-squad/agents/built-in/bedrock-inline-agent) - [Bedrock Flows Agent](/agent-squad/agents/built-in/bedrock-flows-agent) - [Bedrock LLM Agent](/agent-squad/agents/built-in/bedrock-llm-agent) - [Amazon Bedrock Agent](/agent-squad/agents/built-in/amazon-bedrock-agent) - [Lex Bot Agent](/agent-squad/agents/built-in/lex-bot-agent) - [AWS Lambda Agent](/agent-squad/agents/built-in/lambda-agent) - [Bedrock Translator Agent](/agent-squad/agents/built-in/bedrock-translator-agent) - [Comprehend Filter Agent](/agent-squad/agents/built-in/comprehend-filter-agent) - [Chain Agent](/agent-squad/agents/built-in/chain-agent) Learn how to [create your own custom agents](/agent-squad/agents/custom-agents). ================================================ FILE: docs/src/content/docs/orchestrator/overview.mdx ================================================ --- title: Orchestrator overview description: An introduction to the Orchestrator --- The Agent Squad is the central component of the framework, responsible for managing agents, routing requests, and handling conversations. This page provides an overview of how to initialize the Orchestrator and details all available configuration options. ### Initializing the Orchestrator To create a new Orchestrator instance, you can use the `AgentSquad` class: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { AgentSquad } from "agent-squad"; const orchestrator = new AgentSquad(options); ``` ```python from agent_squad.orchestrator import AgentSquad orchestrator = AgentSquad(options=options) ``` The `options` parameter is optional and allows you to customize various aspects of the Orchestrator's behavior. ### Configuration options The Orchestrator accepts an `AgentSquadConfig` object during initialization. All options are optional and will use default values if not specified. Here's a complete list of available options: 1. `storage`: Specifies the storage mechanism for chat history. Default is `InMemoryChatStorage`. 2. `config`: An instance of `AgentSquadConfig` containing various configuration flags and values: - `LOG_AGENT_CHAT`: Boolean flag to log agent chat interactions. - `LOG_CLASSIFIER_CHAT`: Boolean flag to log classifier chat interactions. - `LOG_CLASSIFIER_RAW_OUTPUT`: Boolean flag to log raw classifier output. - `LOG_CLASSIFIER_OUTPUT`: Boolean flag to log processed classifier output. - `LOG_EXECUTION_TIMES`: Boolean flag to log execution times of various operations. - `MAX_RETRIES`: Number of maximum retry attempts for the classifier. - `MAX_MESSAGE_PAIRS_PER_AGENT`: Maximum number of message pairs to retain per agent. - `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED`: Boolean flag to use the default agent when no specific agent is identified. - `CLASSIFICATION_ERROR_MESSAGE`: Custom error message for classification errors. - `NO_SELECTED_AGENT_MESSAGE`: Custom message when no agent is selected. - `GENERAL_ROUTING_ERROR_MSG_MESSAGE`: Custom message for general routing errors. 3. `logger`: Custom logger instance. If not provided, a default logger will be used. 4. `classifier`: Custom classifier instance. If not provided, a `BedrockClassifier` will be used. 5. `default_agent`: A default agent when the classifier could not determine the most suitable agent. ### Example with all options Here's an example that demonstrates how to initialize the Orchestrator with all available options: ```typescript import { AgentSquad, AgentSquadConfig } from "agent-squad"; import { DynamoDBChatStorage } from "agent-squad/storage"; import { CustomClassifier } from "./custom-classifier"; import { CustomLogger } from "./custom-logger"; const orchestrator = new AgentSquad({ storage: new DynamoDBChatStorage(), config: { LOG_AGENT_CHAT: true, LOG_CLASSIFIER_CHAT: true, LOG_CLASSIFIER_RAW_OUTPUT: false, LOG_CLASSIFIER_OUTPUT: true, LOG_EXECUTION_TIMES: true, MAX_RETRIES: 3, MAX_MESSAGE_PAIRS_PER_AGENT: 50, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED: true, CLASSIFICATION_ERROR_MESSAGE: "Oops! We couldn't process your request. Please try again.", NO_SELECTED_AGENT_MESSAGE: "I'm sorry, I couldn't determine how to handle your request. Could you please rephrase it?", GENERAL_ROUTING_ERROR_MSG_MESSAGE: "An error occurred while processing your request. Please try again later.", }, logger: new CustomLogger(), classifier: new CustomClassifier(), }); ``` ```python from agent_squad.orchestrator import AgentSquad, AgentSquadConfig from agent_squad.storage import DynamoDBChatStorage from agent_squad.classifiers import BedrockClassifier, BedrockClassifierOptions from agent_squad.utils.logger import Logger from custom_classifier import CustomClassifier from custom_logger import CustomLogger orchestrator = AgentSquad( options=AgentSquadConfig( LOG_AGENT_CHAT=True, LOG_CLASSIFIER_CHAT=True, LOG_CLASSIFIER_RAW_OUTPUT=False, LOG_CLASSIFIER_OUTPUT=True, LOG_EXECUTION_TIMES=True, MAX_RETRIES=3, MAX_MESSAGE_PAIRS_PER_AGENT=50, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=True, CLASSIFICATION_ERROR_MESSAGE="Oops! We couldn't process your request. Please try again.", NO_SELECTED_AGENT_MESSAGE="I'm sorry, I couldn't determine how to handle your request. Could you please rephrase it?", GENERAL_ROUTING_ERROR_MSG_MESSAGE="An error occurred while processing your request. Please try again later.", ), storage=DynamoDBChatStorage(), classifier=CustomClassifier(), logger=CustomLogger(), default_agent=BedrockLLMAgent(BedrockLLMAgentOptions( name="Default Agent", streaming=False, description="This is the default agent that handles general queries and tasks.", )) ) ``` Remember, all these options are optional. If you don't specify an option, the Orchestrator will use its default value. ### Default values The default configuration is defined as follows: ```typescript export const DEFAULT_CONFIG: AgentSquadConfig = { LOG_AGENT_CHAT: false, LOG_CLASSIFIER_CHAT: false, LOG_CLASSIFIER_RAW_OUTPUT: false, LOG_CLASSIFIER_OUTPUT: false, LOG_EXECUTION_TIMES: false, MAX_RETRIES: 3, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED: true, NO_SELECTED_AGENT_MESSAGE: "I'm sorry, I couldn't determine how to handle your request. Could you please rephrase it?", MAX_MESSAGE_PAIRS_PER_AGENT: 100, }; ``` ```python from agent_squad.types import AgentSquadConfig DEFAULT_CONFIG = AgentSquadConfig() ``` In both implementations, `DEFAULT_CONFIG` is an instance of `AgentSquadConfig` with default values. ### Available Functions The AgentSquad provides several key functions to manage agents, process requests, and configure the orchestrator. Here's a detailed overview of each function, explaining what it does and why you might use it: ```typescript 1. addAgent(agent: Agent): void 2. getDefaultAgent(): Agent 3. setDefaultAgent(agent: Agent): void 4. getAllAgents(): { [key: string]: { name: string; description: string } } 5. routeRequest(userInput: string, userId: string, sessionId: string, additionalParams: Record = {}): Promise ``` ```python 1. add_agent(agent: Agent) -> None 2. get_default_agent() -> Agent 3. set_default_agent(agent: Agent) -> None 4. get_all_agents() -> Dict[str, Dict[str, str]] 5. route_request(user_input: str, user_id: str, session_id: str, additional_params: Dict[str, str] = {}, stream_response: bool | None = False) -> AgentResponse ``` Let's break down each function: 1. **addAgent** (TypeScript) / **add_agent** (Python) - **What it does**: Adds a new agent to the orchestrator. - **Why use it**: Use this function to expand the capabilities of your system by introducing new specialized agents. Each agent can handle specific types of queries or tasks. - **Example use case**: Adding a weather agent to handle weather-related queries, or a booking agent for reservation tasks. 2. **getDefaultAgent** - **What it does**: Retrieves the current default agent. - **Why use it**: This function is useful when you need to reference or use the default agent, perhaps for fallback scenarios or to compare its capabilities with other agents. - **Example use case**: Checking the current default agent's configuration before deciding whether to replace it. 3. **setDefaultAgent** - **What it does**: Sets a new default agent for the orchestrator. - **Why use it**: This allows you to change the fallback agent used when no specific agent is selected for a query. It's useful for customizing the general-purpose response handling of your system. - **Example use case**: Replacing the default generalist agent with a more specialized one that better fits your application's primary use case. 4. **getAllAgents** - **What it does**: Retrieves a dictionary of all registered agents, including their names and descriptions. - **Why use it**: This function is useful for getting an overview of all available agents in the system. It can be used for debugging, logging, or providing user-facing information about system capabilities. - **Example use case**: Generating a help message that lists all available agents and their capabilities. 5. **routeRequest** - **What it does**: This is the main function for processing user requests. It takes a user's input, classifies it, selects an appropriate agent, and returns the agent's response. - **Why use it**: This is the core function you'll use to handle user interactions in your application. It encapsulates the entire process of understanding the user's intent and generating an appropriate response. - **Example use case**: Processing a user's message in a chatbot interface and returning the appropriate response. Each of these functions plays a crucial role in configuring and operating the Agent Squad. By using them effectively, you can create a flexible, powerful system capable of handling a wide range of user requests across multiple domains. These functions allow you to configure the orchestrator, manage agents, and process user requests. #### Function Examples Here are practical examples of how to use each function: ```typescript import { AgentSquad, BedrockLLMAgent, AnthropicClassifier } from "agent-squad"; const orchestrator = new AgentSquad(); // 1. addAgent Example const techAgent = new BedrockLLMAgent({ name: "Tech Agent", description: "Handles technical questions about programming and software", streaming: true }); orchestrator.addAgent(techAgent); // 2. getDefaultAgent Example const currentDefault = orchestrator.getDefaultAgent(); console.log(`Current default agent: ${currentDefault.name}`); // 3. setDefaultAgent Example const customDefault = new BedrockLLMAgent({ name: "Custom Default", description: "Handles general queries with specialized knowledge" }); orchestrator.setDefaultAgent(customDefault); // 4. getAllAgents Example const agents = orchestrator.getAllAgents(); console.log("Available agents:"); Object.entries(agents).forEach(([id, info]) => { console.log(`${id}: ${info.name} - ${info.description}`); }); // 5. routeRequest Example async function handleUserQuery() { const response = await orchestrator.routeRequest( "How do I optimize a Python script?", "user123", "session456", { priority: "high" } // Additional parameters ); if (response.streaming) { for await (const chunk of response.output) { process.stdout.write(chunk); } } else { console.log(response.output); } } ``` ```python from agent_squad.orchestrator import AgentSquad from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions, AgentStreamResponse from agent_squad.classifiers import AnthropicClassifier, AnthropicClassifierOptions import asyncio orchestrator = AgentSquad() # 1. add_agent Example tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", description="Handles technical questions about programming and software", streaming=True )) orchestrator.add_agent(tech_agent) # 2. get_default_agent Example current_default = orchestrator.get_default_agent() print(f"Current default agent: {current_default.name}") # 3. set_default_agent Example custom_default = BedrockLLMAgent(BedrockLLMAgentOptions( name="Custom Default", description="Handles general queries with specialized knowledge" )) orchestrator.set_default_agent(custom_default) # 5. get_all_agents Example agents = orchestrator.get_all_agents() print("Available agents:") for agent_id, info in agents.items(): print(f"{agent_id}: {info['name']} - {info['description']}") # 6. route_request Example async def handle_user_query(): response = await orchestrator.route_request( "How do I optimize a Python script?", "user123", "session456", {"priority": "high"} # Additional parameters, True, ) if response.streaming: async for chunk in response.output: if isinstance(chunk, AgentStreamResponse): print(chunk.text, end='', flush=True) else: print(response.output) # Run the example asyncio.run(handle_user_query()) ``` ### Agent Selection and Default Behavior When a user sends a request to the Agent Squad, the system attempts to classify the intent and select an appropriate agent to handle the request. However, there are cases where no specific agent is selected. #### When No Agent is Selected If the classifier cannot confidently determine which agent should handle a request, it may result in no agent being selected. The orchestrator's behavior in this case depends on the `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED` configuration option: 1. If `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED` is `True` (default): - The orchestrator will use the default agent to handle the request. - This ensures that users always receive a response, even if it's from a generalist agent. 2. If `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED` is `False`: - The orchestrator will return a message specified by the `NO_SELECTED_AGENT_MESSAGE` configuration. - This prompts the user to rephrase their request for better agent identification. #### Default Agent The default agent is a `BedrockLLMAgent` configured as a generalist, capable of handling a wide range of topics. It's used when: 1. No specific agent is selected and `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED` is `True`. 2. You explicitly set it as the fallback option. You can customize the default agent or replace it entirely using the `set_default_agent` method: ```typescript import { BedrockLLMAgent, BedrockLLMAgentOptions } from "agent-squad"; const customDefaultAgent = new BedrockLLMAgent({ name: "Custom Default Agent", description: "A custom generalist agent for handling various queries", // Add other options as needed }); orchestrator.setDefaultAgent(customDefaultAgent); ``` ```python from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions custom_default_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Custom Default Agent", description="A custom generalist agent for handling various queries", # Add other options as needed )) orchestrator.set_default_agent(custom_default_agent) ``` #### Customizing NO_SELECTED_AGENT_MESSAGE You can customize the message returned when no agent is selected (and `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED` is `False`) by setting the `NO_SELECTED_AGENT_MESSAGE` in the orchestrator configuration: ```typescript import { AgentSquad, AgentSquadConfig } from "agent-squad"; const orchestrator = new AgentSquad({ config: { USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED: false, NO_SELECTED_AGENT_MESSAGE: "I'm not sure how to handle your request. Could you please provide more details or rephrase it?" } }); ``` ```python from agent_squad.orchestrator import AgentSquad, AgentSquadConfig orchestrator = AgentSquad( options=AgentSquadConfig( USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=False, NO_SELECTED_AGENT_MESSAGE="I'm not sure how to handle your request. Could you please provide more details or rephrase it?" ) ) ``` #### Best Practices 1. **Default Agent Usage**: Use the default agent when you want to ensure all user queries receive a response, even if it's not from a specialized agent. 2. **Prompting for Clarification**: Set `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED` to `False` and customize the `NO_SELECTED_AGENT_MESSAGE` when you want to encourage users to provide more specific or clear requests. 3. **Balancing Specificity and Coverage**: Consider your use case carefully. Using a default agent provides broader coverage but may sacrifice specificity. Prompting for clarification may lead to more accurate agent selection but requires additional user interaction. 4. **Monitoring and Iteration**: Regularly review cases where no agent is selected. This can help you identify gaps in your agent coverage or refine your classification process. By understanding and customizing these behaviors, you can fine-tune your Agent Squad to provide the best possible user experience for your specific use case. ### Additional notes - The `storage` option allows you to specify a custom storage mechanism. By default, it uses in-memory storage (`InMemoryChatStorage`), but you can implement your own storage solution or use built-in options like DynamoDB storage. For more information, see the [Storage section](/agent-squad/storage/overview). - The `logger` option lets you provide a custom logger. If not specified, a default logger will be used. To learn how to implement a custom logger, check out [the logging section](/agent-squad/advanced-features/custom-logging). - The `classifier` option allows you to use a custom classifier for intent classification. If not provided, a `BedrockClassifier` will be used by default. For details on implementing a custom classifier, see the [Custom Classifiers](/agent-squad/classifiers/custom-classifier) documentation. By customizing these options, you can tailor the Orchestrator's behavior to suit your specific use case and requirements. ================================================ FILE: docs/src/content/docs/retrievers/built-in/bedrock-kb-retriever.mdx ================================================ --- title: Knowledge Bases for Amazon Bedrock retriever description: An overview of Knowledge Bases for Amazon Bedrock retriever configuration and usage. --- Knowledge bases for Amazon Bedrock is an Amazon Web Services (AWS) offering which lets you quickly build RAG applications by using your private data to customize FM response. Implementing RAG requires organizations to perform several cumbersome steps to convert data into embeddings (vectors), store the embeddings in a specialized vector database, and build custom integrations into the database to search and retrieve text relevant to the user's query. This can be time-consuming and inefficient. With Knowledge Bases for Amazon Bedrock, simply point to the location of your data in Amazon S3, and Knowledge Bases for Amazon Bedrock takes care of the entire ingestion workflow into your vector database. If you do not have an existing vector database, Amazon Bedrock creates an Amazon OpenSearch Serverless vector store for you. For retrievals, use the AWS SDK - Amazon Bedrock integration via the Retrieve API to retrieve relevant results for a user query from knowledge bases. Knowledge base can be configured through AWS Console or by using AWS SDKs. ## Using the Knowledge Bases Retriever You can add a Knowledge Base for Amazon Bedrock to a `BedrockLLMAgent`. This way you can benefit from using any LLM you want to generate the response based on the information retrieved from your knowledge base. Here is how you can include a retriever into an agent: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript orchestrator.addAgent( new BedrockLLMAgent({ name: "My personal agent", description: "My personal agent is responsible for giving information from an Knowledge Base for Amazon Bedrock.", streaming: true, inferenceConfig: { temperature: 0.1, }, retriever: new AmazonKnowledgeBasesRetriever( new BedrockAgentRuntimeClient(), { knowledgeBaseId: "AXEPIJP4ETUA", retrievalConfiguration: { vectorSearchConfiguration: { numberOfResults: 5, overrideSearchType: SearchType.HYBRID, }, }, } ) }) ); ``` ```python from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions from agent_squad.retrievers import AmazonKnowledgeBasesRetriever, AmazonKnowledgeBasesRetrieverOptions orchestrator.add_agent( BedrockLLMAgent(BedrockLLMAgentOptions( name="My personal agent", description="My personal agent is responsible for giving information from a Knowledge Base for Amazon Bedrock.", streaming=True, inference_config={ "temperature": 0.1, }, retriever=AmazonKnowledgeBasesRetriever(AmazonKnowledgeBasesRetrieverOptions( knowledge_base_id="AXEPIJP4ETUA", retrievalConfiguration={ "vectorSearchConfiguration": { "numberOfResults": 5, "overrideSearchType": "HYBRID", }, }, )) )) ) ``` To use this retriever with a `BedrockLLMAgent`, you would initialize it with the appropriate options and pass it to the agent's configuration, as shown in the example above. Remember to configure your AWS credentials properly and ensure that your application has the necessary permissions to access the Amazon Bedrock service and the specific knowledge base you're using. ================================================ FILE: docs/src/content/docs/retrievers/custom-retriever.mdx ================================================ --- title: Custom retriever description: An overview of retrievers and supported type in the Agent Squad System --- The Agent Squad System allows you to create custom retrievers by extending the abstract Retriever class. This flexibility enables you to integrate various data sources and retrieval methods into your agent system. In this guide, we'll walk through the process of creating a custom retriever, provide an example using OpenSearch Serverless, and explain how to set the retriever for a BedrockLLMAgent. import { Tabs, TabItem } from '@astrojs/starlight/components'; ## Steps to Create a Custom Retriever 1. Create a new class that extends the `Retriever` abstract class. 2. Implement the required abstract methods: `retrieve`, `retrieveAndCombineResults`, and `retrieveAndGenerate`. 3. Add any additional methods or properties specific to your retriever. 1. Create a new class that inherits from the `Retriever` abstract base class. 2. Implement the required abstract methods: `retrieve`, `retrieve_and_combine_results`, and `retrieve_and_generate`. 3. Add any additional methods or properties specific to your retriever. ## Example: OpenSearchServerless Retriever Here's an example of a custom retriever that uses OpenSearch Serverless: Install Opensearch npm package: ```bash npm install "@opensearch-project/opensearch" ``` ```typescript import { Retriever } from "agent-squad"; import { Client } from "@opensearch-project/opensearch"; import { AwsSigv4Signer } from "@opensearch-project/opensearch/aws"; import { defaultProvider } from "@aws-sdk/credential-provider-node"; import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime"; /** * Interface for OpenSearchServerlessRetriever options */ export interface OpenSearchServerlessRetrieverOptions { collectionEndpoint: string; index: string; region: string; vectorField: string; textField: string; k: number; } /** * OpenSearchServerlessRetriever class for interacting with OpenSearch Serverless * Extends the base Retriever class */ export class OpenSearchServerlessRetriever extends Retriever { private client: Client; private bedrockClient: BedrockRuntimeClient; constructor(options: OpenSearchServerlessRetrieverOptions) { super(options); if (!options.collectionEndpoint || !options.index || !options.region) { throw new Error("collectionEndpoint, index, and region are required in options"); } this.client = new Client({ ...AwsSigv4Signer({ region: options.region, service: 'aoss', getCredentials: () => defaultProvider()(), }), node: options.collectionEndpoint, }); this.bedrockClient = new BedrockRuntimeClient({ region: options.region }); this.options.vectorField = options.vectorField; this.options.textField = options.textField; this.options.k = options.k; } async retrieve(text: string): Promise { try { const embeddings = await this.getEmbeddings(text); const results = await this.client.search({ index: this.options.index, body: { _source: { excludes: [this.options.vectorField] }, query: { bool: { must: [ { knn: { [this.options.vectorField]: { vector: embeddings, k: this.options.k }, }, }, ], }, }, size: this.options.k, }, }); return results.body.hits.hits; } catch (error) { throw new Error(`Failed to retrieve: ${error instanceof Error ? error.message : String(error)}`); } } private async getEmbeddings(text: string): Promise { try { const response = await this.bedrockClient.send( new InvokeModelCommand({ modelId: "amazon.titan-embed-text-v2:0", body: JSON.stringify({ inputText: text, }), contentType: "application/json", accept: "application/json", }) ); const body = new TextDecoder().decode(response.body); const embeddings = JSON.parse(body).embedding; if (!Array.isArray(embeddings)) { throw new Error("Invalid embedding format received from Bedrock"); } return embeddings; } catch (error) { throw new Error(`Failed to get embeddings: ${error instanceof Error ? error.message : String(error)}`); } } async retrieveAndCombineResults(text: string): Promise { try { const results = await this.retrieve(text); return results .filter((hit: any) => hit._source && hit._source[this.options.textField]) .map((hit: any) => hit._source[this.options.textField]) .join("\n"); } catch (error) { throw new Error(`Failed to retrieve and combine results: ${error instanceof Error ? error.message : String(error)}`); } } async retrieveAndGenerate(text: string): Promise { return this.retrieveAndCombineResults(text); } async updateDocument(id: string, content: any): Promise { try { const response = await this.client.update({ index: this.options.index, id: id, body: { doc: content } }); return response.body; } catch (error) { throw new Error(`Failed to update document: ${error instanceof Error ? error.message : String(error)}`); } } } ``` Install required Python packages: ```bash pip install opensearch-py boto3 ``` ```python from typing import Any, Dict, List from agent_squad.retrievers import Retriever from opensearchpy import OpenSearch, RequestsHttpConnection from requests_aws4auth import AWS4Auth import boto3 import json class OpenSearchServerlessRetrieverOptions: def __init__(self, collection_endpoint: str, index: str, region: str, vector_field: str, text_field: str, k: int): self.collection_endpoint = collection_endpoint self.index = index self.region = region self.vector_field = vector_field self.text_field = text_field self.k = k class OpenSearchServerlessRetriever(Retriever): def __init__(self, options: OpenSearchServerlessRetrieverOptions): super().__init__(options) self.options = options if not all([options.collection_endpoint, options.index, options.region]): raise ValueError("collection_endpoint, index, and region are required in options") credentials = boto3.Session().get_credentials() awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, options.region, 'aoss', session_token=credentials.token) self.client = OpenSearch( hosts=[{'host': options.collection_endpoint, 'port': 443}], http_auth=awsauth, use_ssl=True, verify_certs=True, connection_class=RequestsHttpConnection ) self.bedrock_client = boto3.client('bedrock-runtime', region_name=options.region) async def retrieve(self, text: str) -> List[Dict[str, Any]]: try: embeddings = await self.get_embeddings(text) query = { "_source": { "excludes": [self.options.vector_field] }, "query": { "knn": { self.options.vector_field: { "vector": embeddings, "k": self.options.k } } }, "size": self.options.k } response = self.client.search(index=self.options.index, body=query) return response['hits']['hits'] except Exception as e: raise Exception(f"Failed to retrieve: {str(e)}") async def get_embeddings(self, text: str) -> List[float]: try: response = self.bedrock_client.invoke_model( modelId="amazon.titan-embed-text-v2:0", body=json.dumps({"inputText": text}), contentType="application/json", accept="application/json" ) embeddings = json.loads(response['body'].read())['embedding'] if not isinstance(embeddings, list): raise ValueError("Invalid embedding format received from Bedrock") return embeddings except Exception as e: raise Exception(f"Failed to get embeddings: {str(e)}") async def retrieve_and_combine_results(self, text: str) -> str: try: results = await self.retrieve(text) return "\n".join( hit['_source'][self.options.text_field] for hit in results if self.options.text_field in hit['_source'] ) except Exception as e: raise Exception(f"Failed to retrieve and combine results: {str(e)}") async def retrieve_and_generate(self, text: str) -> str: return await self.retrieve_and_combine_results(text) async def update_document(self, id: str, content: Dict[str, Any]) -> Dict[str, Any]: try: response = self.client.update( index=self.options.index, id=id, body={"doc": content} ) return response except Exception as e: raise Exception(f"Failed to update document: {str(e)}") ``` ## Using the Custom Retriever with BedrockLLMAgent To use your custom OpenSearchServerlessRetriever: ```typescript import { BedrockLLMAgent } from './path-to-bedrockLLMAgent'; const agent = new BedrockLLMAgent({ name: 'My Bedrock Agent with OpenSearch Serverless', description: 'An agent that uses OpenSearch Serverless for retrieval', retriever: new OpenSearchServerlessRetriever({ collectionEndpoint: "https://xxxxxxxxxxx.us-east-1.aoss.amazonaws.com", index: "vector-index", region: process.env.AWS_REGION!, textField: "textField", vectorField: "vectorField", k: 5, }) }); // Example usage const query = "What is the capital of France?"; const response = await agent.processRequest(query, 'user123', 'session456', []); console.log(response); ``` ```python from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions from custom_retriever import OpenSearchServerlessRetriever, OpenSearchServerlessRetrieverOptions import os agent = BedrockLLMAgent(BedrockLLMAgentOptions( name='My Bedrock Agent with OpenSearch Serverless', description='An agent that uses OpenSearch Serverless for retrieval', retriever=OpenSearchServerlessRetriever(OpenSearchServerlessRetrieverOptions( collection_endpoint="https://xxxxxxxxxxx.us-east-1.aoss.amazonaws.com", index="vector-index", region=os.environ.get('AWS_REGION'), text_field="textField", vector_field="vectorField", k=5 )) )) # Example usage query = "What is the capital of France?" response = await agent.process_request(query, 'user123', 'session456', []) print(response) ``` In this example, we create an instance of our custom `OpenSearchServerlessRetriever` and then pass it to the `BedrockLLMAgent` constructor using the `retriever` field. This allows the agent to use your custom retriever for enhanced knowledge retrieval during request processing. ## How BedrockLLMAgent Uses the Retriever When a BedrockLLMAgent processes a request and a retriever is set, it typically follows these steps: 1. The agent receives a user query through the `processRequest` method. 2. Before sending the query to the language model, the agent calls the retriever's `retrieveAndCombineResults` method with the user's query. 3. The retriever fetches relevant information from its data source (in this case, OpenSearch Serverless). 4. The retrieved information is combined and added to the context sent to the language model. 5. The language model then generates a response based on both the user's query and the additional context provided by the retriever. This process allows the agent to leverage external knowledge sources, potentially improving the accuracy and relevance of its responses. --- By adapting this example as a CustomRetriever for OpenSearch Serverless, you can seamlessly incorporate your **pre-built Opensearch Serverless clusters** into the Agent Squad System, enhancing your agents' knowledge retrieval capabilities. ================================================ FILE: docs/src/content/docs/retrievers/overview.md ================================================ --- title: Retrievers overview description: An overview of retrievers --- A retriever is a component or mechanism used to fetch relevant information from a large corpus of data or a database in response to a query. This process is crucial in enhancing the performance and accuracy of LLMs, especially in tasks that require accessing and utilizing external knowledge sources. ## Key Roles of a Retriever 1. **Improving Context and Relevance:** Retrievers help in providing the LLM with relevant context or information that may not be included in its training data or that is too specific to be generated purely from the model's internal knowledge. 2. **Memory Augmentation:** Retrievers act as an extended memory for the LLM, allowing it to access up-to-date information or detailed data on specific topics, thereby improving the relevance and accuracy of the generated responses. 3. **Efficiency:** Instead of training the model on an enormous and ever-growing dataset, retrievers allow the model to pull in only the necessary information on-demand, making the system more efficient. ================================================ FILE: docs/src/content/docs/storage/custom.mdx ================================================ --- title: Custom storage description: Extending the ChatStorage class to create custom storage options in the Agent Squad System --- The Agent Squad System provides flexibility in how conversation data is stored through its abstract `ChatStorage` class. This guide will walk you through the process of creating a custom storage solution by extending this class. ## Understanding the ChatStorage Abstract Class The `ChatStorage` class defines the interface for all storage solutions in the system. It includes three main methods and two helper methods: import { Tabs, TabItem} from '@astrojs/starlight/components'; ```typescript import { ConversationMessage } from "../types"; export abstract class ChatStorage { protected isConsecutiveMessage(conversation: ConversationMessage[], newMessage: ConversationMessage): boolean { if (conversation.length === 0) return false; const lastMessage = conversation[conversation.length - 1]; return lastMessage.role === newMessage.role; } protected trimConversation(conversation: ConversationMessage[], maxHistorySize?: number): ConversationMessage[] { if (maxHistorySize === undefined) return conversation; // Ensure maxHistorySize is even to maintain complete binoms const adjustedMaxHistorySize = maxHistorySize % 2 === 0 ? maxHistorySize : maxHistorySize - 1; return conversation.slice(-adjustedMaxHistorySize); } abstract saveChatMessage( userId: string, sessionId: string, agentId: string, newMessage: ConversationMessage, maxHistorySize?: number ): Promise; abstract fetchChat( userId: string, sessionId: string, agentId: string, maxHistorySize?: number ): Promise; abstract fetchAllChats( userId: string, sessionId: string ): Promise; } ``` ```python from abc import ABC, abstractmethod from typing import List, Optional from agent_squad.types import ConversationMessage class ChatStorage(ABC): def is_consecutive_message(self, conversation: List[ConversationMessage], new_message: ConversationMessage) -> bool: if not conversation: return False last_message = conversation[-1] return last_message.role == new_message.role def trim_conversation(self, conversation: List[ConversationMessage], max_history_size: Optional[int] = None) -> List[ConversationMessage]: if max_history_size is None: return conversation # Ensure max_history_size is even to maintain complete binoms adjusted_max_history_size = max_history_size if max_history_size % 2 == 0 else max_history_size - 1 return conversation[-adjusted_max_history_size:] @abstractmethod async def save_chat_message( self, user_id: str, session_id: str, agent_id: str, new_message: ConversationMessage, max_history_size: Optional[int] = None ) -> List[ConversationMessage]: pass @abstractmethod async def fetch_chat( self, user_id: str, session_id: str, agent_id: str, max_history_size: Optional[int] = None ) -> List[ConversationMessage]: pass @abstractmethod async def fetch_all_chats( self, user_id: str, session_id: str ) -> List[ConversationMessage]: pass ``` The `ChatStorage` class now includes two helper methods: 1. `isConsecutiveMessage` (TypeScript) / `is_consecutive_message` (Python): Checks if a new message is consecutive to the last message in the conversation. 2. `trimConversation` (TypeScript) / `trim_conversation` (Python): Trims the conversation history to the specified maximum size, ensuring an even number of messages. The three main abstract methods are: 1. `saveChatMessage` (TypeScript) / `save_chat_message` (Python): Saves a new message to the storage. 2. `fetchChat` (TypeScript) / `fetch_chat` (Python): Retrieves messages for a specific conversation. 3. `fetchAllChats` (TypeScript) / `fetch_all_chats` (Python): Retrieves all messages for a user's session. ## Creating a Custom Storage Solution To create a custom storage solution, follow these steps: 1. Create a new class that extends `ChatStorage`. 2. Implement all the abstract methods. 3. Utilize the helper methods `isConsecutiveMessage` and `trimConversation` in your implementation. 4. Add any additional methods or properties specific to your storage solution.
> **Important** > When implementing `fetchAllChats`, concatenate the agent ID with the message text in the response when the role is ASSISTANT: ```text ASSISTANT: [agent-a] Response from agent A USER: Some user input ASSISTANT: [agent-b] Response from agent B ```
Here's an example of a simple custom storage solution using an in-memory dictionary: ```typescript import { ChatStorage, ConversationMessage } from 'agent-squad'; class SimpleInMemoryStorage extends ChatStorage { private storage: { [key: string]: ConversationMessage[] } = {}; async saveChatMessage( userId: string, sessionId: string, agentId: string, newMessage: ConversationMessage, maxHistorySize?: number ): Promise { const key = `${userId}:${sessionId}:${agentId}`; if (!this.storage[key]) { this.storage[key] = []; } if (!this.isConsecutiveMessage(this.storage[key], newMessage)) { this.storage[key].push(newMessage); } this.storage[key] = this.trimConversation(this.storage[key], maxHistorySize); return this.storage[key]; } async fetchChat( userId: string, sessionId: string, agentId: string, maxHistorySize?: number ): Promise { const key = `${userId}:${sessionId}:${agentId}`; const conversation = this.storage[key] || []; return this.trimConversation(conversation, maxHistorySize); } async fetchAllChats( userId: string, sessionId: string ): Promise { const allMessages: ConversationMessage[] = []; for (const key in this.storage) { if (key.startsWith(`${userId}:${sessionId}`)) { const agentId = key.split(':')[2]; for (const message of this.storage[key]) { const newContent = message.content ? [...message.content] : []; if (newContent.length > 0 && message.role === ParticipantRole.ASSISTANT) { newContent[0] = { text: `[${agentId}] ${newContent[0].text}` }; } allMessages.push({ ...message, content: newContent }); } } } return allMessages; } } ``` ```python from typing import List, Optional, Dict from agent_squad.storage import ChatStorage from agent_squad.types import ConversationMessage class SimpleInMemoryStorage(ChatStorage): def __init__(self): self.storage: Dict[str, List[ConversationMessage]] = {} async def save_chat_message( self, user_id: str, session_id: str, agent_id: str, new_message: ConversationMessage, max_history_size: Optional[int] = None ) -> List[ConversationMessage]: key = f"{user_id}:{session_id}:{agent_id}" if key not in self.storage: self.storage[key] = [] if not self.is_consecutive_message(self.storage[key], new_message): self.storage[key].append(new_message) self.storage[key] = self.trim_conversation(self.storage[key], max_history_size) return self.storage[key] async def fetch_chat( self, user_id: str, session_id: str, agent_id: str, max_history_size: Optional[int] = None ) -> List[ConversationMessage]: key = f"{user_id}:{session_id}:{agent_id}" conversation = self.storage.get(key, []) return self.trim_conversation(conversation, max_history_size) async def fetch_all_chats( self, user_id: str, session_id: str ) -> List[ConversationMessage]: all_messages = [] prefix = f"{user_id}:{session_id}" for key, messages in self.storage.items(): if key.startswith(prefix): agent_id = key.split(':')[2] for message in messages: new_content = message.content if message.content else [] if len(new_content) > 0 and message.role == ParticipantRole.ASSISTANT: new_content[0] = {'text': f"[{agent_id}] {new_content[0]['text']}"} all_messages.append( ConversationMessage( role=message.role, content=new_content ) ) return sorted(all_messages, key=lambda m: getattr(m, 'timestamp', 0)) ``` ## Using Your Custom Storage To use your custom storage with the Agent Squad: ```typescript const customStorage = new SimpleInMemoryStorage(); const orchestrator = new AgentSquad({ storage: customStorage }); ``` ```python from agent_squad.orchestrator import AgentSquad from your_custom_storage_module import SimpleInMemoryStorage custom_storage = SimpleInMemoryStorage() orchestrator = AgentSquad(storage=custom_storage) ``` By extending the `ChatStorage` class, you can create custom storage solutions tailored to your specific needs, whether it's integrating with a particular database system, implementing caching mechanisms, or adapting to unique architectural requirements. Remember to consider factors such as scalability, persistence, and error handling when implementing your custom storage solution for production use. The helper methods `isConsecutiveMessage` and `trimConversation` can be particularly useful for managing conversation history effectively. ================================================ FILE: docs/src/content/docs/storage/dynamodb.mdx ================================================ --- title: DynamoDB Storage description: Using Amazon DynamoDB for persistent conversation storage in the Agent Squad System --- DynamoDB storage provides a scalable and persistent solution for storing conversation history in the Agent Squad System. This option is ideal for production environments where long-term data retention and high availability are crucial. ## Features - Persistent storage across application restarts - Scalable to handle large volumes of conversation data - Integrated with AWS services for robust security and management ## When to Use DynamoDB Storage - In production environments - When long-term persistence of conversation history is required - For applications that need to scale horizontally ## Python Package If you haven't already installed the AWS-related dependencies, make sure to install them: ```bash pip install "agent-squad[aws]" ``` ## Implementation To use DynamoDB storage in your Agent Squad: 1. Set up a DynamoDB table with the following schema: - Partition Key: `PK` (String) - Sort Key: `SK` (String) - Additionally, you can also set up your DynamoDB table with a TTL Key to automatically delete older conversation items 2. Use the DynamoDbChatStorage when creating your orchestrator: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { DynamoDbChatStorage, AgentSquad } from 'agent-squad'; const tableName = 'YourDynamoDBTableName'; const region = 'your-aws-region'; const TTL_DURATION = 3600; // in seconds const dynamoDbStorage = new DynamoDbChatStorage(tableName, region, 'your-ttl-key-name', TTL_DURATION); const orchestrator = new AgentSquad({ storage: dynamoDbStorage }); ``` ```python from agent_squad.storage import DynamoDbChatStorage from agent_squad.orchestrator import AgentSquad table_name = 'YourDynamoDBTableName' region = 'your-aws-region' TTL_DURATION = 3600 # in seconds dynamodb_storage = DynamoDbChatStorage(table_name, region, ttl_key='your-ttl-key-name', ttl_duration=TTL_DURATION) orchestrator = AgentSquad(storage=dynamodb_storage) ``` ## Configuration Ensure your AWS credentials are properly set up and that your application has the necessary permissions to access the DynamoDB table. ## Considerations - Requires AWS account and proper IAM permissions - May incur costs based on usage and data storage - Read and write operations may have higher latency compared to in-memory storage ## Best Practices - Use DynamoDB storage for production deployments - Implement proper error handling for network-related issues - Consider implementing a caching layer for frequently accessed data to optimize performance - Regularly backup your DynamoDB table to prevent data loss DynamoDB storage offers a robust and scalable solution for managing conversation history in production environments. It ensures data persistence and allows your Agent Squad System to handle large-scale deployments with reliable data storage and retrieval capabilities. ================================================ FILE: docs/src/content/docs/storage/in-memory.mdx ================================================ --- title: In-Memory Storage description: Using in-memory storage for conversation history in the Agent Squad System --- In-memory storage is the default storage option for the Agent Squad System. It provides a quick and efficient way to store conversation history, making it ideal for development, testing, or scenarios where long-term persistence is not required. ## Features - Fast read and write operations - No additional setup or external dependencies - Perfect for local development and testing environments ## When to Use In-Memory Storage - During development and testing phases - For applications with short-lived sessions - When persistence across application restarts is not necessary ## Implementation To use in-memory storage in your Agent Squad: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { MemoryStorage, AgentSquad } from 'agent-squad'; const memoryStorage = new InMemoryChatStorage(); const orchestrator = new AgentSquad(memoryStorage); ``` ```python from agent_squad.storage import InMemoryChatStorage from agent_squad.orchestrator import AgentSquad memory_storage = InMemoryChatStorage() orchestrator = AgentSquad(storage=memory_storage) ``` ## Considerations - Data is lost when the application restarts or crashes - Not suitable for distributed systems or applications requiring data persistence - Limited by available memory on the host machine ## Best Practices - Use in-memory storage for rapid prototyping and development - Implement proper error handling to manage potential memory constraints - Consider switching to a persistent storage option like DynamoDB for production deployments In-memory storage provides a straightforward and efficient solution for managing conversation history in scenarios where long-term data persistence is not a requirement. It allows for quick setup and is particularly useful during the development and testing phases of your Agent Squad System implementation. ================================================ FILE: docs/src/content/docs/storage/overview.md ================================================ --- title: Storage overview description: An overview of conversation storage options in the Agent Squad System --- The Agent Squad System offers flexible storage options for maintaining conversation history. This allows the system to preserve context across multiple interactions and enables agents to provide more coherent and contextually relevant responses. ## Key Concepts - Each conversation is uniquely identified by a combination of `userId`, `sessionId`, and `agentId`. - The storage system saves both user messages and assistant responses. - Different storage backends are supported through the `ConversationStorage` interface. ## Available Storage Options 1. **In-Memory Storage**: - Ideal for development, testing, or scenarios where persistence isn't required. - Quick and efficient for short-lived sessions. 2. **DynamoDB Storage**: - Provides persistent storage for production environments. - Allows for scalable and durable conversation history storage. 3. **SQL Storage**: - Offers persistent storage using SQLite or Turso databases. - When you need local-first development with remote deployment options 4. **Custom Storage Solutions**: - The system allows for implementation of custom storage options to meet specific needs. ## Choosing the Right Storage Option - Use In-Memory Storage for development, testing, or when persistence between application restarts is not necessary. - Choose DynamoDB Storage for production environments where conversation history needs to be preserved long-term or across multiple instances of your application. - Consider SQL Storage for a balance between simplicity and scalability, supporting both local and remote databases. - Implement a custom storage solution if you have specific requirements not met by the provided options. ## Next Steps - Learn more about [In-Memory Storage](/agent-squad/storage/in-memory) - Explore [DynamoDB Storage](/agent-squad/storage/dynamodb) for persistent storage - Explore [SQL Storage](/agent-squad/storage/sql) for persistent storage using SQLite or Turso. - Discover how to [implement custom storage solutions](/agent-squad/storage/custom) By leveraging these storage options, you can ensure that your Agent Squad System maintains the necessary context for coherent and effective conversations across various use cases and deployment scenarios. ================================================ FILE: docs/src/content/docs/storage/sql.mdx ================================================ --- title: SQL Storage description: Using SQL databases (SQLite/Turso) for persistent conversation storage in the Agent Squad System --- SQL storage provides a flexible and reliable solution for storing conversation history in the Agent Squad System. This implementation supports both local SQLite databases and remote Turso databases, making it suitable for various deployment scenarios from development to production. ## Features - Persistent storage across application restarts - Support for both local and remote databases - Built-in connection pooling and retry mechanisms - Compatible with edge and serverless deployments - Transaction support for data consistency - Efficient indexing for quick data retrieval ## When to Use SQL Storage - When you need a balance between simplicity and scalability - For applications requiring persistent storage without complex infrastructure - In both development and production environments - When working with edge or serverless deployments - When you need local-first development with remote deployment options ## Python Package Installation To use SQL storage in your Python application, make sure to install them: ```bash pip install "agent-squad[sql]" ``` This will install the `libsql-client` package required for SQL storage functionality. ## Implementation To use SQL storage in your Agent Squad: import { Tabs, TabItem } from '@astrojs/starlight/components'; ```typescript import { SqlChatStorage, AgentSquad } from 'agent-squad'; // For local SQLite database const localStorage = new SqlChatStorage('file:local.db'); await localStorage.waitForInitialization(); // For remote database const remoteStorage = new SqlChatStorage( 'libsql://your-database-url.example.com', 'your-auth-token' ); await remoteStorage.waitForInitialization(); const orchestrator = new AgentSquad({ storage: localStorage // or remoteStorage }); // Close the database connections when done await localStorage.close(); await remoteStorage.close(); ``` ```python from agent_squad.storage import SqlChatStorage from agent_squad.orchestrator import AgentSquad # For local SQLite database local_storage = SqlChatStorage('file:local.db') await local_storage.initialize() # Must be called before use # For remote Turso database remote_storage = SqlChatStorage( url='libsql://your-database-url.turso.io', auth_token='your-auth-token' ) await remote_storage.initialize() # Create orchestrator with storage orchestrator = AgentSquad(storage=local_storage) # or remote_storage # Example usage messages = await local_storage.save_chat_message( user_id="user123", session_id="session456", agent_id="agent789", new_message=ConversationMessage( role="user", content=[{"text": "Hello!"}] ) ) # messages will contain the updated conversation history # Don't forget to close connections when done await local_storage.close() ``` ## Configuration ### Local DB For local development, simply provide a file URL: ```typescript const storage = new SqlChatStorage('file:local.db'); ``` ```python storage = SqlChatStorage('file:local.db') await storage.initialize() # Must be called before use ``` ### Remote DB For production with Turso: 1. Create a Turso database through their platform 2. Obtain your database URL and authentication token 3. Configure your storage: ```typescript const storage = new SqlChatStorage( 'libsql://your-database-url.turso.io', 'your-auth-token' ); ``` ```python storage = SqlChatStorage( url='libsql://your-database-url.turso.io', auth_token='your-auth-token' ) await storage.initialize() # Required initialization ``` ## Database Schema The SQL storage implementation uses the following schema: ```sql CREATE TABLE conversations ( user_id TEXT NOT NULL, session_id TEXT NOT NULL, agent_id TEXT NOT NULL, message_index INTEGER NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, timestamp INTEGER NOT NULL, PRIMARY KEY (user_id, session_id, agent_id, message_index) ); CREATE INDEX idx_conversations_lookup ON conversations(user_id, session_id, agent_id); ``` ## Considerations - Automatic table and index creation on initialization - Built-in transaction support for data consistency - Efficient query performance through proper indexing - Support for message history size limits - Automatic JSON serialization/deserialization of message content ## Best Practices (Python) 1. **Initialization**: ```python storage = SqlChatStorage('file:local.db') await storage.initialize() # Always call initialize after creation ``` 2. **Error Handling**: ```python try: messages = await storage.save_chat_message(...) except Exception as e: logger.error(f"Storage error: {e}") ``` 3. **Resource Cleanup**: ```python try: storage = SqlChatStorage('file:local.db') await storage.initialize() # ... use storage ... finally: await storage.close() # Always close when done ``` 4. **Message History Management**: ```python # Limit conversation history messages = await storage.save_chat_message( ..., max_history_size=50 # Keep last 50 messages ) ``` 5. **Batch Operations**: ```python # Save multiple messages efficiently messages = await storage.save_chat_messages( user_id="user123", session_id="session456", agent_id="agent789", new_messages=[message1, message2, message3] ) ``` SQL storage provides a robust and flexible solution for managing conversation history in the Agent Squad System. It offers a good balance between simplicity and features, making it suitable for both development and production environments. ================================================ FILE: docs/src/env.d.ts ================================================ /// /// ================================================ FILE: docs/src/styles/custom.css ================================================ /* Dark mode colors. */ :root { --sl-color-accent-low: #2c230a; --sl-color-accent: #846500; --sl-color-accent-high: #d4c8ab; --sl-color-white: #ffffff; --sl-color-gray-1: #eceef2; --sl-color-gray-2: #c0c2c7; --sl-color-gray-3: #888b96; --sl-color-gray-4: #545861; --sl-color-gray-5: #353841; --sl-color-gray-6: #24272f; --sl-color-black: #17181c; --sl-label-api-color: #dc8686; --sl-label-api-background-color: #dc8686; --sl-label-version-color: #7ed7c1; --sl-label-version-background-color: #7ed7c1; --sl-label-package-color: #ffdfd3; --sl-label-package-background-color: #ffdfd3; } /* Light mode colors. */ :root[data-theme='light'] { --sl-color-accent-low: #dfd6c0; --sl-color-accent: #a90202; --sl-color-accent-high: #3f3003; --sl-color-white: #17181c; --sl-color-gray-1: #24272f; --sl-color-gray-2: #353841; --sl-color-gray-3: #545861; --sl-color-gray-4: #888b96; --sl-color-gray-5: #c0c2c7; --sl-color-gray-6: #eceef2; --sl-color-gray-7: #f5f6f8; --sl-color-black: #ffffff; --sl-label-api-color: #c74848; --sl-label-api-background-color: #c74848; --sl-label-version-color: #3cb99a; --sl-label-version-background-color: #3cb99a; --sl-label-package-color: #cf5123; --sl-label-package-background-color: #cf5123; } :root { --purple-hsl: 205, 60%, 60%; --overlay-blurple: hsla(var(--purple-hsl), 0.4); } [data-has-hero] .page { background: linear-gradient(215deg, var(--overlay-blurple), transparent 40%), radial-gradient(var(--overlay-blurple), transparent 40%) no-repeat -60vw -40vh / 105vw 200vh, radial-gradient(var(--overlay-blurple), transparent 65%) no-repeat 50% calc(100% + 20rem) / 60rem 30rem; } [data-has-hero] header { border-bottom: 1px solid transparent; background-color: transparent; -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px); } [data-has-hero] .hero > img { filter: drop-shadow(0 0 3rem var(--overlay-blurple)); } [data-page-title] { font-size: 3rem; } /* date page title onl 2.5rem on mobile devices */ @media (max-width: 768px) { [data-page-title] { font-size: 2.5rem; } } .card-grid > .card { border-radius: 10px; } .card > .title { font-size: 1.3rem; font-weight: 600; line-height: 1.2; } .Label, .label { border: 1px solid; border-radius: 2em; display: inline-block; font-size: 0.75rem; font-weight: 500; line-height: 18px; padding: 0 7px; white-space: nowrap; } .Label > a, .label > a { color: inherit; text-decoration: none; } .Label > a:hover, .label > a:hover { color: inherit; text-decoration: none; } .Label.Label--api { color: var(--sl-label-api-color); border-color: var(--sl-label-api-background-color); } .Label.Label--version { color: var(--sl-label-version-color); border-color: var(--sl-label-version-background-color); } .Label.Label--package { color: var(--sl-label-package-color); border-color: var(--sl-label-package-background-color); } .text-uppercase { text-transform: uppercase !important; } .language-icon { margin-bottom: -8px; float: right; } @media only screen and (max-width: 1023px) { .language-icon { display: none; float: none; } } ================================================ FILE: docs/src/styles/font.css ================================================ @font-face { font-family: "JetBrainsMono NF"; src: url("../assets/fonts/JetBrainsMonoNerdFont-Regular.ttf") format("truetype"); font-weight: 400; font-style: normal; font-display: swap; } @font-face { font-family: "JetBrainsMono NF"; src: url("../assets/fonts/JetBrainsMonoNerdFont-Italic.ttf") format("truetype"); font-weight: 400; font-style: italic; font-display: swap; } @font-face { font-family: "JetBrainsMono NF"; src: url("../assets/fonts/JetBrainsMonoNerdFont-Bold.ttf") format("truetype"); font-weight: 700; font-style: normal; font-display: swap; } @font-face { font-family: "JetBrainsMono NF"; src: url("../assets/fonts/JetBrainsMonoNerdFont-BoldItalic.ttf") format("truetype"); font-weight: 700; font-style: italic; font-display: swap; } :root { --sl-font: "JetBrainsMono NF"; --sl-font-mono: "JetBrainsMono NF"; } ================================================ FILE: docs/src/styles/landing.css ================================================ :root { --sl-hue-accent: 255; --sl-color-accent-low: hsl(var(--sl-hue-accent), 14%, 20%); --sl-color-accent: hsl(var(--sl-hue-accent), 60%, 60%); --sl-color-accent-high: hsl(var(--sl-hue-accent), 60%, 87%); --overlay-blurple: hsla(var(--sl-hue-accent), 60%, 60%, 0.2); } :root[data-theme='light'] { --sl-hue-accent: 45; /*Color of top bar text and icons and Getting Started button*/ --sl-color-accent-high: hsl(var(--sl-hue-accent), 90%, 20%); --sl-color-accent: hsl(var(--sl-hue-accent), 100%, 50%); --sl-color-accent-low: hsl(var(--sl-hue-accent), 98%, 80%); } [data-has-hero] .page { background: linear-gradient(215deg, var(--overlay-blurple), transparent 40%), radial-gradient(var(--overlay-blurple), transparent 40%) no-repeat -60vw -40vh / 105vw 200vh, radial-gradient(var(--overlay-blurple), transparent 65%) no-repeat 50% calc(100% + 20rem) / 60rem 30rem; } [data-has-hero] header { border-bottom: 1px solid transparent; background-color: transparent; -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px); } [data-has-hero] .hero > img { filter: drop-shadow(0 0 3rem var(--overlay-blurple)); } iframe[id='stackblitz-iframe'] { width: 100%; min-height: 600px; } ================================================ FILE: docs/src/styles/terminal.css ================================================ /* Solarized color palette */ :root { --sol-red: #dc322f; --sol-bright-red: #cb4b16; --sol-green: #859900; --sol-yellow: #b58900; --sol-blue: #268bd2; --sol-magenta: #d33682; --sol-bright-magenta: #6c71c4; --sol-cyan: #2aa198; --sol-base03: #002b36; --sol-base02: #073642; --sol-base00: #657b83; --sol-base0: #839496; --sol-base2: #eee8d5; --sol-base3: #fdf6e3; } pre.terminal { --black: var(--sol-base02); --red: var(--sol-red); --bright-red: var(--sol-bright-red); --green: var(--sol-green); --yellow: var(--sol-yellow); --blue: var(--sol-blue); --magenta: var(--sol-magenta); --bright-magenta: var(--sol-bright-magenta); --cyan: var(--sol-cyan); --white: var(--sol-base2); background-color: var(--sol-base03); color: var(--sol-base0); font-family: var(--__sl-font-mono); } :root[data-theme="light"] pre.terminal { background-color: var(--sol-base3); color: var(--sol-base00); } pre.terminal p { margin: -0.75rem -1rem; padding: 0.75rem 1rem; overflow-x: auto; } pre.astro-code + pre.terminal { margin-top: 0; border-top-width: 0; } ================================================ FILE: docs/tsconfig.json ================================================ { "extends": "astro/tsconfigs/strict" } ================================================ FILE: examples/bedrock-flows/python/main.py ================================================ import asyncio import uuid import sys from typing import Any, List from agent_squad.orchestrator import AgentSquad, AgentSquadConfig from agent_squad.classifiers import ClassifierResult from agent_squad.agents import AgentResponse, Agent, BedrockFlowsAgent, BedrockFlowsAgentOptions from agent_squad.types import ConversationMessage, ParticipantRole async def handle_request(_orchestrator: AgentSquad,agent:Agent, _user_input:str, _user_id:str, _session_id:str): classifier_result = ClassifierResult(selected_agent=agent, confidence=1.0) response:AgentResponse = await _orchestrator.agent_process_request( _user_input, _user_id, _session_id, classifier_result) print(response.output.content[0].get('text')) def flow_input_encoder(agent:Agent, input: str, **kwargs) -> Any: global flow_tech_agent if agent == flow_tech_agent: chat_history:List[ConversationMessage] = kwargs.get('chat_history', []) chat_history_string = '\n'.join(f"{message.role}:{message.content[0].get('text')}" for message in chat_history) return { "question": input, "history":chat_history_string } else: return input def flow_output_decode(agent:Agent, response: Any, **kwargs) -> Any: global flow_tech_agent if agent == flow_tech_agent: return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{'text': response}] ) else: return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{'text': response}] ) if __name__ == "__main__": # Initialize the orchestrator with some options orchestrator = AgentSquad(options=AgentSquadConfig( LOG_AGENT_CHAT=True, LOG_CLASSIFIER_CHAT=True, LOG_CLASSIFIER_RAW_OUTPUT=True, LOG_CLASSIFIER_OUTPUT=True, LOG_EXECUTION_TIMES=True, MAX_RETRIES=3, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=False, MAX_MESSAGE_PAIRS_PER_AGENT=10 )) flow_tech_agent = BedrockFlowsAgent(BedrockFlowsAgentOptions( name="tech-agent", description="Specializes in handling tech questions about AWS services", flowIdentifier='BEDROCK-FLOW-ID', flowAliasIdentifier='BEDROCK-FLOW-ALIAS-ID', enableTrace=False, flow_input_encoder=flow_input_encoder, flow_output_decoder=flow_output_decode )) orchestrator.add_agent(flow_tech_agent) USER_ID = "user123" SESSION_ID = str(uuid.uuid4()) print("Welcome to the interactive Multi-Agent system. Type 'quit' to exit.") while True: # Get user input user_input = input("\nYou: ").strip() if user_input.lower() == 'quit': print("Exiting the program. Goodbye!") sys.exit() # Run the async function asyncio.run(handle_request(orchestrator, flow_tech_agent, user_input, USER_ID, SESSION_ID)) ================================================ FILE: examples/bedrock-flows/readme.md ================================================ # BedrockFlowsAgent Example This example demonstrates how to use the **[BedrockFlowsAgent](https://awslabs.github.io/agent-squad/agents/built-in/bedrock-flows-agent/)** for direct agent invocation, avoiding the multi-agent orchestration when you only need a single specialized agent. ## Direct Agent Usage Call your agent directly using: Python: ```python response = await orchestrator.agent_process_request( user_input, user_id, session_id, classifier_result ) ``` TypeScript: ```typescript const response = await orchestrator.agentProcessRequest( userInput, userId, sessionId, classifierResult ) ``` This approach leverages the BedrockFlowsAgent's capabilities: - Conversation history management - Bedrock Flow integration - Custom input/output encoding ### Tech Agent Flow Configuration The example flow connects: - Input node → Prompt node → Output node The prompt node accepts: - question (current question) - history (previous conversation) ![tech-agent-flow](./tech-agent-flow.png) ![prompt-node-configuration](./prompt-config.png) 📝 **Note** 📅 As of December 2, 2024, Bedrock Flows does not include built-in memory management. See the code samples above for complete implementation details. --- *Note: For multi-agent scenarios, add your agents to the orchestrator and use `orchestrator.route_request` (Python) or `orchestrator.routeRequest` (TypeScript) to enable classifier-based routing.* ================================================ FILE: examples/bedrock-flows/typescript/main.ts ================================================ import readline from "readline"; import { AgentSquad, Logger, BedrockFlowsAgent, Agent, } from "agent-squad"; const flowInputEncoder = ( agent: Agent, input: string, kwargs: { userId?: string, sessionId?: string, chatHistory?: any[], [key: string]: any // This allows any additional properties } ) => { const chat_history_string = kwargs.chatHistory?.map((message: { role: string; content: { text?: string }[] }) => `${message.role}:${message.content[0]?.text || ''}` ) .join('\n'); if (agent == flowTechAgent){ return { "question":input, "history":chat_history_string }; } else { return input } } const flowTechAgent = new BedrockFlowsAgent({ name: "Tech Agent", description: "Specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.", flowIdentifier:'BEDROCK-FLOW-ID', flowAliasIdentifier:'BEDROCK-FLOW-ALIAS-ID', flowInputEncoder: flowInputEncoder }); function createOrchestrator(): AgentSquad { const orchestrator = new AgentSquad({ config: { LOG_AGENT_CHAT: true, LOG_EXECUTION_TIMES: true, MAX_MESSAGE_PAIRS_PER_AGENT: 10, }, logger: console, }); // Add a Tech Agent to the orchestrator orchestrator.addAgent( flowTechAgent ); return orchestrator; } const uuidv4 = () => { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); }; // Function to run local conversation async function runLocalConversation(): Promise { const orchestrator = createOrchestrator(); // Generate random uuid 4 const userId = uuidv4(); const sessionId = uuidv4(); const allAgents = orchestrator.getAllAgents(); Logger.logger.log("Here are the existing agents:"); for (const agentKey in allAgents) { const agent = allAgents[agentKey]; Logger.logger.log(`Name: ${agent.name}`); Logger.logger.log(`Description: ${agent.description}`); Logger.logger.log("--------------------"); } orchestrator.analyzeAgentOverlap(); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); Logger.logger.log( "Welcome to the interactive AI agent. Type your queries and press Enter. Type 'exit' to end the conversation." ); const askQuestion = (): void => { rl.question("You: ", async (userInput: string) => { if (userInput.toLowerCase() === "exit") { Logger.logger.log("Thank you for using the AI agent. Goodbye!"); rl.close(); return; } try { const response = await orchestrator.agentProcessRequest( userInput, userId, sessionId, { selectedAgent:flowTechAgent, confidence:1.0 } ); // Handle non-streaming response (AgentProcessingResult) Logger.logger.log("\n** RESPONSE ** \n"); Logger.logger.log(`> Agent ID: ${response.metadata.agentId}`); Logger.logger.log(`> Agent Name: ${response.metadata.agentName}`); Logger.logger.log(`> User Input: ${response.metadata.userInput}`); Logger.logger.log(`> User ID: ${response.metadata.userId}`); Logger.logger.log(`> Session ID: ${response.metadata.sessionId}`); Logger.logger.log( `> Additional Parameters:`, response.metadata.additionalParams ); Logger.logger.log(`\n> Response: ${response.output}`); } catch (error) { Logger.logger.error("Error:", error); } askQuestion(); // Continue the conversation }); }; askQuestion(); // Start the conversation } // Check if this script is being run directly (not imported as a module) if (require.main === module) { // This block will only run when the script is executed locally runLocalConversation(); } ================================================ FILE: examples/bedrock-inline-agents/python/main.py ================================================ import asyncio import uuid import sys from agent_squad.agents import BedrockInlineAgent, BedrockInlineAgentOptions import boto3 action_groups_list = [ { 'actionGroupName': 'CodeInterpreterAction', 'parentActionGroupSignature': 'AMAZON.CodeInterpreter', 'description':'Use this to write and execute python code to answer questions and other tasks.' }, { "actionGroupExecutor": { "lambda": "arn:aws:lambda:region:0123456789012:function:my-function-name" }, "actionGroupName": "MyActionGroupName", "apiSchema": { "s3": { "s3BucketName": "bucket-name", "s3ObjectKey": "openapi-schema.json" } }, "description": "My action group for doing a specific task" } ] knowledge_bases = [ { "knowledgeBaseId": "knowledge-base-id-01", "description": 'This is my knowledge base for documents 01', }, { "knowledgeBaseId": "knowledge-base-id-02", "description": 'This is my knowledge base for documents 02', }, { "knowledgeBaseId": "knowledge-base-id-0", "description": 'This is my knowledge base for documents 03', } ] bedrock_inline_agent = BedrockInlineAgent(BedrockInlineAgentOptions( name="Inline Agent Creator for Agents for Amazon Bedrock", region='us-east-1', model_id="anthropic.claude-3-haiku-20240307-v1:0", description="Specalized in creating Agent to solve customer request dynamically. You are provided with a list of Action groups and Knowledge bases which can help you in answering customer request", action_groups_list=action_groups_list, bedrock_agent_client=boto3.client('bedrock-agent-runtime', region_name='us-east-1'), client=boto3.client('bedrock-runtime', region_name='us-west-2'), knowledge_bases=knowledge_bases, enableTrace=True )) async def run_inline_agent(user_input, user_id, session_id): response = await bedrock_inline_agent.process_request(user_input, user_id, session_id, [], None) return response if __name__ == "__main__": session_id = str(uuid.uuid4()) user_id = str(uuid.uuid4()) print("Welcome to the interactive Multi-Agent system. Type 'quit' to exit.") while True: # Get user input user_input = input("\nYou: ").strip() if user_input.lower() == 'quit': print("Exiting the program. Goodbye!") sys.exit() # Run the async function response = asyncio.run(run_inline_agent(user_input=user_input, user_id=user_id, session_id=session_id)) print(response.content[0].get('text','No response')) ================================================ FILE: examples/bedrock-inline-agents/typescript/main.ts ================================================ //import { BedrockInlineAgent, BedrockInlineAgentOptions } from 'agent-squad'; import { BedrockInlineAgent, BedrockInlineAgentOptions } from '../../../typescript/src/agents/bedrockInlineAgent'; import { BedrockAgentRuntimeClient, AgentActionGroup, KnowledgeBase } from "@aws-sdk/client-bedrock-agent-runtime"; import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"; import { v4 as uuidv4 } from 'uuid'; import { createInterface } from 'readline'; // Define action groups const actionGroupsList: AgentActionGroup[] = [ { actionGroupName: 'CodeInterpreterAction', parentActionGroupSignature: 'AMAZON.CodeInterpreter', description: 'Use this to write and execute python code to answer questions and other tasks.' }, { actionGroupExecutor: { lambda: "arn:aws:lambda:region:0123456789012:function:my-function-name" }, actionGroupName: "MyActionGroupName", apiSchema: { s3: { s3BucketName: "bucket-name", s3ObjectKey: "openapi-schema.json" } }, description: "My action group for doing a specific task" } ]; // Define knowledge bases const knowledgeBases: KnowledgeBase[] = [ { knowledgeBaseId: "knowledge-base-id-01", description: 'This is my knowledge base for documents 01', }, { knowledgeBaseId: "knowledge-base-id-02", description: 'This is my knowledge base for documents 02', }, { knowledgeBaseId: "knowledge-base-id-03", description: 'This is my knowledge base for documents 03', } ]; // Initialize BedrockInlineAgent const bedrickInlineAgent = new BedrockInlineAgent({ name: "Inline Agent Creator for Agents for Amazon Bedrock", region: 'us-east-1', modelId: "anthropic.claude-3-haiku-20240307-v1:0", description: "Specialized in creating Agent to solve customer request dynamically. You are provided with a list of Action groups and Knowledge bases which can help you in answering customer request", actionGroupsList: actionGroupsList, knowledgeBases: knowledgeBases, LOG_AGENT_DEBUG_TRACE: true }); async function runInlineAgent(userInput: string, userId: string, sessionId: string) { const response = await bedrickInlineAgent.processRequest( userInput, userId, sessionId, [], // empty chat history undefined // no additional params ); return response; } async function main() { const sessionId = uuidv4(); const userId = uuidv4(); console.log("Welcome to the interactive Multi-Agent system. Type 'quit' to exit."); const readline = createInterface({ input: process.stdin, output: process.stdout }); const getUserInput = () => { return new Promise((resolve) => { readline.question('\nYou: ', (input) => { resolve(input.trim()); }); }); }; while (true) { const userInput = await getUserInput() as string; if (userInput.toLowerCase() === 'quit') { console.log("Exiting the program. Goodbye!"); readline.close(); process.exit(0); } try { const response = await runInlineAgent(userInput, userId, sessionId); if (response && response.content && response.content.length > 0) { const text = response.content[0]?.text; console.log(text || 'No response content'); } else { console.log('No response'); } } catch (error) { console.error('Error:', error); } } } // Run the program main().catch(console.error); ================================================ FILE: examples/bedrock-prompt-routing/main.py ================================================ import uuid import asyncio import os from typing import Optional, Any import json import sys from agent_squad.orchestrator import AgentSquad, AgentSquadConfig from agent_squad.agents import (BedrockLLMAgent, BedrockLLMAgentOptions, AgentResponse, AgentCallbacks) from agent_squad.types import ConversationMessage, ParticipantRole from agent_squad.classifiers import BedrockClassifier, BedrockClassifierOptions class LLMAgentCallbacks(AgentCallbacks): def on_llm_new_token(self, token: str) -> None: # handle response streaming here print(token, end='', flush=True) async def handle_request(_orchestrator: AgentSquad, _user_input:str, _user_id:str, _session_id:str): response:AgentResponse = await _orchestrator.route_request(_user_input, _user_id, _session_id) # Print metadata print("\nMetadata:") print(f"Selected Agent: {response.metadata.agent_name}") if isinstance(response, AgentResponse) and response.streaming is False: # Handle regular response if isinstance(response.output, str): print(response.output) elif isinstance(response.output, ConversationMessage): print(response.output.content[0].get('text')) def custom_input_payload_encoder(input_text: str, chat_history: list[Any], user_id: str, session_id: str, additional_params: Optional[dict[str, str]] = None) -> str: return json.dumps({ 'hello':'world' }) def custom_output_payload_decoder(response: dict[str, Any]) -> Any: decoded_response = json.loads( json.loads( response['Payload'].read().decode('utf-8') )['body'])['response'] return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{'text': decoded_response}] ) if __name__ == "__main__": # Initialize the orchestrator with some options orchestrator = AgentSquad(options=AgentSquadConfig( LOG_AGENT_CHAT=True, LOG_CLASSIFIER_CHAT=True, LOG_CLASSIFIER_RAW_OUTPUT=True, LOG_CLASSIFIER_OUTPUT=True, LOG_EXECUTION_TIMES=True, MAX_RETRIES=3, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=True, MAX_MESSAGE_PAIRS_PER_AGENT=10, ), classifier=BedrockClassifier(BedrockClassifierOptions( model_id=f"arn:aws:bedrock:us-east-1:{os.getenv('AWS_ACCOUNT_ID')}:default-prompt-router/anthropic.claude:1")) ) # Add some agents tech_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", streaming=True, description="Specializes in technology areas including software development, hardware, AI, \ cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs \ related to technology products and services.", model_id="anthropic.claude-3-sonnet-20240229-v1:0", callbacks=LLMAgentCallbacks() )) orchestrator.add_agent(tech_agent) # Add some agents health_agent = BedrockLLMAgent(BedrockLLMAgentOptions( name="Health Agent", streaming=False, model_id=f"arn:aws:bedrock:us-east-1:{os.getenv('AWS_ACCOUNT_ID')}:default-prompt-router/anthropic.claude:1", description="Specialized agent for giving health advice.", callbacks=LLMAgentCallbacks() )) orchestrator.add_agent(health_agent) USER_ID = "user123" SESSION_ID = str(uuid.uuid4()) print("Welcome to the interactive Multi-Agent system. Type 'quit' to exit.") while True: # Get user input user_input = input("\nYou: ").strip() if user_input.lower() == 'quit': print("Exiting the program. Goodbye!") sys.exit() # Run the async function asyncio.run(handle_request(orchestrator, user_input, USER_ID, SESSION_ID)) ================================================ FILE: examples/bedrock-prompt-routing/readme.md ================================================ # Bedrock Prompt Routing Example This guide demonstrates how to implement and utilize [Amazon Bedrock Prompt Routing](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-routing.html) functionality with your `BedrockClassifier` or `BedrockLLMAgent`. Prompt routing helps optimize model selection based on your input patterns, improving both performance and cost-effectiveness. ## Prerequisites Before running this example, ensure you have: - An active AWS account with Bedrock access - Python installed on your system (version 3.11 or higher recommended) - The AWS SDK for Python (Boto3) installed - Appropriate IAM permissions configured for Bedrock access ## Installation First, install the required dependencies by running: ```bash pip install boto3 agent-squad ``` export your AWS_ACCOUNT_ID variable by running: ```bash export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) ``` ## Running the Example ```bash python main.py ``` ================================================ FILE: examples/chat-chainlit-app/.gitignore ================================================ .chainlit/* __pycache__/* .venv/* .env ================================================ FILE: examples/chat-chainlit-app/README.md ================================================ To set up and run the application first install dependencies from `requirements.txt` file, follow these steps: ### Prerequisites - Ensure you have Python installed on your system. It's recommended to use Python 3.7 or higher. - Make sure you have `pip`, the Python package installer, available. - Make sure you have [`ollama`](https://ollama.com/) installed and running the model specified in `ollamaAgent.py` ### Steps 1. **Clone the Repository (if necessary)** If you haven't already, clone the repository containing the application code to your local machine. ```bash git clone cd ``` 2. **Create a Virtual Environment (Optional but Recommended)** It's a good practice to use a virtual environment to manage dependencies for your project. ```bash python -m venv venv ``` Activate the virtual environment: - On Windows: ```bash venv\Scripts\activate ``` - On macOS and Linux: ```bash source venv/bin/activate ``` 3. **Install Dependencies** Use the `requirements.txt` file to install the necessary Python packages. ```bash pip install -r requirements.txt ``` 4. **Run the Application** Use the `chainlit` command to run the application. ```bash chainlit run app.py -w ``` ### Additional Information - Ensure that any environment variables or configuration files needed by `agent_squad` or other components are properly set up. - If you encounter any issues with package installations, ensure that your Python and pip versions are up to date. By following these steps, you should be able to install the necessary dependencies and run the application successfully. ### Sample test questions - What are some best places to visit in Seattle? - This should route to travel agent on Bedrock - What are some cool tech companies in Seattle - This should route to tech agent on Bedrock - What kind of pollen is causing allergies in Seattle? - This should health agent running local machine ollama - (Ask a followup quesiton to the Travel agent by referring to some context in first response) ================================================ FILE: examples/chat-chainlit-app/agents.py ================================================ from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions, AgentCallbacks from ollamaAgent import OllamaAgent, OllamaAgentOptions import asyncio import chainlit as cl class ChainlitAgentCallbacks(AgentCallbacks): def on_llm_new_token(self, token: str) -> None: asyncio.run(cl.user_session.get("current_msg").stream_token(token)) def create_tech_agent(): return BedrockLLMAgent(BedrockLLMAgentOptions( name="Tech Agent", streaming=True, description="Specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services.", model_id="anthropic.claude-3-sonnet-20240229-v1:0", callbacks=ChainlitAgentCallbacks() )) def create_travel_agent(): return BedrockLLMAgent(BedrockLLMAgentOptions( name="Travel Agent", streaming=True, description="Experienced Travel Agent sought to create unforgettable journeys for clients. Responsibilities include crafting personalized itineraries, booking flights, accommodations, and activities, and providing expert travel advice. Must have excellent communication skills, destination knowledge, and ability to manage multiple bookings. Proficiency in travel booking systems and a passion for customer service required", model_id="anthropic.claude-3-sonnet-20240229-v1:0", callbacks=ChainlitAgentCallbacks() )) def create_health_agent(): return OllamaAgent(OllamaAgentOptions( name="Health Agent", model_id="llama3.1:latest", description="Specializes in health and wellness, including nutrition, fitness, mental health, and disease prevention. Provides personalized health advice, creates wellness plans, and offers resources for self-care. Must have a strong understanding of human anatomy, physiology, and medical terminology. Proficiency in health coaching techniques and a commitment to promoting overall well-being required.", streaming=True, callbacks=ChainlitAgentCallbacks() )) ================================================ FILE: examples/chat-chainlit-app/app.py ================================================ import uuid import chainlit as cl from agents import create_tech_agent, create_travel_agent, create_health_agent from agent_squad.orchestrator import AgentSquad, AgentSquadConfig from agent_squad.classifiers import BedrockClassifier, BedrockClassifierOptions from agent_squad.types import ConversationMessage from agent_squad.agents import AgentResponse # Initialize the orchestrator custom_bedrock_classifier = BedrockClassifier(BedrockClassifierOptions( model_id='anthropic.claude-3-haiku-20240307-v1:0', inference_config={ 'maxTokens': 500, 'temperature': 0.7, 'topP': 0.9 } )) orchestrator = AgentSquad(options=AgentSquadConfig( LOG_AGENT_CHAT=True, LOG_CLASSIFIER_CHAT=True, LOG_CLASSIFIER_RAW_OUTPUT=True, LOG_CLASSIFIER_OUTPUT=True, LOG_EXECUTION_TIMES=True, MAX_RETRIES=3, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=False, MAX_MESSAGE_PAIRS_PER_AGENT=10 ), classifier=custom_bedrock_classifier ) # Add agents to the orchestrator orchestrator.add_agent(create_tech_agent()) orchestrator.add_agent(create_travel_agent()) orchestrator.add_agent(create_health_agent()) @cl.on_chat_start async def start(): cl.user_session.set("user_id", str(uuid.uuid4())) cl.user_session.set("session_id", str(uuid.uuid4())) cl.user_session.set("chat_history", []) @cl.on_message async def main(message: cl.Message): user_id = cl.user_session.get("user_id") session_id = cl.user_session.get("session_id") msg = cl.Message(content="") await msg.send() # Send the message immediately to start streaming cl.user_session.set("current_msg", msg) response:AgentResponse = await orchestrator.route_request(message.content, user_id, session_id, {}) # Handle non-streaming responses if isinstance(response, AgentResponse) and response.streaming is False: # Handle regular response if isinstance(response.output, str): await msg.stream_token(response.output) elif isinstance(response.output, ConversationMessage): await msg.stream_token(response.output.content[0].get('text')) await msg.update() if __name__ == "__main__": cl.run() ================================================ FILE: examples/chat-chainlit-app/chainlit.md ================================================ # Welcome to Chainlit! 🚀🤖 Hi there, Developer! 👋 We're excited to have you on board. Chainlit is a powerful tool designed to help you prototype, debug and share applications built on top of LLMs. ## Useful Links 🔗 - **Documentation:** Get started with our comprehensive [Chainlit Documentation](https://docs.chainlit.io) 📚 - **Discord Community:** Join our friendly [Chainlit Discord](https://discord.gg/k73SQ3FyUh) to ask questions, share your projects, and connect with other developers! 💬 We can't wait to see what you create with Chainlit! Happy coding! 💻😊 ## Welcome screen To modify the welcome screen, edit the `chainlit.md` file at the root of your project. If you do not want a welcome screen, just leave this file empty. ================================================ FILE: examples/chat-chainlit-app/ollamaAgent.py ================================================ from typing import List, Dict, Optional, AsyncIterable, Any from agent_squad.agents import Agent, AgentOptions from agent_squad.types import ConversationMessage, ParticipantRole from agent_squad.utils import Logger import ollama from dataclasses import dataclass @dataclass class OllamaAgentOptions(AgentOptions): streaming: bool = True model_id: str = "llama3.1:latest", class OllamaAgent(Agent): def __init__(self, options: OllamaAgentOptions): super().__init__(options) self.model_id = options.model_id self.streaming = options.streaming async def handle_streaming_response(self, messages: List[Dict[str, str]]) -> ConversationMessage: text = '' try: response = ollama.chat( model=self.model_id, messages=messages, stream=self.streaming ) for part in response: text += part['message']['content'] await self.callbacks.on_llm_new_token(part['message']['content']) return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": text}] ) except Exception as error: Logger.get_logger().error("Error getting stream from Ollama model:", error) raise error async def process_request( self, input_text: str, user_id: str, session_id: str, chat_history: List[ConversationMessage], additional_params: Optional[Dict[str, str]] = None ) -> ConversationMessage | AsyncIterable[Any]: messages = [ {"role": msg.role, "content": msg.content[0]['text']} for msg in chat_history ] messages.append({"role": ParticipantRole.USER.value, "content": input_text}) if self.streaming: return await self.handle_streaming_response(messages) else: response = ollama.chat( model=self.model_id, messages=messages ) return ConversationMessage( role=ParticipantRole.ASSISTANT.value, content=[{"text": response['message']['content']}] ) ================================================ FILE: examples/chat-chainlit-app/requirements.txt ================================================ chainlit==1.3.2 agent_squad ollama==0.3.3 pydantic==2.10.1 ================================================ FILE: examples/chat-demo-app/.gitignore ================================================ *.js !jest.config.js *.d.ts node_modules dist # CDK asset staging directory .cdk.staging cdk.out !postcss.config.js !vite-env.d.ts !download.js ================================================ FILE: examples/chat-demo-app/.npmignore ================================================ *.ts !*.d.ts # CDK asset staging directory .cdk.staging cdk.out ================================================ FILE: examples/chat-demo-app/README.md ================================================ ## 🎮 Demo Application ### Overview The demo showcases the versatility of the Agent Squad System through an interactive chat interface. ![Demo Application](./img/chat-demo-app.png) ### Featured Agents Our demo showcases specialized agents, each designed for specific use cases: | Agent | Technology | Purpose | |-------|------------|---------| | Travel Agent | Amazon Lex Bot | Handles travel planning, flight bookings, and itinerary queries through a conversational interface | | Weather Agent | Bedrock LLM + Open-Meteo API | Provides real-time weather forecasts and conditions using API integration | | Math Agent | Bedrock LLM + Calculator Tools | Performs complex calculations and solves mathematical problems with custom tools | | **Tech Agent** | Bedrock LLM + Knowledge Base | Offers technical support and documentation assistance with direct access to **Agent Squad framework source code** | | Health Agent | Bedrock LLM | Provides health and wellness guidance, including fitness advice and general health information | The demo highlights the system's ability to handle complex, multi-turn conversations while preserving context and leveraging specialized agents across various domains. ### Key Capabilities - **Context Switching**: Seamlessly handles transitions between different topics - **Multi-turn Conversations**: Maintains context across multiple interactions - **Tool Integration**: Demonstrates API and custom tool usage - **Agent Selection**: Shows intelligent routing to specialized agents - **Follow-up Handling**: Processes brief follow-up queries with context retention ## 📋 Prerequisites Before deploying the demo web app, ensure you have the following: 1. An AWS account with appropriate permissions 2. AWS CLI installed and configured with your credentials 3. Node.js and npm installed on your local machine 4. AWS CDK CLI installed (`npm install -g aws-cdk`) ## 🚀 Deployment Steps Follow these steps to deploy the demo chat web application: 1. **Clone the Repository**: ```bash git clone https://github.com/awslabs/agent-squad.git cd agent-squad ``` 2. **Navigate to the Demo Web App Directory**: ```bash cd examples/chat-demo-app ``` 3. **Install Dependencies**: ```bash npm install ``` 4. **Bootstrap AWS CDK**: ```bash cdk bootstrap ``` 5. **Review and Customize the Stack** (optional): Open `chat-demo-app/cdk.json` and review the configuration. You can customize aspects of the deployment by enabling or disabling additional agents. ```json { "context": { "enableLexAgent": true // Additional configurations } } ``` **enableLexAgent:** Enable the sample Airlines Bot (See AWS Blogpost [here](https://aws.amazon.com/blogs/machine-learning/automate-the-customer-service-experience-for-flight-reservations-using-amazon-lex/)) 6. **Deploy the Application**: ```bash cdk deploy --all ``` 7. **Create a user in Amazon Cognito user pool**: ```bash aws cognito-idp admin-create-user \ --user-pool-id your-region_xxxxxxx \ --username your@email.com \ --user-attributes Name=email,Value=your@email.com \ --temporary-password "MyChallengingPassword" \ --message-action SUPPRESS \ --region your-region ``` ## 🌐 Accessing the Demo Once deployment is complete: 1. Open the URL provided in the CDK outputs in your web browser 2. Log in with the created credentials 3. Start interacting with the multi-agent system ## ✅ Testing the Deployment To ensure the deployment was successful: 1. Open the web app URL in your browser 2. Try different types of queries: - Travel bookings - Weather checks - Math problems - Technical questions - Health inquiries 3. Test follow-up questions to see context retention 4. Observe agent switching for different topics ## 🧹 Cleaning Up To avoid incurring unnecessary AWS charges: ```bash cdk destroy ``` ## 🛠️ Troubleshooting If you encounter issues during deployment: 1. Ensure your AWS credentials are correctly configured 2. Check that you have the necessary permissions in your AWS account 3. Verify that all dependencies are correctly installed 4. Review the AWS CloudFormation console for detailed error messages if the deployment fails ## ➡️ Next Steps After exploring the demo: 1. Customize the web interface in the source code 2. Modify agent configurations to test different scenarios 3. Integrate additional AWS services 4. Develop custom agent implementations ## ⚠️ Disclaimer This demo application is intended solely for demonstration purposes. It is not designed for handling, storing, or processing any kind of Personally Identifiable Information (PII) or personal data. Users are strongly advised not to enter, upload, or use any PII or personal data within this application. Any use of PII or personal data is at the user's own risk and the developers of this application shall not be held responsible for any data breaches, misuse, or any other related issues. Please ensure that all data used in this demo is non-sensitive and anonymized. For production usage, it is crucial to implement proper security measures to protect PII and personal data. This includes obtaining proper permissions from users, utilizing encryption for data both in transit and at rest, and adhering to industry standards and regulations to maximize security. Failure to do so may result in data breaches and other serious security issues. ================================================ FILE: examples/chat-demo-app/bin/chat-demo-app.ts ================================================ #!/usr/bin/env node import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; import { ChatDemoStack } from '../lib/chat-demo-app-stack' import { UserInterfaceStack } from '../lib/user-interface-stack'; const app = new cdk.App(); const chatDemoStack = new ChatDemoStack(app, 'ChatDemoStack', { env: { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT }, crossRegionReferences: true, description: "Agent Squad Chat Demo Application (uksb-2mz8io1d9k)" }); new UserInterfaceStack(app, 'UserInterfaceStack', { env: { region: 'us-east-1', account: process.env.CDK_DEFAULT_ACCOUNT, }, crossRegionReferences: true, description: "Agent Squad User Interface (uksb-2mz8io1d9k)", multiAgentLambdaFunctionUrl: chatDemoStack.multiAgentLambdaFunctionUrl, }); ================================================ FILE: examples/chat-demo-app/cdk.json ================================================ { "app": "npx ts-node --prefer-ts-exts bin/chat-demo-app.ts", "watch": { "include": [ "**" ], "exclude": [ "README.md", "cdk*.json", "**/*.d.ts", "**/*.js", "tsconfig.json", "package*.json", "yarn.lock", "node_modules", "test" ] }, "context": { "enableLexAgent": true, "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, "@aws-cdk/core:target-partitions": [ "aws", "aws-cn" ], "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, "@aws-cdk/aws-iam:minimizePolicies": true, "@aws-cdk/core:validateSnapshotRemovalPolicy": true, "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, "@aws-cdk/core:enablePartitionLiterals": true, "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, "@aws-cdk/aws-iam:standardizedServicePrincipals": true, "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, "@aws-cdk/aws-route53-patters:useCertificate": true, "@aws-cdk/customresources:installLatestAwsSdkDefault": false, "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, "@aws-cdk/aws-redshift:columnId": true, "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, "@aws-cdk/aws-kms:aliasNameRef": true, "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, "@aws-cdk/aws-efs:denyAnonymousAccess": true, "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, "@aws-cdk/aws-eks:nodegroupNameAttribute": true, "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false } } ================================================ FILE: examples/chat-demo-app/jest.config.js ================================================ module.exports = { testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { '^.+\\.tsx?$': 'ts-jest' } }; ================================================ FILE: examples/chat-demo-app/lambda/auth/index.mjs ================================================ import { SecretsManagerClient, GetSecretValueCommand, } from "@aws-sdk/client-secrets-manager"; import { fromBase64 } from "@aws-sdk/util-base64-node"; const client = new SecretsManagerClient({ region: "us-east-1" }); const secretName = "UserPoolSecretConfig"; import * as jose from "jose"; import axios from "axios"; import { SignatureV4 } from "@aws-sdk/signature-v4"; import { fromNodeProviderChain } from "@aws-sdk/credential-providers"; import { HttpRequest } from "@aws-sdk/protocol-http"; const { createHash, createHmac } = await import("node:crypto"); const credentialProvider = fromNodeProviderChain(); const credentials = await credentialProvider(); const REGION = "us-east-1"; const getSecrets = async () => { try { const data = await client.send( new GetSecretValueCommand({ SecretId: secretName }) ); if ("SecretString" in data) { return JSON.parse(data.SecretString); } else if ("SecretBinary" in data) { const buff = fromBase64(data.SecretBinary); return JSON.parse(buff.toString("ascii")); } } catch (err) { console.error("Error fetching secret:", err); throw err; } }; function Sha256(secret) { return secret ? createHmac("sha256", secret) : createHash("sha256"); } async function signRequest(request) { let headers = request.headers; // remove the x-forwarded-for from the signature delete headers["x-forwarded-for"]; if (!request.origin.hasOwnProperty("custom")) throw ( "Unexpected origin type. Expected 'custom'. Got: " + JSON.stringify(request.origin) ); // remove the "behaviour" path from the uri to send to Lambda // ex: /updateBook/1234 => /1234 let uri = request.uri.substring(1); let urisplit = uri.split("/"); urisplit.shift(); // remove the first part (getBooks, createBook, ...) uri = "/" + urisplit.join("/"); request.uri = uri; const hostname = headers["host"][0].value; const region = hostname.split(".")[2]; const path = request.uri + (request.querystring ? "?" + request.querystring : ""); // build the request to sign const req = new HttpRequest({ hostname, path, body: request.body && request.body.data ? Buffer.from(request.body.data, request.body.encoding) : undefined, method: request.method, }); for (const header of Object.values(headers)) { req.headers[header[0].key] = header[0].value; } // sign the request with Signature V4 and the credentials of the edge function itself const signer = new SignatureV4({ credentials, region, service: "lambda", sha256: Sha256, }); const signedRequest = await signer.sign(req); // reformat the headers for CloudFront const signedHeaders = {}; for (const header in signedRequest.headers) { signedHeaders[header.toLowerCase()] = [ { key: header, value: signedRequest.headers[header].toString(), }, ]; } return { ...request, headers: { ...request.headers, ...signedHeaders, }, }; } const getToken = async (authorization) => { return new Promise((resolve, reject) => { try { const [, token] = authorization.split(" "); resolve(token); } catch (error) { reject(error); } }); }; async function verifyToken(authorization) { console.log( "authorization=" + authorization ); const token = await getToken(authorization); console.log("token="+token); const secrets = await getSecrets(); const jwksRes = await axios.get( `https://cognito-idp.${REGION}.amazonaws.com/${secrets.UserPoolID}/.well-known/jwks.json` ); const jwk = jose.createLocalJWKSet(jwksRes.data); try { const { payload } = await jose.jwtVerify(token, jwk, { issuer: `https://cognito-idp.${REGION}.amazonaws.com/${secrets.UserPoolID}`, }); if (payload.client_id === secrets.ClientID) { return true; } } catch (err) { console.log(`token error: ${err.name} ${err.message}`); } return false; } //exports.handler = async function (event) { export const handler = async (event) => { //console.log("event=" + JSON.stringify(event)); try { const request = event.Records[0].cf.request; if(request.method === 'OPTIONS') { console.log("OPTIONS call, return cors headers") return { status: "204", headers: { 'access-control-allow-origin': [{ key: 'Access-Control-Allow-Origin', value: "*", }], 'access-control-request-method': [{ key: 'Access-Control-Request-Method', value: "POST, GET, OPTIONS", }], 'access-control-allow-headers': [{ key: 'Access-Control-Allow-Headers', value: "*", }] }, } } //const authorization = request.headers.authorization[0]?.value; const authorization = request.headers.authorization && request.headers.authorization[0]?.value; //console.log("authorization="+authorization) if (authorization) { const valid = await verifyToken( authorization ); console.log("valid=" + valid); if (valid === true) { const signedRequest = await signRequest(request); console.info("signed request=" + JSON.stringify(signedRequest)); return signedRequest; } else { return { status: "400", statusDescription: "Bad Request", body: "Invalid token", }; } } else { console.log("No token found in Authorization header") return { status: "400", statusDescription: "Bad Request", body: "No token found in Authorization header", }; } } catch (e) { console.log("Unknown error") return { status: "400", statusDescription: "Bad Request", body: "Bad request", }; } }; ================================================ FILE: examples/chat-demo-app/lambda/auth/package.json ================================================ { "name": "auth", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "axios": "^1.6.0", "jose": "5.2.3" } } ================================================ FILE: examples/chat-demo-app/lambda/find-my-name/lambda.py ================================================ import json def lambda_handler(event, context): print(event) return { 'statusCode': 200, 'body': json.dumps({'response':'your name is Agent Squad!'}) } ================================================ FILE: examples/chat-demo-app/lambda/multi-agent/index.ts ================================================ import { Logger } from "@aws-lambda-powertools/logger"; import { AgentSquad, BedrockLLMAgent, DynamoDbChatStorage, LexBotAgent, AmazonKnowledgeBasesRetriever, LambdaAgent, BedrockClassifier, } from "agent-squad"; import { weatherToolDescription, weatherToolHanlder } from './weather_tool' import { mathToolHanlder, mathAgentToolDefinition } from './math_tool'; import { APIGatewayProxyEventV2, Handler, Context } from "aws-lambda"; import { Buffer } from "buffer"; import { GREETING_AGENT_PROMPT, HEALTH_AGENT_PROMPT, MATH_AGENT_PROMPT, TECH_AGENT_PROMPT, WEATHER_AGENT_PROMPT } from "./prompts"; import { BedrockAgentRuntimeClient, SearchType } from '@aws-sdk/client-bedrock-agent-runtime'; const logger = new Logger(); declare global { namespace awslambda { function streamifyResponse( f: ( event: APIGatewayProxyEventV2, responseStream: NodeJS.WritableStream, context: Context ) => Promise ): Handler; } } interface LexAgentConfig { name: string; description: string; botId: string; botAliasId: string; localeId: string; } interface BodyData { query: string; sessionId: string; userId: string; } const LEX_AGENT_ENABLED = process.env.LEX_AGENT_ENABLED || "false"; const storage = new DynamoDbChatStorage( process.env.HISTORY_TABLE_NAME!, process.env.AWS_REGION!, process.env.HISTORY_TABLE_TTL_KEY_NAME, Number(process.env.HISTORY_TABLE_TTL_DURATION), ); const orchestrator = new AgentSquad({ storage: storage, config: { LOG_AGENT_CHAT: true, LOG_CLASSIFIER_CHAT: true, LOG_CLASSIFIER_RAW_OUTPUT: true, LOG_CLASSIFIER_OUTPUT: true, LOG_EXECUTION_TIMES: true, }, logger: logger, classifier: new BedrockClassifier({ modelId: "anthropic.claude-3-sonnet-20240229-v1:0", }), }); const healthAgent = new BedrockLLMAgent({ name: "Health Agent", description: "Focuses on health and medical topics such as general wellness, nutrition, diseases, treatments, mental health, fitness, healthcare systems, and medical terminology or concepts.", }); healthAgent.setSystemPrompt(HEALTH_AGENT_PROMPT); const weatherAgent = new BedrockLLMAgent({ name: "Weather Agent", description: "Specialized agent for giving weather condition from a city.", streaming: true, inferenceConfig: { temperature: 0.0, }, toolConfig: { useToolHandler: weatherToolHanlder, tool: weatherToolDescription, toolMaxRecursions: 5, }, }); weatherAgent.setSystemPrompt(WEATHER_AGENT_PROMPT); // Add a our custom Math Agent to the orchestrator const mathAgent = new BedrockLLMAgent({ name: "Math Agent", description: "Specialized agent for solving mathematical problems. Can dynamically create and execute mathematical operations, handle complex calculations, and explain mathematical concepts. Capable of working with algebra, calculus, statistics, and other advanced mathematical fields.", streaming: false, inferenceConfig: { temperature: 0.0, }, toolConfig: { useToolHandler: mathToolHanlder, tool: mathAgentToolDefinition, toolMaxRecursions: 5, }, }); mathAgent.setSystemPrompt(MATH_AGENT_PROMPT); if (LEX_AGENT_ENABLED === "true") { const config: LexAgentConfig = JSON.parse(process.env.LEX_AGENT_CONFIG!); orchestrator.addAgent( new LexBotAgent({ name: config.name, description: config.description, botId: config.botId, botAliasId: config.botAliasId, localeId: config.localeId, }) ); } if (process.env.LAMBDA_AGENTS){ const lambdaAgents = JSON.parse(process.env.LAMBDA_AGENTS); for (const agent of lambdaAgents) { orchestrator.addAgent(new LambdaAgent({ name: agent.name, description: agent.description, functionName: agent.functionName, functionRegion: agent.region } )); } } // Add a our Agent Squad documentation agent const maoDocAgent = new BedrockLLMAgent({ name: "Tech agent", description: "A tech expert specializing in the Agent Squad framework, technical domains, and AI-driven solutions.", streaming: true, inferenceConfig: { temperature: 0.0, }, customSystemPrompt:{ template:` You are a tech expert specializing in both the technical domain, including software development, AI, cloud computing, and the Agent Squad framework. Your role is to provide comprehensive, accurate, and helpful information about these areas, with a specific focus on the orchestrator framework, its agents, and their applications. Always structure your responses using clear, well-formatted markdown. Key responsibilities: - Explain the Agent Squad framework, its agents, and its benefits - Guide users on how to get started with the framework and configure agents - Provide technical advice on topics like software development, AI, and cloud computing - Detail the process of creating and configuring an orchestrator - Describe the various components and elements of the framework - Provide examples and best practices for technical implementation When responding to queries: 1. Start with a brief overview of the topic 2. Break down complex concepts into clear, digestible sections 3. **When the user asks for an example or code, always respond with a code snippet, using proper markdown syntax for code blocks (\`\`\`).** Provide explanations alongside the code when necessary. 4. Conclude with next steps or additional resources if relevant Always use proper markdown syntax, including: - Headings (##, ###) for main sections and subsections - Bullet points (-) or numbered lists (1., 2., etc.) for enumerating items - Code blocks (\`\`\`) for code snippets or configuration examples - Bold (**text**) for emphasizing key terms or important points - Italic (*text*) for subtle emphasis or introducing new terms - Links ([text](URL)) when referring to external resources or documentation Tailor your responses to both beginners and experienced developers, providing clear explanations and technical depth as appropriate.` }, retriever: new AmazonKnowledgeBasesRetriever( new BedrockAgentRuntimeClient(), { knowledgeBaseId: process.env.KNOWLEDGE_BASE_ID, retrievalConfiguration: { vectorSearchConfiguration: { numberOfResults: 10, overrideSearchType: SearchType.HYBRID, }, }, } ) }); orchestrator.addAgent(maoDocAgent); //orchestrator.addAgent(techAgent); orchestrator.addAgent(healthAgent); orchestrator.addAgent(weatherAgent); orchestrator.addAgent(mathAgent); const greetingAgent = new BedrockLLMAgent({ name: "Greeting Agent", description: "Welcome the user and list him the available agents", streaming: true, inferenceConfig: { temperature: 0.0, }, saveChat: false, }); const agents = orchestrator.getAllAgents(); const agentList = Object.entries(agents) .map(([agentKey, agentInfo], index) => { const name = (agentInfo as any).name || agentKey; const description = (agentInfo as any).description; return `${index + 1}. **${name}**: ${description}`; }) .join('\n\n'); greetingAgent.setSystemPrompt(GREETING_AGENT_PROMPT(agentList)); orchestrator.addAgent(greetingAgent); async function eventHandler( event: APIGatewayProxyEventV2, responseStream: NodeJS.WritableStream ) { logger.info(event); try { const userBody = JSON.parse(event.body as string) as BodyData; const userId = userBody.userId; const sessionId = userBody.sessionId; logger.info("calling the orchestrator"); const response = await orchestrator.routeRequest( userBody.query, userId, sessionId ); logger.info("response from the orchestrator"); let safeBuffer = Buffer.from( JSON.stringify({ type: "metadata", data: response, }) + "\n", "utf8" ); responseStream.write(safeBuffer); if (response.streaming == true) { logger.info("\n** RESPONSE STREAMING ** \n"); // Send metadata immediately logger.info(` > Agent ID: ${response.metadata.agentId}`); logger.info(` > Agent Name: ${response.metadata.agentName}`); logger.info(`> User Input: ${response.metadata.userInput}`); logger.info(`> User ID: ${response.metadata.userId}`); logger.info(`> Session ID: ${response.metadata.sessionId}`); logger.info( `> Additional Parameters:`, response.metadata.additionalParams ); logger.info(`\n> Response: `); for await (const chunk of response.output) { if (typeof chunk === "string") { process.stdout.write(chunk); safeBuffer = Buffer.from( JSON.stringify({ type: "chunk", data: chunk, }) + "\n" ); responseStream.write(safeBuffer); } else { logger.error("Received unexpected chunk type:", typeof chunk); } } } else { // Handle non-streaming response (AgentProcessingResult) logger.info("\n** RESPONSE ** \n"); logger.info(` > Agent ID: ${response.metadata.agentId}`); logger.info(` > Agent Name: ${response.metadata.agentName}`); logger.info(`> User Input: ${response.metadata.userInput}`); logger.info(`> User ID: ${response.metadata.userId}`); logger.info(`> Session ID: ${response.metadata.sessionId}`); logger.info( `> Additional Parameters:`, response.metadata.additionalParams ); logger.info(`\n> Response: ${response.output}`); safeBuffer = Buffer.from( JSON.stringify({ type: "complete", data: response.output, }) ); responseStream.write(safeBuffer); } } catch (error) { logger.error("Error: " + error); responseStream.write( JSON.stringify({ response: error, }) ); } finally { responseStream.end(); } } export const handler = awslambda.streamifyResponse(eventHandler); ================================================ FILE: examples/chat-demo-app/lambda/multi-agent/math_tool.ts ================================================ import { ConversationMessage, ParticipantRole, Logger } from "agent-squad"; export const mathAgentToolDefinition = [ { toolSpec: { name: "perform_math_operation", description: "Perform a mathematical operation. This tool supports basic arithmetic and various mathematical functions.", inputSchema: { json: { type: "object", properties: { operation: { type: "string", description: "The mathematical operation to perform. Supported operations include:\n" + "- Basic arithmetic: 'add' (or 'addition'), 'subtract' (or 'subtraction'), 'multiply' (or 'multiplication'), 'divide' (or 'division')\n" + "- Exponentiation: 'power' (or 'exponent')\n" + "- Trigonometric: 'sin', 'cos', 'tan'\n" + "- Logarithmic and exponential: 'log', 'exp'\n" + "- Rounding: 'round', 'floor', 'ceil'\n" + "- Other: 'sqrt', 'abs'\n" + "Note: For operations not listed here, check if they are standard Math object functions.", }, args: { type: "array", items: { type: "number", }, description: "The arguments for the operation. Note:\n" + "- Addition and multiplication can take multiple arguments\n" + "- Subtraction, division, and exponentiation require exactly two arguments\n" + "- Most other operations take one argument, but some may accept more", }, }, required: ["operation", "args"], }, }, }, }, { toolSpec: { name: "perform_statistical_calculation", description: "Perform statistical calculations on a set of numbers.", inputSchema: { json: { type: "object", properties: { operation: { type: "string", description: "The statistical operation to perform. Supported operations include:\n" + "- 'mean': Calculate the average of the numbers\n" + "- 'median': Calculate the middle value of the sorted numbers\n" + "- 'mode': Find the most frequent number\n" + "- 'variance': Calculate the variance of the numbers\n" + "- 'stddev': Calculate the standard deviation of the numbers", }, args: { type: "array", items: { type: "number", }, description: "The set of numbers to perform the statistical operation on.", }, }, required: ["operation", "args"], }, }, }, }, ]; /** * Executes a mathematical operation using JavaScript's Math library. * @param operation - The mathematical operation to perform. * @param args - Array of numbers representing the arguments for the operation. * @returns An object containing either the result of the operation or an error message. */ function executeMathOperation( operation: string, args: number[] ): { result: number } | { error: string } { const safeEval = (code: string) => { return Function('"use strict";return (' + code + ")")(); }; try { let result: number; switch (operation.toLowerCase()) { case 'add': case 'addition': result = args.reduce((sum, current) => sum + current, 0); break; case 'subtract': case 'subtraction': if (args.length !== 2) { throw new Error('Subtraction requires exactly two arguments'); } result = args[0] - args[1]; break; case 'multiply': case 'multiplication': result = args.reduce((product, current) => product * current, 1); break; case 'divide': case 'division': if (args.length !== 2) { throw new Error('Division requires exactly two arguments'); } if (args[1] === 0) { throw new Error('Division by zero'); } result = args[0] / args[1]; break; case 'power': case 'exponent': if (args.length !== 2) { throw new Error('Power operation requires exactly two arguments'); } result = Math.pow(args[0], args[1]); break; default: // For other operations, use the Math object if the function exists if (typeof Math[operation as keyof typeof Math] === 'function') { result = safeEval(`Math.${operation}(${args.join(",")})`); } else { throw new Error(`Unsupported operation: ${operation}`); } } return { result }; } catch (error) { return { error: `Error executing ${operation}: ${(error as Error).message}`, }; } } function calculateStatistics(operation: string, args: number[]): { result: number } | { error: string } { try { switch (operation.toLowerCase()) { case 'mean': return { result: args.reduce((sum, num) => sum + num, 0) / args.length }; case 'median': { const sorted = args.slice().sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return { result: sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2, }; } case 'mode': { const counts = args.reduce((acc, num) => { acc[num] = (acc[num] || 0) + 1; return acc; }, {} as Record); const maxCount = Math.max(...Object.values(counts)); const modes = Object.keys(counts).filter(key => counts[Number(key)] === maxCount); return { result: Number(modes[0]) }; // Return first mode if there are multiple } case 'variance': { const mean = args.reduce((sum, num) => sum + num, 0) / args.length; const squareDiffs = args.map(num => Math.pow(num - mean, 2)); return { result: squareDiffs.reduce((sum, square) => sum + square, 0) / args.length }; } case 'stddev': { const mean = args.reduce((sum, num) => sum + num, 0) / args.length; const squareDiffs = args.map(num => Math.pow(num - mean, 2)); const variance = squareDiffs.reduce((sum, square) => sum + square, 0) / args.length; return { result: Math.sqrt(variance) }; } default: throw new Error(`Unsupported statistical operation: ${operation}`); } } catch (error) { return { error: `Error executing ${operation}: ${(error as Error).message}` }; } } export async function mathToolHanlder(response:any, conversation: ConversationMessage[]): Promise{ const responseContentBlocks = response.content as any[]; const mathOperations: string[] = []; let lastResult: number | string | undefined; // Initialize an empty list of tool results let toolResults:any = [] if (!responseContentBlocks) { throw new Error("No content blocks in response"); } for (const contentBlock of response.content) { if ("text" in contentBlock) { Logger.logger.info(contentBlock.text); } if ("toolUse" in contentBlock) { const toolUseBlock = contentBlock.toolUse; const toolUseName = toolUseBlock.name; if (toolUseName === "perform_math_operation") { const operation = toolUseBlock.input.operation; let args = toolUseBlock.input.args; if (['sin', 'cos', 'tan'].includes(operation) && args.length > 0) { const degToRad = Math.PI / 180; args = [args[0] * degToRad]; } const result = executeMathOperation(operation, args); if ('result' in result) { lastResult = result.result; mathOperations.push(`Tool call ${mathOperations.length + 1}: perform_math_operation: args=[${args.join(', ')}] operation=${operation} result=${lastResult}\n`); toolResults.push({ toolResult: { toolUseId: toolUseBlock.toolUseId, content: [{ json: { result: lastResult } }], status: "success" } }); } else { // Handle error case const errorMessage = `Error in ${toolUseName}: ${operation}(${toolUseBlock.input.args.join(', ')}) - ${result.error}`; mathOperations.push(errorMessage); toolResults.push({ toolResult: { toolUseId: toolUseBlock.toolUseId, content: [{ text: result.error }], status: "error" } }); } } else if (toolUseName === "perform_statistical_calculation") { const operation = toolUseBlock.input.operation; const args = toolUseBlock.input.args; const result = calculateStatistics(operation, args); if ('result' in result) { lastResult = result.result; mathOperations.push(`Tool call ${mathOperations.length + 1}: perform_statistical_calculation: args=[${args.join(', ')}] operation=${operation} result=${lastResult}\n`); toolResults.push({ toolResult: { toolUseId: toolUseBlock.toolUseId, content: [{ json: { result: lastResult } }], status: "success" } }); } else { // Handle error case const errorMessage = `Error in ${toolUseName}: ${operation}(${args.join(', ')}) - ${result.error}`; mathOperations.push(errorMessage); toolResults.push({ toolResult: { toolUseId: toolUseBlock.toolUseId, content: [{ text: result.error }], status: "error" } }); } } } } // Embed the tool results in a new user message const message:ConversationMessage = {role: ParticipantRole.USER, content: toolResults}; return message; } ================================================ FILE: examples/chat-demo-app/lambda/multi-agent/prompts.ts ================================================ import { Agent } from "agent-squad"; export const WEATHER_AGENT_PROMPT = ` You are a weather assistant that provides current weather data and forecasts for user-specified locations using only the Weather_Tool, which expects latitude and longitude. Your role is to deliver accurate, detailed, and easily understandable weather information to users with varying levels of meteorological knowledge. Core responsibilities: - Infer the coordinates from the location provided by the user. If the user provides coordinates, infer the approximate location and refer to it in your response. - To use the tool, strictly apply the provided tool specification. - Explain your step-by-step process, giving brief updates before each step. - Only use the Weather_Tool for data. Never guess or make up information. - Repeat the tool use for subsequent requests if necessary. - If the tool errors, apologize, explain weather is unavailable, and suggest other options. Reporting guidelines: - Report temperatures in °C (°F) and wind in km/h (mph). - Keep weather reports concise but informative. - Sparingly use emojis where appropriate to enhance readability. - Provide practical advice related to weather preparedness and outdoor planning when relevant. - Interpret complex weather data and translate it into user-friendly information. Conversation flow: 1. The user may initiate with a weather-related question or location-specific inquiry. 2. Provide a relevant, informative, and scientifically accurate response using the Weather_Tool. 3. The user may follow up with more specific questions or request clarification on weather details. 4. Adapt your responses to address evolving topics or new weather-related concepts introduced. Remember to: - Only respond to weather queries. Remind off-topic users of your purpose. - Never claim to search online, access external data, or use tools besides Weather_Tool. - Complete the entire process until you have all required data before sending the complete response. - Acknowledge the uncertainties in long-term forecasts when applicable. - Encourage weather safety and preparedness, especially in cases of severe weather. - Be sensitive to the serious nature of extreme weather events and their potential consequences. Always respond in markdown format, using the following guidelines: - Use ## for main headings and ### for subheadings. - Use bullet points (-) for lists of weather conditions or factors. - Use numbered lists (1., 2., etc.) for step-by-step advice or sequences of weather events. - Use **bold** for important terms or critical weather information. - Use *italic* for emphasis or to highlight less critical but noteworthy points. - Use tables for organizing comparative data (e.g., daily forecasts) if applicable. Example structure: \`\`\` ## Current Weather in [Location] - Temperature: **23°C (73°F)** - Wind: NW at 10 km/h (6 mph) - Conditions: Partly cloudy ### Today's Forecast [Include brief forecast details here] ## Weather Alert (if applicable) **[Any critical weather information]** ### Weather Tip [Include a relevant weather-related tip or advice] \`\`\` By following these guidelines, you'll provide comprehensive, accurate, and well-formatted weather information, catering to users seeking both casual and detailed meteorological insights. ` export const HEALTH_AGENT_PROMPT = ` You are a Health Agent that focuses on health and medical topics such as general wellness, nutrition, diseases, treatments, mental health, fitness, healthcare systems, and medical terminology or concepts. Your role is to provide helpful, accurate, and compassionate information based on your expertise in health and medical topics. Core responsibilities: - Engage in open-ended discussions about health, wellness, and medical concerns. - Offer evidence-based information and gentle guidance. - Always encourage users to consult healthcare professionals for personalized medical advice. - Explain complex medical concepts in easy-to-understand terms. - Promote overall wellness, preventive care, and healthy lifestyle choices. Conversation flow: 1. The user may initiate with a health-related question or concern. 2. Provide a relevant, informative, and empathetic response. 3. The user may follow up with additional questions or share more context about their situation. 4. Adapt your responses to address evolving topics or new health concerns introduced. Throughout the conversation, aim to: - Understand the context and potential urgency of each health query. - Offer substantive, well-researched information while acknowledging the limits of online health guidance. - Draw connections between various aspects of health (e.g., how diet might affect a medical condition). - Clarify any ambiguities in the user's questions to ensure accurate responses. - Maintain a warm, professional tone that puts users at ease when discussing sensitive health topics. - Emphasize the importance of consulting healthcare providers for diagnosis, treatment, or medical emergencies. - Provide reliable sources or general guidelines from reputable health organizations when appropriate. Remember: - Never attempt to diagnose specific conditions or prescribe treatments. - Encourage healthy skepticism towards unproven remedies or health trends. - Be sensitive to the emotional aspects of health concerns, offering supportive and encouraging language. - Stay up-to-date with current health guidelines and medical consensus, avoiding outdated or controversial information. Always respond in markdown format, using the following guidelines: - Use ## for main headings and ### for subheadings. - Use bullet points (-) for lists of health factors, symptoms, or recommendations. - Use numbered lists (1., 2., etc.) for step-by-step advice or processes. - Use **bold** for important terms or critical health information. - Use *italic* for emphasis or to highlight less critical but noteworthy points. - Use blockquotes (>) for direct quotes from reputable health sources or organizations. Example structure: \`\`\` ## [Health Topic] ### Key Points - Point 1 - Point 2 - Point 3 ### Recommendations 1. First recommendation 2. Second recommendation 3. Third recommendation **Important:** [Critical health information or disclaimer] > "Relevant quote from a reputable health organization" - Source *Remember: This information is for general educational purposes only and should not replace professional medical advice.* \`\`\` By following these guidelines, you'll provide comprehensive, accurate, and well-formatted health information, while maintaining a compassionate and responsible approach to health communication. `; export const TECH_AGENT_PROMPT = ` You are a TechAgent that specializes in technology areas including software development, hardware, AI, cybersecurity, blockchain, cloud computing, emerging tech innovations, and pricing/costs related to technology products and services. Your role is to provide expert, cutting-edge information and insights on technology topics, catering to both tech enthusiasts and professionals seeking in-depth knowledge. Core responsibilities: - Engage in discussions covering a wide range of technology fields, including software development, hardware, AI, cybersecurity, blockchain, cloud computing, and emerging tech innovations. - Offer detailed explanations of complex tech concepts, current trends, and future predictions in the tech industry. - Provide practical advice on tech-related problems, best practices, and industry standards. - Stay neutral when discussing competing technologies, offering balanced comparisons based on technical merits. Conversation flow: 1. The user may initiate with a technology-related question, problem, or topic of interest. 2. Provide a relevant, informative, and technically accurate response. 3. The user may follow up with more specific questions or request clarification on technical details. 4. Adapt your responses to address evolving topics or new tech concepts introduced. Throughout the conversation, aim to: - Quickly assess the user's technical background and adjust your explanations accordingly. - Offer substantive, well-researched information, including recent developments in the tech world. - Draw connections between various tech domains (e.g., how AI impacts cybersecurity). - Use technical jargon appropriately, explaining terms when necessary for clarity. - Maintain an engaging tone that conveys enthusiasm for technology while remaining professional. - Provide code snippets, pseudocode, or technical diagrams when they help illustrate a point. - Cite reputable tech sources, research papers, or documentation when appropriate. Remember to: - Stay up-to-date with the latest tech news, product releases, and industry trends. - Acknowledge the rapid pace of change in technology and indicate when information might become outdated quickly. - Encourage best practices in software development, system design, and tech ethics. - Be honest about limitations in current technology and areas where the field is still evolving. - Discuss potential societal impacts of emerging technologies. Always respond in markdown format, using the following guidelines: - Use ## for main headings and ### for subheadings. - Use bullet points (-) for lists of features, concepts, or comparisons. - Use numbered lists (1., 2., etc.) for step-by-step instructions or processes. - Use **bold** for important terms or critical technical information. - Use *italic* for emphasis or to highlight less critical but noteworthy points. - Use \`inline code\` for short code snippets, commands, or technical terms. - Use code blocks (\`\`\`) for longer code examples, with appropriate syntax highlighting. Example structure: \`\`\` ## [Technology Topic] ### Key Concepts - Concept 1 - Concept 2 - Concept 3 ### Practical Application 1. Step one 2. Step two 3. Step three **Important:** [Critical technical information or best practice] Example code: \`\`\`python def example_function(): return "This is a code example" \`\`\` *Note: Technology in this area is rapidly evolving. This information is current as of [current date], but may change in the near future.* \`\`\` By following these guidelines, you'll provide comprehensive, accurate, and well-formatted technical information, catering to a wide range of users from curious beginners to seasoned tech professionals. ` export const MATH_AGENT_PROMPT = ` You are a MathAgent, a mathematical assistant capable of performing various mathematical operations and statistical calculations. Your role is to provide clear, accurate, and detailed mathematical explanations and solutions. Core responsibilities: - Use the provided tools to perform calculations accurately. - Always show your work, explain each step, and provide the final result of the operation. - If a calculation involves multiple steps, use the tools sequentially and explain the process thoroughly. - Only respond to mathematical queries. For non-math questions, politely redirect the conversation to mathematics. - Adapt your explanations to suit both students and professionals seeking mathematical assistance. Conversation flow: 1. The user may initiate with a mathematical question, problem, or topic of interest. 2. Provide a relevant, informative, and mathematically accurate response. 3. The user may follow up with more specific questions or request clarification on mathematical concepts. 4. Adapt your responses to address evolving topics or new mathematical concepts introduced. Throughout the conversation, aim to: - Assess the user's mathematical background and adjust your explanations accordingly. - Offer substantive, well-structured solutions to mathematical problems. - Draw connections between various mathematical concepts when relevant. - Use mathematical notation and terminology appropriately, explaining terms when necessary for clarity. - Maintain an engaging tone that conveys the elegance and logic of mathematics. - Provide visual representations (using ASCII art or markdown tables) when they help illustrate a concept. - Cite mathematical theorems, properties, or famous mathematicians when appropriate. Remember to: - Be precise in your language and notation. - Encourage mathematical thinking and problem-solving skills. - Highlight the real-world applications of mathematical concepts when relevant. - Be honest about the limitations of certain mathematical approaches or when a problem requires advanced techniques beyond the scope of the conversation. Always respond in markdown format, using the following guidelines: - Use ## for main headings and ### for subheadings. - Use bullet points (-) for lists of concepts, properties, or steps in a process. - Use numbered lists (1., 2., etc.) for sequential steps in a solution or proof. - Use **bold** for important terms, theorems, or key results. - Use *italic* for emphasis or to highlight noteworthy points. - Use \`inline code\` for short mathematical expressions or equations. - Use code blocks (\`\`\`) with LaTeX syntax for more complex equations or mathematical displays. - Use tables for organizing data or showing step-by-step calculations. Example structure: \`\`\` ## [Mathematical Topic or Problem] ### Problem Statement [State the problem or question clearly] ### Solution Approach 1. Step one 2. Step two 3. Step three ### Detailed Calculation [Show detailed work here, using LaTeX for equations] \`\`\`latex f(x) = ax^2 + bx + c \`\`\` ### Final Result **The solution is: [result]** ### Explanation [Provide a clear explanation of the solution and its significance] *Note: This solution method is applicable to [specific types of problems]. For more complex cases, additional techniques may be required.* \`\`\` By following these guidelines, you'll provide comprehensive, accurate, and well-formatted mathematical information, catering to users seeking both basic and advanced mathematical assistance. ` export const GREETING_AGENT_PROMPT = (agentList: string) => ` You are a friendly and helpful greeting agent. Your primary roles are to welcome users, respond to greetings, and provide assistance in navigating the available agents. Always maintain a warm and professional tone in your interactions. Core responsibilities: - Respond warmly to greetings such as "hello", "hi", or similar phrases. - Provide helpful information when users ask for "help" or guidance. - Introduce users to the range of specialized agents available to assist them. - Guide users on how to interact with different agents based on their needs. When greeting or helping users: 1. Start with a warm welcome or acknowledgment of their greeting. 2. Briefly explain your role as a greeting and help agent. 3. Introduce the list of available agents and their specialties. 4. Encourage the user to ask questions or specify their needs for appropriate agent routing. Available Agents: ${agentList} Remember to: - Be concise yet informative in your responses. - Tailor your language to be accessible to users of all technical levels. - Encourage users to be specific about their needs for better assistance. - Maintain a positive and supportive tone throughout the interaction. Always respond in markdown format, using the following guidelines: - Use ## for main headings and ### for subheadings if needed. - Use bullet points (-) for lists. - Use **bold** for emphasis on important points or agent names. - Use *italic* for subtle emphasis or additional details. By following these guidelines, you'll provide a warm, informative, and well-structured greeting that helps users understand and access the various agents available to them. `; ================================================ FILE: examples/chat-demo-app/lambda/multi-agent/weather_tool.ts ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { ConversationMessage, ParticipantRole } from "agent-squad"; export const weatherToolDescription = [ { toolSpec: { name: "Weather_Tool", description: "Get the current weather for a given location, based on its WGS84 coordinates.", inputSchema: { json: { type: "object", properties: { latitude: { type: "string", description: "Geographical WGS84 latitude of the location.", }, longitude: { type: "string", description: "Geographical WGS84 longitude of the location.", }, }, required: ["latitude", "longitude"], } }, } } ]; interface InputData { latitude: number; longitude: number; } interface WeatherData { weather_data?: any; error?: string; message?: string; } export async function weatherToolHanlder(response:ConversationMessage, conversation: ConversationMessage[]): Promise{ const responseContentBlocks = response.content as any[]; // Initialize an empty list of tool results let toolResults:any = [] if (!responseContentBlocks) { throw new Error("No content blocks in response"); } for (const contentBlock of responseContentBlocks) { if ("text" in contentBlock) { } if ("toolUse" in contentBlock) { const toolUseBlock = contentBlock.toolUse; const toolUseName = toolUseBlock.name; if (toolUseName === "Weather_Tool") { const response = await fetchWeatherData({latitude: toolUseBlock.input.latitude, longitude: toolUseBlock.input.longitude}); toolResults.push({ "toolResult": { "toolUseId": toolUseBlock.toolUseId, "content": [{ json: { result: response } }], } }); } } } // Embed the tool results in a new user message const message:ConversationMessage = {role: ParticipantRole.USER, content: toolResults}; return message; } async function fetchWeatherData(inputData: InputData): Promise { const endpoint = "https://api.open-meteo.com/v1/forecast"; const { latitude, longitude } = inputData; const params = new URLSearchParams({ latitude: latitude.toString(), longitude: longitude?.toString() || "", current_weather: "true", }); try { const response = await fetch(`${endpoint}?${params}`); const data = await response.json() as any; if (!response.ok) { return { error: 'Request failed', message: data.message || 'An error occurred' }; } return { weather_data: data }; } catch (error: any) { return { error: error.name, message: error.message }; } } ================================================ FILE: examples/chat-demo-app/lambda/sync_bedrock_knowledgebase/lambda.py ================================================ import boto3 client = boto3.client('bedrock-agent') def lambda_handler(event, context): response = client.start_ingestion_job( dataSourceId=event.get('dataSourceId'), knowledgeBaseId=event.get('knowledgeBaseId') ) print(response) ================================================ FILE: examples/chat-demo-app/lib/CustomResourcesLambda/aoss-index-create.ts ================================================ import { defaultProvider } from '@aws-sdk/credential-provider-node'; import { Client } from '@opensearch-project/opensearch'; import { AwsSigv4Signer } from '@opensearch-project/opensearch/aws'; import { OnEventRequest, OnEventResponse } from 'aws-cdk-lib/custom-resources/lib/provider-framework/types'; import { retryAsync } from 'ts-retry'; import { Logger } from '@aws-lambda-powertools/logger'; const logger = new Logger({ serviceName: 'BedrockAgentsBlueprints', logLevel: "INFO" }); const CLIENT_TIMEOUT_MS = 1000; const CLIENT_MAX_RETRIES = 5; const CREATE_INDEX_RETRY_CONFIG = { delay: 30000, // 30 sec maxTry: 20, // Should wait at least 10 mins for the permissions to propagate }; // TODO: make an embedding to config map to support more models // Dafault config for titan embedding v2. Derived from https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-setup.html const DEFAULT_INDEX_CONFIG = { mappings: { properties: { id: { type: 'text', fields: { keyword: { type: 'keyword', ignore_above: 256, }, }, }, AMAZON_BEDROCK_METADATA: { type: 'text', index: false, }, AMAZON_BEDROCK_TEXT_CHUNK: { type: 'text', }, 'bedrock-knowledge-base-default-vector': { type: 'knn_vector', dimension: 1536, method: { engine: 'faiss', space_type: 'l2', name: 'hnsw', }, }, }, }, settings: { index: { number_of_shards: 2, 'knn.algo_param': { ef_search: 512, }, knn: true, }, }, }; /** * OnEvent is called to create/update/delete the custom resource. * * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html * * @param event request object containing event type and request variables. This contains 3 * params: indexName(Required), collectionEndpoint(Required), indexConfiguration(Optional) * @param _context Lambda context * * @returns reponse object containing the physical resource ID of the indexName. */ export const onEvent = async (event: OnEventRequest, _context: unknown): Promise => { const { indexName, collectionEndpoint, indexConfiguration } = event.ResourceProperties; try { logger.info("Initiating custom resource for index operations"); const signerResponse = AwsSigv4Signer({ region: process.env.AWS_REGION!, service: 'aoss', getCredentials: defaultProvider(), }); const openSearchClient = new Client({ ...signerResponse, maxRetries: CLIENT_MAX_RETRIES, node: collectionEndpoint, requestTimeout: CLIENT_TIMEOUT_MS, }); logger.info("AOSS client creation successful"); if (event.RequestType == 'Create') { return await createIndex(openSearchClient, indexName, indexConfiguration); } else if (event.RequestType == 'Update') { return await updateIndex(openSearchClient, indexName, indexConfiguration); } else if (event.RequestType == 'Delete') { return await deleteIndex(openSearchClient, indexName); } else { throw new Error(`Unsupported request type: ${event.RequestType}`); } } catch (error) { logger.error((error as Error).toString()); throw new Error(`Custom aoss-index operation failed: ${error}`); } }; const createIndex = async (openSearchClient: Client, indexName: string, indexConfig?: any): Promise => { logger.info("AOSS index creation started"); // Create index based on default or user provided config. const indexConfiguration = indexConfig ?? DEFAULT_INDEX_CONFIG; // Retry index creation to allow data policy to propagate. await retryAsync( async () => { await openSearchClient.indices.create({ index: indexName, body: indexConfiguration, }); logger.info('Successfully created index!'); }, CREATE_INDEX_RETRY_CONFIG, ); return { PhysicalResourceId: `osindex_${indexName}`, }; }; const deleteIndex = async (openSearchClient: Client, indexName: string): Promise => { logger.info("AOSS index deletion started"); await openSearchClient.indices.delete({ index: indexName, }); return { PhysicalResourceId: `osindex_${indexName}`, }; }; const updateIndex = async (openSearchClient: Client, indexName: string, indexConfig?: any): Promise => { logger.info("AOSS index update started"); // OpenSearch doesn't have an update index function. Hence, delete and create index await deleteIndex(openSearchClient, indexName); return await createIndex(openSearchClient, indexName, indexConfig); }; ================================================ FILE: examples/chat-demo-app/lib/CustomResourcesLambda/data-source-sync.ts ================================================ import { BedrockAgentClient, StartIngestionJobCommand, DeleteDataSourceCommand, DeleteKnowledgeBaseCommand, GetDataSourceCommand } from "@aws-sdk/client-bedrock-agent"; import { OnEventRequest, OnEventResponse } from 'aws-cdk-lib/custom-resources/lib/provider-framework/types'; import { Logger } from '@aws-lambda-powertools/logger'; const logger = new Logger({ serviceName: 'BedrockAgentsBlueprints', logLevel: "INFO" }); /** * OnEvent is called to create/update/delete the custom resource. We are only using it * here to start a one-off ingestion job at deployment. * * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html * * @param event request object containing event type and request variables. This contains 2 * params: knowledgeBaseId(Required), dataSourceId(Required) * @param _context Lambda context, currently unused. * * @returns reponse object containing the physical resource ID of the ingestionJob. */ export const onEvent = async (event: OnEventRequest, _context: unknown): Promise => { logger.info("Received Event into Data Sync Function", JSON.stringify(event, null, 2)); const brAgentClient = new BedrockAgentClient({}); const { knowledgeBaseId, dataSourceId } = event.ResourceProperties; switch (event.RequestType) { case 'Create': return await handleCreateEvent(brAgentClient, knowledgeBaseId, dataSourceId); case 'Delete': return await handleDeleteEvent(brAgentClient, knowledgeBaseId, dataSourceId, event); default: return { PhysicalResourceId: 'skip' }; } }; /** * Handles the "Create" event by starting an ingestion job. * * @param brAgentClient The BedrockAgentClient instance. * @param knowledgeBaseId The ID of the knowledge base. * @param dataSourceId The ID of the data source. * @returns The response object containing the physical resource ID and optional reason for failure. */ const handleCreateEvent = async (brAgentClient: BedrockAgentClient, knowledgeBaseId: string, dataSourceId: string): Promise => { try { // Start Knowledgebase and datasource sync job logger.info('Starting ingestion job'); const dataSyncResponse = await brAgentClient.send( new StartIngestionJobCommand({ knowledgeBaseId, dataSourceId, }), ); logger.info(`Data Sync Response ${JSON.stringify(dataSyncResponse, null, 2)}`); return { PhysicalResourceId: dataSyncResponse && dataSyncResponse.ingestionJob ? `datasync_${dataSyncResponse.ingestionJob.ingestionJobId}` : 'datasync_failed', }; } catch (err) { logger.error((err as Error).toString()); return { PhysicalResourceId: 'datasync_failed', Reason: `Failed to start ingestion job: ${err}`, }; } }; /** * Handles the "Delete" event by deleting the data source and knowledge base. * * @param brAgentClient The BedrockAgentClient instance. * @param knowledgeBaseId The ID of the knowledge base. * @param dataSourceId The ID of the data source. * @returns The response object containing the physical resource ID and optional reason for failure. */ const handleDeleteEvent = async (brAgentClient: BedrockAgentClient, knowledgeBaseId: string, dataSourceId: string, event: OnEventRequest): Promise => { try { // Retrieve the data source details const dataSourceResponse = await brAgentClient.send( new GetDataSourceCommand({ dataSourceId, knowledgeBaseId, }), ); const dataSource = dataSourceResponse.dataSource; logger.info(`DataSourceResponse DataSource ${dataSource}`); if (!dataSource) { throw new Error('Data source not found'); } // Delete the data source const deleteDataSourceResponse = await brAgentClient.send( new DeleteDataSourceCommand({ dataSourceId, knowledgeBaseId, }), ); logger.info(`Delete DataSource Response: ${deleteDataSourceResponse}`); // Delete the knowledge base const deleteKBResponse = await brAgentClient.send( new DeleteKnowledgeBaseCommand({ knowledgeBaseId, }), ); logger.info(`Delete KB Response: ${deleteKBResponse}`); return { PhysicalResourceId: event.PhysicalResourceId, }; } catch (err) { logger.error((err as Error).toString()); return { PhysicalResourceId: event.PhysicalResourceId, Reason: `Failed to delete data source or knowledge base: ${err}`, }; } }; ================================================ FILE: examples/chat-demo-app/lib/CustomResourcesLambda/permission-validation.ts ================================================ import { defaultProvider } from '@aws-sdk/credential-provider-node'; import { Client } from '@opensearch-project/opensearch'; import { AwsSigv4Signer } from '@opensearch-project/opensearch/aws'; import { OnEventRequest, OnEventResponse } from 'aws-cdk-lib/custom-resources/lib/provider-framework/types'; import { retryAsync } from 'ts-retry'; import { Logger } from '@aws-lambda-powertools/logger'; const logger = new Logger({ serviceName: 'BedrockAgentsBlueprints', logLevel: "INFO" }); const CLIENT_TIMEOUT_MS = 10000; const CLIENT_MAX_RETRIES = 5; const RETRY_CONFIG = { delay: 30000, // 30 sec maxTry: 20, // Should wait at least 10 mins for the permissions to propagate }; /** * Handles the 'Create', 'Update', and 'Delete' events for a custom resource. * * This function checks the existence of an OpenSearch index and retries the operation if the index is not found, * with a configurable retry strategy. * * @param event - The request object containing the event type and request variables. * - indexName (required): The name of the OpenSearch index to check. * - collectionEndpoint (required): The endpoint of the OpenSearch collection. * @param _context - The Lambda context object. Unused currently. * * @returns - A response object containing the physical resource ID of the index name. * - For 'Create' or 'Update' events, the physical resource ID is 'osindex_'. * - For 'Delete' events, the physical resource ID is 'skip'. */ export const onEvent = async (event: OnEventRequest, _context: unknown): Promise => { const { indexName, collectionEndpoint } = event.ResourceProperties; try { const signerResponse = AwsSigv4Signer({ region: process.env.AWS_REGION!, service: 'aoss', getCredentials: defaultProvider(), }); const openSearchClient = new Client({ ...signerResponse, maxRetries: CLIENT_MAX_RETRIES, node: collectionEndpoint, requestTimeout: CLIENT_TIMEOUT_MS, }); if (event.RequestType === 'Create' || event.RequestType === 'Update') { // Validate permissions to access index await retryAsync( async () => { let statusCode: null | number = 404; let result = await openSearchClient.indices.exists({ index: indexName, }); statusCode = result.statusCode; if (statusCode === 404) { throw new Error('Index not found'); } else if (statusCode === 200) { logger.info('Successfully checked index!'); } else { throw new Error(`Unknown error while looking for index result opensearch response: ${JSON.stringify(result)}`); } }, RETRY_CONFIG, ); //Validate permissions to use index await retryAsync( async () => { let statusCode: null | number = 404; const openSearchQuery = { query: { match_all: {} }, size: 1 // Limit the number of results to 1 }; let result = await openSearchClient.search({ index: indexName, body: openSearchQuery }); statusCode = result.statusCode; if (statusCode === 404) { throw new Error('Index not accesible'); } else if (statusCode === 200) { logger.info('Successfully queried index!'); } else { throw new Error(`Unknown error while querying index in opensearch response: ${JSON.stringify(result)}`); } }, RETRY_CONFIG, ); } else if (event.RequestType === 'Delete') { // Handle delete event try { const result = await openSearchClient.indices.delete({ index: indexName, }); if (result.statusCode === 404) { logger.info('Index not found, considered as deleted'); } else { logger.info('Successfully deleted index!'); } } catch (error) { logger.error(`Error deleting index: ${error}`); } return { PhysicalResourceId: `osindex_${indexName}` }; } } catch (error) { logger.error((error as Error).toString()); throw new Error(`Failed to check for index: ${error}`); } await sleep(5000); // Wait for 5 seconds before returning status return { PhysicalResourceId: `osindex_${indexName}`, }; }; async function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } ================================================ FILE: examples/chat-demo-app/lib/airlines.yaml ================================================ AWSTemplateFormatVersion: 2010-09-09 Description: > Amazon Lex for travel hospitality offers pre-built solutions so you can enable experiences at scale and drive digital engagement. The purpose-built bots provide ready to use conversation flows along with training data and dialog prompts, for both voice and chat modalities. Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Amazon Lex bot parameters Parameters: - BotName - BusinessLogicFunctionName - Label: default: Amazon DynamoDB parameters Parameters: - DynamoDBTableName - Label: default: Amazon Connect parameters (Optional) Parameters: - ConnectInstanceARN - ContactFlowName Mappings: BucketName: us-east-1: Name: 'lex-usecases-us-east-1' us-west-2: Name: 'lex-usecases-us-west-2' eu-west-2: Name: 'lex-usecases-eu-west-2' eu-west-1: Name: 'lex-usecases-eu-west-1' eu-central-1: Name: 'lex-usecases-eu-central-1' ca-central-1: Name: 'lex-usecases-ca-central-1' ap-southeast-2: Name: 'lex-usecases-ap-southeast-2' ap-southeast-1: Name: 'lex-usecases-ap-southeast-1' ap-northeast-2: Name: 'lex-usecases-ap-northeast-2' ap-northeast-1: Name: 'lex-usecases-ap-northeast-1' S3Path: LexImportSource: Name: 'travel/airlines/lex_import.zip' DBImportSource: Name: 'travel/airlines/db_import.zip' BusinessLogicSource: Name: 'travel/airlines/lambda_import.zip' ConnectImportSource: Name: 'travel/airlines/connect_import.zip' Parameters: ConnectInstanceARN: Type: String Description: > ARN of Connect Instance. To find your instance ARN: 'https://docs.aws.amazon.com/connect/latest/adminguide/find-instance-arn.html' Default: '' ContactFlowName: Type: String Description: > Name of the Connect contact flow. Please ensure contact flow with the same name does not exist. Default: AirlinesContactFlow BusinessLogicFunctionName: Type: String Description: > Name of the Lambda function for validation and fulfilment Default: AirlinesBusinessLogic BotName: Type: String Description: > Name of the Lex bot Default: AirlinesBot DynamoDBTableName: Type: String Description: > Name of the DynamoDB table that contains the sample policy data Default: Airlines_db Resources: LexRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com - lex.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: !Join [ "_", [ !Ref AWS::StackName, 'LexPolicy' ] ] PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'polly:SynthesizeSpeech' Resource: - '*' LexImportFunction: Type: 'AWS::Lambda::Function' Properties: Code: S3Bucket: !FindInMap [BucketName, !Ref "AWS::Region", 'Name'] S3Key: !FindInMap [S3Path, 'LexImportSource', 'Name'] Handler: lambda_function.lambda_handler Role: !GetAtt - LexImportRole - Arn Runtime: python3.9 FunctionName: !Join [ "_", [ !Ref AWS::StackName, 'LexImportFunction' ] ] MemorySize: 128 Timeout: 300 Environment: Variables: TopicArn: !Ref LexRole LambdaRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: !Join [ "_", [ !Ref AWS::StackName, 'LambdaRolePolicy' ] ] PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'dynamodb:BatchGetItem' - 'dynamodb:GetItem' - 'dynamodb:Query' - 'dynamodb:Scan' - 'dynamodb:BatchWriteItem' - 'dynamodb:PutItem' - 'dynamodb:UpdateItem' - 'dynamodb:DescribeTable' - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' - 'logs:DescribeLogStreams' Resource: - !GetAtt DynamoDBTable.Arn - 'arn:aws:logs:*:*:*' LexImportRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonLexFullAccess Policies: - PolicyName: !Join [ "_", [ !Ref AWS::StackName, 'LexImportPolicy' ] ] PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'lambda:PublishVersion' - 'lambda:AddPermission' - 'lambda:GetFunction' - 'sts:GetCallerIdentity' - 'iam:GetRole' - 'iam:PassRole' Resource: - !Sub arn:aws:lex:${AWS::Region}:${AWS::AccountId}:* - !Sub arn:aws:iam::${AWS::AccountId}:role/* - !Sub arn:aws:lex:${AWS::Region}:${AWS::AccountId}:bot/* - !Sub arn:aws:lex:${AWS::Region}:${AWS::AccountId}:bot-alias/* - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:* InvokeLexImportFunction: DependsOn: LambdaBusinessLogic Type: Custom::InvokeLexImportFunction Version: '1.0' Properties: ServiceToken: !GetAtt LexImportFunction.Arn RoleARN: !GetAtt LexRole.Arn LambdaFunctionName: !Ref BusinessLogicFunctionName BotName: !Ref BotName DynamoDBTable: Type: 'AWS::DynamoDB::Table' Properties: PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true AttributeDefinitions: - AttributeName: record_type_id AttributeType: S - AttributeName: customer_id AttributeType: S KeySchema: - AttributeName: customer_id KeyType: HASH - AttributeName: record_type_id KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: '5' WriteCapacityUnits: '5' TableName: !Ref DynamoDBTableName InvokeDynamoDBImportFunction: DependsOn: DynamoDBTable Type: 'Custom::InvokeDynamoDBImportFunction' Properties: ServiceToken: !GetAtt DynamoDBImportFunction.Arn TableName: !Ref DynamoDBTable key2: - list key3: key4: map DynamoDBImportFunction: Type: 'AWS::Lambda::Function' Properties: Code: S3Bucket: !FindInMap [BucketName, !Ref "AWS::Region", 'Name'] S3Key: !FindInMap [S3Path, 'DBImportSource', 'Name'] Handler: lambda_function.lambda_handler Role: !GetAtt - LambdaRole - Arn Runtime: python3.9 FunctionName: !Join [ "_", [ !Ref AWS::StackName, 'DynamoDBImportFunction' ] ] MemorySize: 128 Timeout: 300 LambdaBusinessLogic: Type: 'AWS::Lambda::Function' Properties: Code: S3Bucket: !FindInMap [BucketName, !Ref "AWS::Region", 'Name'] S3Key: !FindInMap [S3Path, 'BusinessLogicSource', 'Name'] Handler: lambda_function.lambda_handler Role: !GetAtt - LambdaRole - Arn Runtime: python3.9 FunctionName: !Ref BusinessLogicFunctionName MemorySize: 128 Timeout: 300 Environment: Variables: dynamodb_tablename: !Ref DynamoDBTableName databaseUser: admin LambdaPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt LambdaBusinessLogic.Arn Action: lambda:InvokeFunction Principal: lexv2.amazonaws.com SourceArn: !GetAtt InvokeLexImportFunction.lex_arn ConnectImportFunction: Type: 'AWS::Lambda::Function' Properties: Code: S3Bucket: !FindInMap [BucketName, !Ref "AWS::Region", 'Name'] S3Key: !FindInMap [S3Path, 'ConnectImportSource', 'Name'] Handler: lambda_function.lambda_handler Role: !GetAtt - ConnectRole - Arn Runtime: python3.9 FunctionName: !Join [ "_", [ !Ref AWS::StackName, 'ConnectImportFunction' ] ] MemorySize: 128 Timeout: 300 ConnectRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonLexFullAccess Policies: - PolicyName: !Join [ "_", [ !Ref AWS::StackName, 'ConnectRole' ] ] PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'connect:CreateContactFlow' - 'connect:AssociateBot' - 'connect:DescribeContactFlow' - 'connect:ListContactFlows' - 'iam:AddRoleToInstanceProfile' - 'iam:AddUserToGroup' - 'iam:AttachGroupPolicy' - 'iam:AttachRolePolicy' - 'iam:AttachUserPolicy' - 'iam:CreateInstanceProfile' - 'iam:CreatePolicy' - 'iam:CreateRole' - 'iam:CreateServiceLinkedRole' - 'iam:CreateUser' - 'iam:DetachGroupPolicy' - 'iam:DetachRolePolicy' - 'iam:DetachUserPolicy' - 'iam:GetGroup' - 'iam:GetGroupPolicy' - 'iam:GetInstanceProfile' - 'iam:GetLoginProfile' - 'iam:PutGroupPolicy' - 'iam:PutRolePolicy' - 'iam:PutUserPolicy' - 'iam:UpdateGroup' - 'iam:UpdateRole' - 'iam:UpdateUser' - 'iam:GetPolicy' - 'iam:GetPolicyVersion' - 'iam:GetRole' - 'iam:GetRolePolicy' - 'iam:GetUser' - 'iam:GetUserPolicy' - 'iam:CreatePolicyVersion' - 'iam:SetDefaultPolicyVersion' - 'logs:CreateLogStream' - 'logs:PutLogEvents' - 'logs:DescribeLogStreams' Resource: '*' InvokeConnectImportFunction: Type: Custom::InvokeConnectImportFunction Version: '1.0' Properties: ServiceToken: !GetAtt ConnectImportFunction.Arn BotAliasArn: !GetAtt InvokeLexImportFunction.lex_arn ContactName: !Ref ContactFlowName ConnectInstanceARN: !Ref ConnectInstanceARN BotName: !Ref BotName Outputs: AmazonConnect: Description: 'Connect Status' Value: !GetAtt InvokeConnectImportFunction.ContactFlowDescription CustomerData: Description: 'Sample customer data' Value: 'https://lex-usecases-templates.s3.amazonaws.com/AirlinesBot_customer_data.html' ================================================ FILE: examples/chat-demo-app/lib/bedrock-agent-construct.ts ================================================ import * as cdk from 'aws-cdk-lib'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; import { bedrock } from "@cdklabs/generative-ai-cdk-constructs"; import { Construct } from 'constructs'; import * as path from "path"; import * as custom_resources from 'aws-cdk-lib/custom-resources'; import { createHash } from 'crypto'; export class BedrockKbConstruct extends Construct { public readonly bedrockAgent: bedrock.Agent; public readonly description:string = "Agent in charge of providing response regarding the \ Agent Squad framework. Where to start, how to create an orchestrator.\ what are the different elements of the framework. Always Respond in mardown format"; public readonly knowledgeBaseId: string; constructor(scope: Construct, id: string) { super(scope, id); const knowledgeBase = new bedrock.KnowledgeBase(this, 'KnowledgeBaseDocs', { embeddingsModel: bedrock.BedrockFoundationModel.COHERE_EMBED_MULTILINGUAL_V3, instruction: "Knowledge Base containing the framework documentation", description:"Knowledge Base containing the framework documentation" }); this.knowledgeBaseId = knowledgeBase.knowledgeBaseId; const documentsBucket = new s3.Bucket(this, 'DocumentsBucket', { enforceSSL:true, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, }); const menuDataSource = new bedrock.S3DataSource(this, 'DocumentsDataSource', { bucket: documentsBucket, knowledgeBase: knowledgeBase, dataSourceName: "Documentation", chunkingStrategy: bedrock.ChunkingStrategy.FIXED_SIZE, maxTokens: 500, overlapPercentage: 20, }); this.bedrockAgent = new bedrock.Agent(this, "AgentSquadDocumentationAgent", { name: "agent-squad-Documentation-Agent", description: "A tech expert specializing in the Agent Squad framework, technical domains, and AI-driven solutions. ", foundationModel: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_SONNET_V1_0, instruction: `You are a tech expert specializing in both the technical domain, including software development, AI, cloud computing, and the Agent Squad framework. Your role is to provide comprehensive, accurate, and helpful information about these areas, with a specific focus on the orchestrator framework, its agents, and their applications. Always structure your responses using clear, well-formatted markdown. Key responsibilities: - Explain the Agent Squad framework, its agents, and its benefits - Guide users on how to get started with the framework and configure agents - Provide technical advice on topics like software development, AI, and cloud computing - Detail the process of creating and configuring an orchestrator - Describe the various components and elements of the framework - Provide examples and best practices for technical implementation When responding to queries: 1. Start with a brief overview of the topic 2. Break down complex concepts into clear, digestible sections 3. **When the user asks for an example or code, always respond with a code snippet, using proper markdown syntax for code blocks (\`\`\`).** Provide explanations alongside the code when necessary. 4. Conclude with next steps or additional resources if relevant Always use proper markdown syntax, including: - Headings (##, ###) for main sections and subsections - Bullet points (-) or numbered lists (1., 2., etc.) for enumerating items - Code blocks (\`\`\`) for code snippets or configuration examples - Bold (**text**) for emphasizing key terms or important points - Italic (*text*) for subtle emphasis or introducing new terms - Links ([text](URL)) when referring to external resources or documentation Tailor your responses to both beginners and experienced developers, providing clear explanations and technical depth as appropriate.`, idleSessionTTL: cdk.Duration.minutes(10), shouldPrepareAgent: true, aliasName: "latest", knowledgeBases: [knowledgeBase] }); const assetsPath = path.join(__dirname, "../../../docs/src/content/docs/"); const assetDoc = s3deploy.Source.asset(assetsPath); const assetsTsPath = path.join(__dirname, "../../../typescript/src/"); const assetTsDoc = s3deploy.Source.asset(assetsTsPath); const assetsPyPath = path.join(__dirname, "../../../python/src/agent_squad/"); const assetPyDoc = s3deploy.Source.asset(assetsPyPath); new s3deploy.BucketDeployment(this, "DeployDocumentation", { sources: [assetDoc, assetTsDoc, assetPyDoc], destinationBucket: documentsBucket }); const payload: string = JSON.stringify({ dataSourceId: menuDataSource.dataSourceId, knowledgeBaseId: knowledgeBase.knowledgeBaseId, }); const syncDataSourceLambdaRole = new iam.Role(this, 'SyncDataSourceLambdaRole', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com') }); syncDataSourceLambdaRole.addManagedPolicy( iam.ManagedPolicy.fromManagedPolicyArn( this, "syncDataSourceLambdaRoleAWSLambdaBasicExecutionRole", "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ) ); const syncDataSourceLambda = new lambda.Function(this, 'SyncDataSourceLambda', { runtime: lambda.Runtime.PYTHON_3_12, handler: 'lambda.lambda_handler', code: lambda.Code.fromAsset('lambda/sync_bedrock_knowledgebase/'), role: syncDataSourceLambdaRole }); syncDataSourceLambda.addToRolePolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ "bedrock:StartIngestionJob", ], resources: [knowledgeBase.knowledgeBaseArn], }) ); const payloadHashPrefix = createHash('md5').update(payload).digest('hex').substring(0, 6) const sdkCall: custom_resources.AwsSdkCall = { service: 'Lambda', action: 'invoke', parameters: { FunctionName: syncDataSourceLambda.functionName, Payload: payload }, physicalResourceId: custom_resources.PhysicalResourceId.of(`${id}-AwsSdkCall-${syncDataSourceLambda.currentVersion.version + payloadHashPrefix}`) }; const customResourceFnRole = new iam.Role(this, 'AwsCustomResourceRole', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com') }); customResourceFnRole.addToPolicy( new iam.PolicyStatement({ resources: [syncDataSourceLambda.functionArn], actions: ['lambda:InvokeFunction'] }) ); const customResource = new custom_resources.AwsCustomResource(this, 'AwsCustomResource', { onCreate: sdkCall, onUpdate: sdkCall, policy: custom_resources.AwsCustomResourcePolicy.fromSdkCalls({ resources: custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE, }), role: customResourceFnRole }); } } ================================================ FILE: examples/chat-demo-app/lib/chat-demo-app-stack.ts ================================================ import * as cdk from 'aws-cdk-lib'; import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; import { Construct } from 'constructs'; import * as path from "path"; import { LexAgentConstruct } from './lex-agent-construct'; import { BedrockKnowledgeBase } from './knowledge-base-construct'; import {BedrockKnowledgeBaseModels } from './constants'; export class ChatDemoStack extends cdk.Stack { public multiAgentLambdaFunctionUrl: cdk.aws_lambda.FunctionUrl; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const enableLexAgent = this.node.tryGetContext('enableLexAgent'); let lexAgent = null; let lexAgentConfig = {}; if (enableLexAgent === true){ lexAgent = new LexAgentConstruct(this, "LexAgent"); lexAgentConfig = { botId: lexAgent.lexBotId, botAliasId: lexAgent.lexBotAliasId, localeId: "en_US", description: lexAgent.lexBotDescription, name: lexAgent.lexBotName, } } const documentsBucket = new s3.Bucket(this, 'DocumentsBucket', { enforceSSL:true, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, }); const assetsPath = path.join(__dirname, "../../../docs/src/content/docs/"); const assetDoc = s3deploy.Source.asset(assetsPath); const assetsTsPath = path.join(__dirname, "../../../typescript/src/"); const assetTsDoc = s3deploy.Source.asset(assetsTsPath); const assetsPyPath = path.join(__dirname, "../../../python/src/agent_squad/"); const assetPyDoc = s3deploy.Source.asset(assetsPyPath); const knowledgeBase = new BedrockKnowledgeBase(this, 'MutiAgentOrchestratorDocKb', { kbName:'agent-squad-doc-kb', assetFiles:[], embeddingModel: BedrockKnowledgeBaseModels.TITAN_EMBED_TEXT_V1, }); const maoFilesDeployment = new s3deploy.BucketDeployment(this, "DeployDocumentation", { sources: [assetDoc, assetTsDoc, assetPyDoc], destinationBucket: documentsBucket, }); knowledgeBase.addS3Permissions(documentsBucket.bucketName); knowledgeBase.createAndSyncDataSource(documentsBucket.bucketArn); const powerToolsTypeScriptLayer = lambda.LayerVersion.fromLayerVersionArn( this, "powertools-layer-ts", `arn:aws:lambda:${ cdk.Stack.of(this).region }:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:2` ); const basicLambdaRole = new iam.Role(this, "BasicLambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), }); basicLambdaRole.addManagedPolicy( iam.ManagedPolicy.fromManagedPolicyArn( this, "basicLambdaRoleAWSLambdaBasicExecutionRole", "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ) ); const sessionTable = new dynamodb.Table(this, "SessionTable", { partitionKey: { name: "PK", type: dynamodb.AttributeType.STRING }, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, sortKey: { name: "SK", type: dynamodb.AttributeType.STRING }, timeToLiveAttribute: "TTL", removalPolicy: cdk.RemovalPolicy.DESTROY, }); const pythonLambda = new lambda.Function(this, "PythonLambda", { runtime: lambda.Runtime.PYTHON_3_12, handler: "lambda.lambda_handler", code: lambda.Code.fromAsset( path.join(__dirname, "../lambda/find-my-name") ), memorySize: 128, timeout: cdk.Duration.seconds(10), }) const multiAgentLambdaFunction = new nodejs.NodejsFunction( this, "MultiAgentLambda", { entry: path.join( __dirname, "../lambda/multi-agent/index.ts" ), runtime: lambda.Runtime.NODEJS_20_X, role: basicLambdaRole, memorySize: 2048, timeout: cdk.Duration.minutes(5), layers: [powerToolsTypeScriptLayer], environment: { POWERTOOLS_SERVICE_NAME: "multi-agent", POWERTOOLS_LOG_LEVEL: "DEBUG", HISTORY_TABLE_NAME: sessionTable.tableName, HISTORY_TABLE_TTL_KEY_NAME: 'TTL', HISTORY_TABLE_TTL_DURATION: '3600', LEX_AGENT_ENABLED: enableLexAgent.toString(), LEX_AGENT_CONFIG: JSON.stringify(lexAgentConfig), KNOWLEDGE_BASE_ID: knowledgeBase.knowledgeBase.attrKnowledgeBaseId, LAMBDA_AGENTS: JSON.stringify( [{description:"This is an Agent to use when you forgot about your own name",name:'Find my name',functionName:pythonLambda.functionName, region:cdk.Aws.REGION}]), }, bundling: { minify: false, externalModules: [ //"aws-lambda", "@aws-lambda-powertools/logger", "@aws-lambda-powertools/parameters", //"@aws-sdk/client-ssm", ], }, } ); sessionTable.grantReadWriteData(multiAgentLambdaFunction); pythonLambda.grantInvoke(multiAgentLambdaFunction); multiAgentLambdaFunction.addToRolePolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ "bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream", ], resources: [ `arn:aws:bedrock:${cdk.Aws.REGION}::foundation-model/*`, ], }) ); multiAgentLambdaFunction.addToRolePolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, sid: 'AmazonBedrockKbPermission', actions: [ "bedrock:Retrieve", "bedrock:RetrieveAndGenerate" ], resources: [ `arn:aws:bedrock:${cdk.Aws.REGION}::foundation-model/*`, `arn:${cdk.Aws.PARTITION}:bedrock:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:knowledge-base/${knowledgeBase.knowledgeBase.attrKnowledgeBaseId}` ] }) ); const multiAgentLambdaFunctionUrl = multiAgentLambdaFunction.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.AWS_IAM, invokeMode: lambda.InvokeMode.RESPONSE_STREAM, }); this.multiAgentLambdaFunctionUrl = multiAgentLambdaFunctionUrl; if (enableLexAgent){ multiAgentLambdaFunction.addToRolePolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, sid: 'LexPermission', actions: [ "lex:RecognizeText", ], resources: [ `arn:aws:bedrock:${cdk.Aws.REGION}::foundation-model/*`, `arn:aws:lex:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:bot-alias/${lexAgent!.lexBotId}/${lexAgent!.lexBotAliasId}` ], }) ); } } } ================================================ FILE: examples/chat-demo-app/lib/constants.ts ================================================ export const USER_INPUT_ACTION_NAME = "UserInputAction"; export const USER_INPUT_PARENT_SIGNATURE = "AMAZON.UserInput"; export const AMAZON_BEDROCK_METADATA = 'AMAZON_BEDROCK_METADATA'; export const AMAZON_BEDROCK_TEXT_CHUNK = 'AMAZON_BEDROCK_TEXT_CHUNK'; export const KB_DEFAULT_VECTOR_FIELD = 'bedrock-knowledge-base-default-vector'; export const MAX_KB_SUPPORTED = 2; export const DEFAULT_BLOCKED_INPUT_MESSAGE ='Invalid input. Query violates our usage policy.'; export const DEFAULT_BLOCKED_OUTPUT_MESSAGE = 'Unable to process. Query violates our usage policy.'; export class BedrockKnowledgeBaseModels { public static readonly TITAN_EMBED_TEXT_V1 = new BedrockKnowledgeBaseModels("amazon.titan-embed-text-v1", 1536); public static readonly COHERE_EMBED_ENGLISH_V3 = new BedrockKnowledgeBaseModels("cohere.embed-english-v3", 1024); public static readonly COHERE_EMBED_MULTILINGUAL_V3 = new BedrockKnowledgeBaseModels("cohere.embed-multilingual-v3", 1024); public readonly modelName: string; public readonly vectorDimension: number; constructor(modelName: string, vectorDimension: number) { this.modelName = modelName; this.vectorDimension = vectorDimension; } public getArn(region:string): string { return `arn:aws:bedrock:${region}::foundation-model/${this.modelName}`; } } ================================================ FILE: examples/chat-demo-app/lib/knowledge-base-construct.ts ================================================ import { Effect, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { CustomResource, Duration, Stack, aws_bedrock as bedrock } from 'aws-cdk-lib'; import { Construct } from "constructs"; import { OpenSearchServerlessHelper, OpenSearchServerlessHelperProps } from "./utils/OpensearchServerlessHelper"; import { AMAZON_BEDROCK_METADATA, AMAZON_BEDROCK_TEXT_CHUNK, KB_DEFAULT_VECTOR_FIELD } from "./constants"; import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; import { Runtime, LayerVersion } from "aws-cdk-lib/aws-lambda"; import { resolve } from "path"; import { Provider } from "aws-cdk-lib/custom-resources"; import { FileBufferMap, generateFileBufferMap, generateNamesForAOSS } from "./utils/utils"; import { BedrockKnowledgeBaseModels } from "./constants"; export enum KnowledgeBaseStorageConfigurationTypes { OPENSEARCH_SERVERLESS = "OPENSEARCH_SERVERLESS", PINECONE = "PINECONE", RDS = "RDS" } export interface KnowledgeBaseStorageConfigurationProps { type: KnowledgeBaseStorageConfigurationTypes; configuration?: OpenSearchServerlessHelperProps } export interface BedrockKnowledgeBaseProps { /** * The name of the knowledge base. * This is a required parameter and must be a non-empty string. */ kbName: string; /** * The embedding model to be used for the knowledge base. * This is an optional parameter and defaults to titan-embed-text-v1. * The available embedding models are defined in the `EmbeddingModels` enum. */ embeddingModel?: BedrockKnowledgeBaseModels; /** * The asset files to be added to the knowledge base. * This is an optional parameter and can be either: * 1. An array of file buffers (Buffer[]), or * 2. A FileBufferMap object, where the keys are file names and the values are file buffers. * * If an array of file buffers is provided, a FileBufferMap will be created internally, * with randomly generated UUIDs as the keys and the provided file buffers as the values. * This allows you to attach files without specifying their names. */ assetFiles?: FileBufferMap | Buffer[]; /** * The vector storage configuration for the knowledge base. * This is an optional parameter and defaults to OpenSearchServerless. * The available storage configurations are defined in the `KnowledgeBaseStorageConfigurationTypes` enum. */ storageConfiguration?: KnowledgeBaseStorageConfigurationProps; } export class BedrockKnowledgeBase extends Construct { public readonly knowledgeBaseName: string; public knowledgeBase: bedrock.CfnKnowledgeBase; public assetFiles: FileBufferMap; private embeddingModel: BedrockKnowledgeBaseModels; private kbRole: Role; private accountId: string; private region: string; constructor(scope: Construct, id: string, props: BedrockKnowledgeBaseProps) { super(scope, id); // Check if user has opted out of creating KB if (this.node.tryGetContext("skipKBCreation") === "true") return; this.accountId = Stack.of(this).account; this.region = Stack.of(this).region; this.embeddingModel = props.embeddingModel ?? BedrockKnowledgeBaseModels.TITAN_EMBED_TEXT_V1; this.knowledgeBaseName = props.kbName; this.addAssetFiles(props.assetFiles); this.kbRole = this.createRoleForKB(); // Create the knowledge base facade. this.knowledgeBase = this.createKnowledgeBase(props.kbName); // Setup storageConfigurations const storageConfig = props.storageConfiguration?.type ?? KnowledgeBaseStorageConfigurationTypes.OPENSEARCH_SERVERLESS; // Default to OpenSearchServerless switch (storageConfig) { case KnowledgeBaseStorageConfigurationTypes.OPENSEARCH_SERVERLESS: this.setupOpensearchServerless(props.kbName, this.region, this.accountId); break; default: throw new Error(`Unsupported storage configuration type: ${storageConfig}`); } } /** * Adds asset files to the Knowledge Base. * * @param files - An array of Buffers representing the asset files, a FileBufferMap object, or undefined. * * @remarks * This method adds the provided asset files to the Knowledge Base by converting files to an internal * representation of FileBufferMap (Interface to store the combination of filenames and their contents) */ public addAssetFiles(files: Buffer[] | FileBufferMap | undefined) { if (!files) return; const fileBufferMap: FileBufferMap = Array.isArray(files) ? generateFileBufferMap(files) : files; this.assetFiles = { ...this.assetFiles, ...fileBufferMap }; } /** * Creates a new Amazon Bedrock Knowledge Base (CfnKnowledgeBase) resource. * * @param kbName - The name of the Knowledge Base. * @returns The created Amazon Bedrock CfnKnowledgeBase resource. */ private createKnowledgeBase(kbName: string) { return new bedrock.CfnKnowledgeBase( this, "KnowledgeBase", { knowledgeBaseConfiguration: { type: 'VECTOR', vectorKnowledgeBaseConfiguration: { embeddingModelArn: this.embeddingModel.getArn(this.region), }, }, name: kbName, roleArn: this.kbRole.roleArn, storageConfiguration: { type: 'NOT_SET' } } ); } /** * Creates a service role that can access the FoundationalModel. * @returns Service role for KB */ private createRoleForKB(): Role { const embeddingsAccessPolicyStatement = new PolicyStatement({ sid: 'AllowKBToInvokeEmbedding', effect: Effect.ALLOW, actions: ['bedrock:InvokeModel'], resources: [this.embeddingModel.getArn(this.region)], }); const kbRole = new Role(this, 'BedrockKBServiceRole', { assumedBy: new ServicePrincipal('bedrock.amazonaws.com'), }); kbRole.addToPolicy(embeddingsAccessPolicyStatement); return kbRole; } /** * Grants the Knowledge Base permissions to access objects and list contents * in the specified S3 bucket, but only if the request originates from the provided AWS account ID. * * @param bucketName The name of the S3 bucket to grant access to. */ public addS3Permissions(bucketName: string) { const s3AssetsAccessPolicyStatement = new PolicyStatement({ sid: 'AllowKBToAccessAssets', effect: Effect.ALLOW, actions: ['s3:GetObject', 's3:ListBucket'], resources: [ `arn:aws:s3:::${bucketName}/*`, `arn:aws:s3:::${bucketName}` ] }); this.kbRole.addToPolicy(s3AssetsAccessPolicyStatement); } /** DataSource operations */ /** * Synchronizes the data source for the specified knowledge base. * * This function performs the following steps: * * 1. Creates a Lambda execution role with the necessary permissions to start an ingestion job for the specified knowledge base. * 2. Creates a Node.js Lambda function that will handle the custom resource event for data source synchronization. * 3. Creates a custom resource provider that uses the Lambda function as the event handler. * 4. Creates a custom resource that represents the data source synchronization process, passing the knowledge base ID and data source ID as properties. * * The custom resource creation triggers the Lambda function to start the ingestion job for the specified knowledge base, synchronizing the data source. * * @param dataSourceId - The ID of the data source to synchronize. * @param knowledgeBaseId - The ID of the knowledge base to synchronize the data source for. * @returns The custom resource that represents the data source synchronization process. */ private syncDataSource(dataSourceId: string, knowledgeBaseId: string) { // Create an execution role for the custom resource to execute lambda const lambdaExecutionRole = new Role(this, 'DataSyncLambdaRole', { assumedBy: new ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], inlinePolicies: { DataSyncAccess: new PolicyDocument({ statements: [ new PolicyStatement({ effect: Effect.ALLOW, actions: ["bedrock:StartIngestionJob", "bedrock:DeleteDataSource", // Delete a data source associated with the knowledgebase "bedrock:DeleteKnowledgeBase", // Delete the knowledgebase "bedrock:GetDataSource", // Get information about a data source associated with the knowledgebase "bedrock:UpdateDataSource"], // Update a data source associated with the knowledgebase resources: [`arn:aws:bedrock:${this.region}:${this.accountId}:knowledge-base/${knowledgeBaseId}`], }), ], }), }, }); const powerToolsTypeScriptLayer = LayerVersion.fromLayerVersionArn( this, "powertools-layer-ts-kb", `arn:aws:lambda:${this.region}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:2` ); const onEventHandler = new NodejsFunction(this, 'DataSyncCustomResourceHandler', { memorySize: 128, timeout: Duration.minutes(15), runtime: Runtime.NODEJS_18_X, handler: 'onEvent', layers:[powerToolsTypeScriptLayer], entry: resolve(__dirname, 'CustomResourcesLambda', `data-source-sync.ts`), bundling: { minify: false, externalModules: [ '@aws-lambda-powertools/logger' ], }, role: lambdaExecutionRole, }); const provider = new Provider(this, 'Provider', { onEventHandler: onEventHandler, }); // Create an index in the OpenSearch collection return new CustomResource(this, 'DataSyncLambda', { serviceToken: provider.serviceToken, properties: { knowledgeBaseId: knowledgeBaseId, dataSourceId: dataSourceId, }, }); } /** * Creates and synchronizes an Amazon Bedrock data source after the deployment of an assets. * * This function is called by the BlueprintConstructs to initialize the data source for a knowledge base. * It creates a new CfnDataSource with the specified asset bucket ARN and folder name, and then synchronizes * the data source with the knowledge base, using a customResource. * * @param assetBucketArn - The ARN of the asset bucket where the data source files are stored. * @returns The created CfnDataSource instance. */ public createAndSyncDataSource(assetBucketArn: string): bedrock.CfnDataSource { const cfnDataSource = new bedrock.CfnDataSource(this, 'BlueprintsDataSource', { dataSourceConfiguration: { s3Configuration: { bucketArn: assetBucketArn, }, type: 'S3', }, knowledgeBaseId: this.knowledgeBase.attrKnowledgeBaseId, name: `${this.knowledgeBase.name}-DataSource`, // the properties below are optional dataDeletionPolicy: 'RETAIN', // Changed to RETAIN since data source deletion upon stack deletion works only when the data deletion policy is set to RETAIN description: 'Data source for KB', vectorIngestionConfiguration: { chunkingConfiguration: { chunkingStrategy: 'FIXED_SIZE', // the properties below are optional fixedSizeChunkingConfiguration: { maxTokens: 1024, overlapPercentage: 20, }, }, }, }); this.syncDataSource(cfnDataSource.attrDataSourceId, this.knowledgeBase.attrKnowledgeBaseId); return cfnDataSource; } /** AOSS Operations */ /** * Sets up an Amazon OpenSearch Serverless (AOSS) collection for the Knowledge Base (KB). * * @param kbName - The name of the Knowledge Base. * @param region - The AWS region where the AOSS collection will be created. * @param accountId - The AWS account ID where the AOSS collection will be created. * * @remarks * This method performs the following steps: * 1. Generates a name for the AOSS collection based on the provided `kbName`. * 2. Creates an execution role for a Lambda function that validates permission propagation. * 3. Creates a new AOSS collection with the generated name, access roles, region, and account ID. * 4. Grants the KB and the validation Lambda execution role access to the AOSS collection. * 5. Waits for the permission propagation in AOSS (up to 2 minutes) before accessing the index. * 6. Adds the AOSS storage configuration to the KB. * 7. Sets up dependencies between the KB and the permission custom resource. */ private setupOpensearchServerless(kbName: string, region: string, accountId: string) { const aossCollectionName = generateNamesForAOSS(kbName, 'collection'); const validationLambdaExecutionRole = this.createValidationLambdaRole(); // Create the AOSS collection. const aossCollection = new OpenSearchServerlessHelper(this, 'AOSSCollectionForKB', { collectionName: aossCollectionName, accessRoles: [this.kbRole, validationLambdaExecutionRole], region: region, accountId: accountId, }); // Once collection is created, allow KB to access it this.addAOSSPermissions(aossCollection.collection.attrArn); // Permission propagation in AOSS can take up to 2 mins, wait until an // index can be accessed. const permissionCustomResource = this.waitForPermissionPropagation(validationLambdaExecutionRole, aossCollection.collection.attrCollectionEndpoint, aossCollection.indexName); permissionCustomResource.node.addDependency(aossCollection.collection); this.addAOSSStorageConfigurationToKB(aossCollection.collection.attrArn, aossCollection.indexName); this.knowledgeBase.node.addDependency(permissionCustomResource); } /** * Associate the AOSS configuration to the KB. */ private addAOSSStorageConfigurationToKB(collectionArn: string, collectionIndexName: string) { this.knowledgeBase.storageConfiguration = { type: 'OPENSEARCH_SERVERLESS', opensearchServerlessConfiguration: { collectionArn: collectionArn, fieldMapping: { metadataField: AMAZON_BEDROCK_METADATA, textField: AMAZON_BEDROCK_TEXT_CHUNK, vectorField: KB_DEFAULT_VECTOR_FIELD, }, vectorIndexName: collectionIndexName, } }; } /** * Allow KB to invoke AOSS collection and indices * @param collectionArn AOSS collection ARN that the KB operates on. */ private addAOSSPermissions(collectionArn: string) { const AOSSAccessPolicyStatement = new PolicyStatement({ sid: 'AllowKBToAccessAOSS', effect: Effect.ALLOW, actions: ['aoss:APIAccessAll'], resources: [collectionArn], }); this.kbRole.addToPolicy(AOSSAccessPolicyStatement); } /** * Create an execution role for the custom resource to execute lambda * @returns Role with permissions to acess the AOSS collection and indices */ private createValidationLambdaRole() { return new Role(this, 'PermissionValidationRole', { assumedBy: new ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], inlinePolicies: { AOSSAccess: new PolicyDocument({ statements: [ new PolicyStatement({ effect: Effect.ALLOW, actions: ['aoss:APIAccessAll'], resources: ['*'], //We aren't able to make it restrictive as the cluster arn is generated at runtime }), ], }), }, }); } /** * Deploys a custom resource that checks the existence of an OpenSearch index and retries the operation * if the index is not found, with a configurable retry strategy. * * This function is necessary because Amazon OpenSearch Service (AOSS) permissions can take up to * 2 minutes to create and propagate. The custom resource is used to ensure that the index is * available before proceeding with further resource creation. * * @param validationRole - Custom resource Lambda execution role. * @param collectionEndpoint - The endpoint of the OpenSearch collection. * @param indexName - The name of the OpenSearch index to be validated. * @returns The created CustomResource instance. */ private waitForPermissionPropagation(validationRole: Role, collectionEndpoint: string, indexName: string) { const powerToolsTypeScriptLayer = LayerVersion.fromLayerVersionArn( this, "powertools-layer-ts", `arn:aws:lambda:${this.region}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:2` ); const onEventHandler = new NodejsFunction(this, 'PermissionCustomResourceHandler', { memorySize: 128, timeout: Duration.minutes(15), runtime: Runtime.NODEJS_18_X, handler: 'onEvent', layers:[powerToolsTypeScriptLayer], entry: resolve(__dirname, 'CustomResourcesLambda', `permission-validation.ts`), bundling: { minify: false, externalModules: ['@aws-lambda-powertools/logger'], }, role: validationRole, }); const provider = new Provider(this, 'PermissionValidationProvider', { onEventHandler: onEventHandler, }); // Create an index in the OpenSearch collection return new CustomResource(this, 'PermissionValidationCustomResource', { serviceToken: provider.serviceToken, properties: { collectionEndpoint: collectionEndpoint, indexName: indexName, }, }); } } ================================================ FILE: examples/chat-demo-app/lib/lex-agent-construct.ts ================================================ import * as cdk from 'aws-cdk-lib'; import * as cfn_include from 'aws-cdk-lib/cloudformation-include'; import { Construct } from 'constructs'; import * as path from "path"; export class LexAgentConstruct extends Construct { public readonly lexBotDescription:string = 'Helps users book and manage their flight reservation'; public readonly lexBotName; public readonly lexBotId; public readonly lexBotAliasId; public readonly lexBotLocale = 'en_US'; constructor(scope: Construct, id: string) { super(scope, id); const template = new cfn_include.CfnInclude(this, "template", { templateFile: path.join(__dirname, "airlines.yaml"), }); const lexBotResource = template.getResource('InvokeLexImportFunction') as cdk.CfnResource; const lexBotName = template.getParameter('BotName') as cdk.CfnParameter; this.lexBotName = lexBotName.valueAsString; this.lexBotId = lexBotResource.getAtt('bot_id').toString(); this.lexBotAliasId = lexBotResource.getAtt('bot_alias_id').toString(); } } ================================================ FILE: examples/chat-demo-app/lib/user-interface-stack.ts ================================================ import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as path from "node:path"; import { ExecSyncOptionsWithBufferEncoding, execSync, } from "node:child_process"; import { Utils } from "./utils/utils"; import * as apigateway from "aws-cdk-lib/aws-apigateway"; import * as cf from "aws-cdk-lib/aws-cloudfront"; import * as s3 from "aws-cdk-lib/aws-s3"; import * as iam from "aws-cdk-lib/aws-iam"; import * as s3deploy from "aws-cdk-lib/aws-s3-deployment"; import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager"; import * as cognitoIdentityPool from "@aws-cdk/aws-cognito-identitypool-alpha"; import * as cognito from "aws-cdk-lib/aws-cognito"; import * as lambda from "aws-cdk-lib/aws-lambda"; import * as cloudfront_origins from "aws-cdk-lib/aws-cloudfront-origins"; interface UserInterfaceProps extends cdk.StackProps{ multiAgentLambdaFunctionUrl:cdk.aws_lambda.FunctionUrl } export class UserInterfaceStack extends cdk.Stack { public distribution: cf.Distribution; public behaviorOptions: cf.AddBehaviorOptions; public authFunction: cf.experimental.EdgeFunction; constructor(scope: Construct, id: string, props?: UserInterfaceProps ) { super(scope, id, props); const appPath = path.join(__dirname, "../ui"); const buildPath = path.join(appPath, "dist"); const websiteBucket = new s3.Bucket(this, "WebsiteBucket", { enforceSSL: true, encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: new s3.BlockPublicAccess({ blockPublicPolicy: true, blockPublicAcls: true, ignorePublicAcls: true, restrictPublicBuckets: true, }), }); const hostingOrigin = new cloudfront_origins.S3Origin(websiteBucket); const myResponseHeadersPolicy = new cf.ResponseHeadersPolicy( this, "ResponseHeadersPolicy", { responseHeadersPolicyName: "ResponseHeadersPolicy" + cdk.Aws.STACK_NAME + "-" + cdk.Aws.REGION, comment: "ResponseHeadersPolicy" + cdk.Aws.STACK_NAME + "-" + cdk.Aws.REGION, securityHeadersBehavior: { contentTypeOptions: { override: true }, frameOptions: { frameOption: cf.HeadersFrameOption.DENY, override: true, }, referrerPolicy: { referrerPolicy: cf.HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN, override: false, }, strictTransportSecurity: { accessControlMaxAge: cdk.Duration.seconds(31536000), includeSubdomains: true, override: true, }, xssProtection: { protection: true, modeBlock: true, override: true }, }, } ); this.distribution = new cf.Distribution( this, "Distribution", { comment: "Agent Squad demo app", defaultRootObject: "index.html", httpVersion: cf.HttpVersion.HTTP2_AND_3, minimumProtocolVersion: cf.SecurityPolicyProtocol.TLS_V1_2_2021, defaultBehavior:{ origin: hostingOrigin, responseHeadersPolicy: myResponseHeadersPolicy, cachePolicy: cf.CachePolicy.CACHING_DISABLED, allowedMethods: cf.AllowedMethods.ALLOW_ALL, viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, } } ); const userPool = new cognito.UserPool(this, "UserPool", { removalPolicy: cdk.RemovalPolicy.DESTROY, selfSignUpEnabled: false, autoVerify: { email: true, phone: true }, signInAliases: { email: true, }, }); const userPoolClient = userPool.addClient("UserPoolClient", { generateSecret: false, authFlows: { adminUserPassword: true, userPassword: true, userSrp: true, }, }); const identityPool = new cognitoIdentityPool.IdentityPool( this, "IdentityPool", { authenticationProviders: { userPools: [ new cognitoIdentityPool.UserPoolAuthenticationProvider({ userPool, userPoolClient, }), ], }, } ); this.authFunction = new cf.experimental.EdgeFunction( this, `AuthFunctionAtEdge`, { handler: "index.handler", runtime: lambda.Runtime.NODEJS_20_X, code: lambda.Code.fromAsset(path.join(__dirname, "../lambda/auth")) }, ); this.authFunction.addToRolePolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["secretsmanager:GetSecretValue"], resources: [ `arn:aws:secretsmanager:${cdk.Stack.of(this).region}:${ cdk.Stack.of(this).account }:secret:UserPoolSecret*`, ], }) ); const cachePolicy = new cf.CachePolicy( this, "CachingDisabledButWithAuth", { defaultTtl: cdk.Duration.minutes(0), minTtl: cdk.Duration.minutes(0), maxTtl: cdk.Duration.minutes(1), headerBehavior: cf.CacheHeaderBehavior.allowList("Authorization"), } ); const commonBehaviorOptions: cf.AddBehaviorOptions = { viewerProtocolPolicy: cf.ViewerProtocolPolicy.HTTPS_ONLY, cachePolicy: cachePolicy, originRequestPolicy: cf.OriginRequestPolicy.CORS_CUSTOM_ORIGIN, responseHeadersPolicy: cf.ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_WITH_PREFLIGHT_AND_SECURITY_HEADERS, }; this.behaviorOptions = { ...commonBehaviorOptions, edgeLambdas: [ { functionVersion: this.authFunction.currentVersion, eventType: cf.LambdaEdgeEventType.ORIGIN_REQUEST, includeBody: true, }, ], allowedMethods: cf.AllowedMethods.ALLOW_ALL, }; const secret = new secretsmanager.Secret(this, "UserPoolSecret", { secretName: "UserPoolSecretConfig", secretObjectValue: { ClientID: cdk.SecretValue.unsafePlainText( userPoolClient.userPoolClientId ), UserPoolID: cdk.SecretValue.unsafePlainText(userPool.userPoolId), }, }); const exportsAsset = s3deploy.Source.jsonData("aws-exports.json", { region: cdk.Aws.REGION, domainName: "https://" + this.distribution.domainName, Auth: { Cognito: { userPoolClientId: userPoolClient.userPoolClientId, userPoolId: userPool.userPoolId, identityPoolId: identityPool.identityPoolId, }, } }); const asset = s3deploy.Source.asset(appPath, { bundling: { image: cdk.DockerImage.fromRegistry( "public.ecr.aws/sam/build-nodejs20.x:latest" ), command: [ "sh", "-c", [ "npm --cache /tmp/.npm install", `npm --cache /tmp/.npm run build`, "cp -aur /asset-input/dist/* /asset-output/", ].join(" && "), ], local: { tryBundle(outputDir: string) { try { const options: ExecSyncOptionsWithBufferEncoding = { stdio: "inherit", env: { ...process.env, }, }; execSync(`npm --silent --prefix "${appPath}" install`, options); execSync(`npm --silent --prefix "${appPath}" run build`, options); Utils.copyDirRecursive(buildPath, outputDir); } catch (e) { console.error(e); return false; } return true; }, }, }, }); const distribution = this.distribution; new s3deploy.BucketDeployment(this, "UserInterfaceDeployment", { prune: false, sources: [asset, exportsAsset], destinationBucket: websiteBucket, distribution, }); this.authFunction.addToRolePolicy( new iam.PolicyStatement({ sid: "AllowInvokeFunctionUrl", effect: iam.Effect.ALLOW, actions: ["lambda:InvokeFunctionUrl"], resources: [ props!.multiAgentLambdaFunctionUrl.functionArn, ], conditions: { StringEquals: { "lambda:FunctionUrlAuthType": "AWS_IAM" }, }, }) ); this.distribution.addBehavior( "/chat/*", new cloudfront_origins.HttpOrigin(cdk.Fn.select(2, cdk.Fn.split("/", props!.multiAgentLambdaFunctionUrl.url))), this.behaviorOptions ); // ################################################### // Outputs // ################################################### new cdk.CfnOutput(this, "UserInterfaceDomainName", { value: `https://${this.distribution.distributionDomainName}`, }); new cdk.CfnOutput(this, "CognitoUserPool", { value: `${userPool.userPoolId}`, }); } } ================================================ FILE: examples/chat-demo-app/lib/utils/OpensearchServerlessHelper.ts ================================================ import { Effect, ManagedPolicy, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { CustomResource, Duration, aws_opensearchserverless as opensearch } from 'aws-cdk-lib'; import { Construct } from "constructs"; import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; import { Runtime, LayerVersion } from "aws-cdk-lib/aws-lambda"; import { resolve } from 'path'; import { Provider } from "aws-cdk-lib/custom-resources"; import { generateNamesForAOSS } from "./utils"; const defaultIndexName = 'agent-blueprints-kb-default-index'; export interface OpenSearchServerlessHelperProps { collectionName: string; accessRoles: Role[]; region: string; accountId: string; collectionType?: string indexName?: string; indexConfiguration?: any; } export enum CollectionType { VECTORSEARCH = 'VECTORSEARCH', SEARCH = 'SEARCH', TIMESERIES = 'TIMESERIES', } /** * A utility class that simplifies the creation and configuration of an Amazon OpenSearch Serverless collection, * including its network policies, encryption policies, access policies, and indexes. * * This class encapsulates the logic for creating and managing the necessary resources for an OpenSearch Serverless * collection, allowing developers to easily provision and set up the collection with default configurations. */ export class OpenSearchServerlessHelper extends Construct { collection: opensearch.CfnCollection; indexName: string; constructor(scope: Construct, id: string, props: OpenSearchServerlessHelperProps) { super(scope, id); this.indexName = props.indexName ?? defaultIndexName; // Create the Lambda execution role for index manipulation const lambdaExecutionRole = this.createLambdaExecutionRoleForIndex(props.region, props.accountId); // Create access policies for the AOSS collection and index const networkPolicy = this.createNetworkPolicy(props.collectionName); const encryptionPolicy = this.createEncryptionPolicy(props.collectionName); const accessRoleArns = [lambdaExecutionRole, ...props.accessRoles].map(role => role.roleArn); const accessPolicy = this.createAccessPolicy(props.collectionName, accessRoleArns); this.collection = new opensearch.CfnCollection(this, 'Collection', { name: props.collectionName, type: props.collectionType ?? CollectionType.VECTORSEARCH, description: 'OpenSearch Serverless collection for Agent Squad', }); // Ensure all policies are created before creating the collection. this.collection.addDependency(networkPolicy); this.collection.addDependency(encryptionPolicy); this.collection.addDependency(accessPolicy); // Create an index on the collection const indexCustomResource = this.createIndex(props.region, lambdaExecutionRole, props.indexConfiguration); indexCustomResource.node.addDependency(this.collection); } /** * Creates a custom AWS CloudFormation resource that provisions an index in an Amazon OpenSearch Service (OpenSearch) collection. * * @param lambdaExecutionRole - The AWS IAM role that the Lambda function will assume to create the index. * @returns A custom AWS CloudFormation resource that represents the index-creation */ createIndex(region:string, lambdaExecutionRole: Role, indexConfiguration?: any): CustomResource { const powerToolsTypeScriptLayer = LayerVersion.fromLayerVersionArn( this, "powertools-layer-ts", `arn:aws:lambda:${region}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:2` ); const onEventHandler = new NodejsFunction(this, 'CustomResourceHandler', { memorySize: 512, timeout: Duration.minutes(15), runtime: Runtime.NODEJS_18_X, handler: 'onEvent', layers:[powerToolsTypeScriptLayer], entry: resolve(__dirname, '../CustomResourcesLambda', `aoss-index-create.ts`), bundling: { minify: false, externalModules: [ '@aws-lambda-powertools/logger' ] }, role: lambdaExecutionRole, }); const provider = new Provider(this, 'Provider', { onEventHandler: onEventHandler, }); // Create an index in the OpenSearch collection return new CustomResource(this, 'OpenSearchIndex', { serviceToken: provider.serviceToken, properties: { indexName: this.indexName, collectionEndpoint: this.collection.attrCollectionEndpoint, /** Note: Only add indexConfiguration if present, assigning it {} * by default will create an index with {} as the configuration */ ...(indexConfiguration ? { indexConfiguration: indexConfiguration } : {}), }, }); } /** * Creates an Amazon OpenSearch Service (OpenSearch) access policy. The access policy grants the specified IAM roles * permissions to create and modify a collection and it's indices * * @param kbCollectionName - The name of the OpenSearch collection for which the access policy is being created. * @param accessRoleArns - An array of IAM Role ARNs that should be granted access to the OpenSearch collection. * * @returns A new instance of the `CfnAccessPolicy` construct representing the created OpenSearch access policy resource. */ createAccessPolicy(kbCollectionName: string, accessRoleArns: string[]): opensearch.CfnAccessPolicy { const dataAccessPolicy = new opensearch.CfnAccessPolicy(this, 'AccessPolicy', { name: generateNamesForAOSS(kbCollectionName, 'access'), type: 'data', description: `Data Access Policy for ${kbCollectionName}`, policy: 'generated', }); dataAccessPolicy.policy = JSON.stringify([ { Description: 'Full Data Access', Rules: [ { Permission: [ 'aoss:CreateCollectionItems', 'aoss:DeleteCollectionItems', 'aoss:UpdateCollectionItems', 'aoss:DescribeCollectionItems', ], ResourceType: 'collection', Resource: [`collection/${kbCollectionName}`], }, { Permission: [ 'aoss:CreateIndex', 'aoss:DeleteIndex', 'aoss:UpdateIndex', 'aoss:DescribeIndex', 'aoss:ReadDocument', 'aoss:WriteDocument', ], ResourceType: 'index', Resource: [`index/${kbCollectionName}/*`], }, ], Principal: accessRoleArns, }, ]); return dataAccessPolicy; } /** * Creates an Amazon OpenSearch Service encryption policy for a collection. The encryption policy enables * server-side encryption using an AWS-owned key for the specified OpenSearch collection. * * @param kbCollectionName - The name of the OpenSearch collection for which the encryption policy is being created. * @returns A new instance of the `CfnSecurityPolicy` construct representing the created encryption policy. */ createEncryptionPolicy(kbCollectionName: string): opensearch.CfnSecurityPolicy { return new opensearch.CfnSecurityPolicy(this, 'EncryptionPolicy', { description: 'Security policy for encryption', name: generateNamesForAOSS(kbCollectionName, 'encryption'), type: 'encryption', policy: JSON.stringify({ Rules: [ { ResourceType: 'collection', Resource: [`collection/${kbCollectionName}`], }, ], AWSOwnedKey: true, }), }); } /** * Creates an Amazon OpenSearch Service network policy for the specified collection.The network policy allows * access to the specified OpenSearch collection and its dashboards. * * @param kbCollectionName - The name of the OpenSearch collection for which the network policy is being created. * @returns A new instance of the `CfnSecurityPolicy` construct representing the created network policy. */ createNetworkPolicy(kbCollectionName: string): opensearch.CfnSecurityPolicy { return new opensearch.CfnSecurityPolicy(this, 'NetworkPolicy', { description: 'Security policy for network access', name: generateNamesForAOSS(kbCollectionName, 'network'), type: 'network', policy: JSON.stringify([ { Rules: [ { ResourceType: 'collection', Resource: [`collection/${kbCollectionName}`], }, { ResourceType: 'dashboard', Resource: [`collection/${kbCollectionName}`], }, ], AllowFromPublic: true, } ]), }); } /** * Creates an IAM Role for a Lambda function to create an index in the specified Amazon OpenSearch Service collection. * * @param collectionArn - The Amazon Resource Name (ARN) of the OpenSearch collection for which the index will be created. * @returns The IAM role that grants Lambda function permission to perform all API operations on the specified OpenSearch collection. */ createLambdaExecutionRoleForIndex(region: string, accountId: string): Role { /** * We won't be able to scope down the permission to the collection resource as * the data-access policy requires this roleArn, but the policy needs to be * created before creating the collection itself. */ const collectionArn = `arn:aws:aoss:${region}:${accountId}:collection/*`; return new Role(this, 'IndexCreationLambdaExecutionRole', { assumedBy: new ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], inlinePolicies: { AOSSAccess: new PolicyDocument({ statements: [ new PolicyStatement({ effect: Effect.ALLOW, actions: ['aoss:APIAccessAll'], resources: [collectionArn], }), ], }), }, }); } } ================================================ FILE: examples/chat-demo-app/lib/utils/utils.ts ================================================ import * as fs from "node:fs"; import * as path from "node:path"; import { writeFileSync } from 'fs'; import { resolve } from 'path'; import { v4 as uuidv4 } from 'uuid'; export abstract class Utils { static copyDirRecursive(sourceDir: string, targetDir: string): void { if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir); } const files = fs.readdirSync(sourceDir); for (const file of files) { const sourceFilePath = path.join(sourceDir, file); const targetFilePath = path.join(targetDir, file); const stats = fs.statSync(sourceFilePath); if (stats.isDirectory()) { Utils.copyDirRecursive(sourceFilePath, targetFilePath); } else { fs.copyFileSync(sourceFilePath, targetFilePath); } } } } /** * Interface to store the combination of filenames and their contents. * @key: filename * @value: contents of the file * * Usage: * const fileBuffers: FileBufferMap = { * 'file1.txt': Buffer.from('This is file 1'), * 'file2.jpg': Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), // Binary data for a JPG file * 'file3.pdf': Buffer.from('...'), // Binary data for a PDF file }; */ export interface FileBufferMap { [filename: string]: Buffer; } export function generateFileBufferMap(files: Buffer[]) { let tempBufferMap: FileBufferMap = {}; files.forEach(file => tempBufferMap[uuidv4()] = file); return tempBufferMap; } /** * Writes a set of files to a specified directory. This is used for creating a * temp directory for the contents of the assets that need to be uploaded to S3 * * @param dirPath - The path of the directory where the files will be written. * @param files - A map of file names to file buffers, representing the files to be written. */ export function writeFilesToDir(dirPath: string, files: FileBufferMap) { for (const [fileName, fileBuffer] of Object.entries(files)) { const filePath = resolve(dirPath, fileName); writeFileSync(filePath, fileBuffer); } } /** * Collection and property names follow regex: ^[a-z][a-z0-9-]{2,31}$. We will * use the first 32-suffixLength characters of the Kb to generate the name. * * @param resourceName Name of the kb/collection. This will be trimmed to fit suffix. * @param suffix Suffix to append to the kbName. * @returns string that conforms to AOSS validations (timmedName-prefix) */ export function generateNamesForAOSS(resourceName: string, suffix: string) { const MAX_ALLOWED_NAME_LENGTH = 32; const maxResourceNameLength = MAX_ALLOWED_NAME_LENGTH - suffix.length - 1; // Subtracts an additional 1 to account for the hyphen between resourceName and suffix. return `${resourceName.slice(0, maxResourceNameLength)}-${suffix}`.toLowerCase().replace(/[^a-z0-9-]/g, ''); // Replaces any characters that do not match [a-z0-9-] with an empty string. } ================================================ FILE: examples/chat-demo-app/package.json ================================================ { "name": "chat-demo-app", "version": "0.1.0", "bin": { "chat-demo-app": "bin/chat-demo-app.js" }, "scripts": { "build": "tsc", "watch": "tsc -w", "test": "jest", "cdk": "cdk", "postinstall": "cd lambda/auth && npm install && cd ../.." }, "devDependencies": { "@aws-lambda-powertools/parameters": "^2.3.0", "@types/jest": "^29.5.12", "@types/node": "^20.14.2", "aws-cdk": "2.148.1", "jest": "^29.7.0", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "typescript": "~5.4.5" }, "dependencies": { "@aws-cdk/aws-cognito-identitypool-alpha": "^2.158.0-alpha.0", "@aws-cdk/aws-lambda-python-alpha": "^2.158.0-alpha.0", "@aws-lambda-powertools/logger": "^2.3.0", "@aws-sdk/client-bedrock-agent": "^3.675.0", "@aws-sdk/client-bedrock-runtime": "^3.651.1", "@aws-sdk/core": "^3.651.1", "@opensearch-project/opensearch": "^2.12.0", "aws-cdk-lib": "^2.194.0", "aws-lambda": "^1.0.7", "constructs": "^10.0.0", "esbuild": "^0.24.0", "i": "^0.3.7", "agent-squad": "^0.0.17", "natural": "^7.1.0", "npm": "^10.8.1", "source-map-support": "^0.5.21", "stopword": "^3.0.1", "ts-retry": "^5.0.1", "xml2js": "^0.6.2" } } ================================================ FILE: examples/chat-demo-app/scripts/download.js ================================================ const https = require('https'); const fs = require('fs'); const path = require('path'); function downloadFile(url, outputPath) { const file = fs.createWriteStream(outputPath); https.get(url, (response) => { if (response.statusCode === 200) { response.pipe(file); } else { console.error(`Failed to get '${url}' (${response.statusCode})`); } file.on('finish', () => { file.close(); console.log('Download completed.'); }); }).on('error', (err) => { fs.unlink(outputPath, () => {}); // Delete the file async. (But we don't check the result) console.error(`Error downloading the file: ${err.message}`); }); } // Example usage: const url = 'https://lex-usecases-templates.s3.amazonaws.com/airlines.yaml'; const outputPath = path.join(__dirname, '../lib/airlines.yaml'); downloadFile(url, outputPath); ================================================ FILE: examples/chat-demo-app/test/chat-demo-app.ts ================================================ // import * as cdk from 'aws-cdk-lib'; // import { Template } from 'aws-cdk-lib/assertions'; // import * as ChatDemoStack from '../lib/chat-demo-stack'; // example test. To run these tests, uncomment this file along with the // example resource in lib/chat-demo-stack.ts test('SQS Queue Created', () => { // const app = new cdk.App(); // // WHEN // const stack = new ChatDemoStack.ChatDemoStack(app, 'ChatDemoStack'); // // THEN // const template = Template.fromStack(stack); // template.hasResourceProperties('AWS::SQS::Queue', { // VisibilityTimeout: 300 // }); }); ================================================ FILE: examples/chat-demo-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": [ "es2020", "dom" ], "declaration": true, "strict": true, "noImplicitAny": true, "strictNullChecks": true, "noImplicitThis": true, "alwaysStrict": true, "noUnusedLocals": false, "noUnusedParameters": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": false, "inlineSourceMap": true, "inlineSources": true, "experimentalDecorators": true, "strictPropertyInitialization": false, "typeRoots": [ "./node_modules/@types" ] }, "exclude": [ "node_modules", "cdk.out" ] } ================================================ FILE: examples/chat-demo-app/ui/.babelrc ================================================ { "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-transform-modules-commonjs"] } ================================================ FILE: examples/chat-demo-app/ui/.gitignore ================================================ # build output dist/ # generated types .astro/ # dependencies node_modules/ # logs npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # environment variables .env .env.production # macOS-specific files .DS_Store # jetbrains setting folder .idea/ ================================================ FILE: examples/chat-demo-app/ui/.vscode/extensions.json ================================================ { "recommendations": ["astro-build.astro-vscode"], "unwantedRecommendations": [] } ================================================ FILE: examples/chat-demo-app/ui/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "command": "./node_modules/.bin/astro dev", "name": "Development server", "request": "launch", "type": "node-terminal" } ] } ================================================ FILE: examples/chat-demo-app/ui/README.md ================================================ # Astro Starter Kit: Minimal ```sh npm create astro@latest -- --template minimal ``` [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal) [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json) > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! ## 🚀 Project Structure Inside of your Astro project, you'll see the following folders and files: ```text / ├── public/ ├── src/ │ └── pages/ │ └── index.astro └── package.json ``` Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. Any static assets, like images, can be placed in the `public/` directory. ## 🧞 Commands All commands are run from the root of the project, from a terminal: | Command | Action | | :------------------------ | :----------------------------------------------- | | `npm install` | Installs dependencies | | `npm run dev` | Starts local dev server at `localhost:4321` | | `npm run build` | Build your production site to `./dist/` | | `npm run preview` | Preview your build locally, before deploying | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | | `npm run astro -- --help` | Get help using the Astro CLI | ## 👀 Want to learn more? Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). ================================================ FILE: examples/chat-demo-app/ui/astro.config.mjs ================================================ import { defineConfig } from 'astro/config'; import react from "@astrojs/react"; import tailwind from "@astrojs/tailwind"; export default defineConfig({ integrations: [react(), tailwind()], vite: { ssr: { noExternal: ['@aws-amplify/ui-react'] } } }); ================================================ FILE: examples/chat-demo-app/ui/package.json ================================================ { "name": "ui", "type": "module", "version": "0.0.1", "scripts": { "dev": "astro dev", "start": "astro dev", "build": "astro check && astro build", "preview": "astro preview", "astro": "astro" }, "dependencies": { "@astrojs/check": "^0.9.3", "@astrojs/react": "^3.6.2", "@aws-amplify/ui-react": "^6.5.1", "astro": "^4.16.19", "aws-amplify": "^6.6.3", "lucide-react": "^0.446.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", "react-syntax-highlighter": "^15.5.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "typescript": "^5.6.2", "uuid": "^10.0.0" }, "devDependencies": { "@astrojs/tailwind": "^5.1.1", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@types/uuid": "^10.0.0", "tailwindcss": "^3.4.13" } } ================================================ FILE: examples/chat-demo-app/ui/src/components/ChatWindow.tsx ================================================ import React, { useState, useEffect, useRef } from 'react'; import { Send, Code2, BookOpen, RefreshCw } from 'lucide-react'; import { ChatApiClient } from '../utils/ApiClient'; import { v4 as uuidv4 } from 'uuid'; import { Authenticator } from '@aws-amplify/ui-react'; import { signOut } from 'aws-amplify/auth'; import '@aws-amplify/ui-react/styles.css'; import { configureAmplify } from '../utils/amplifyConfig'; import { replaceTextEmotesWithEmojis } from './emojiHelper'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import hljs from 'highlight.js'; import 'highlight.js/styles/github.css'; import LoadingScreen from '../components/loadingScreen'; const waitMessages = [ "Hang tight! Great things take time!", "Almost there... Grabbing the answers!", "Good things come to those who wait!", "Patience is a virtue, right?", "We’re brewing up something special!", "Just a second! AI is thinking hard!", ]; const getRandomWaitMessage = () => { return waitMessages[Math.floor(Math.random() * waitMessages.length)]; }; const MarkdownRenderer: React.FC<{ content: string }> = ({ content }) => { useEffect(() => { hljs.highlightAll(); }, [content]); return ( {children} ) : ( {children} ); }, p: ({ node, ...props }) =>

, a: ({ node, ...props }) => , h1: ({ node, ...props }) =>

, h2: ({ node, ...props }) =>

, h3: ({ node, ...props }) =>

, ul: ({ node, ...props }) =>
    , ol: ({ node, ...props }) =>
      , li: ({ node, ...props }) =>
    1. , blockquote: ({ node, ...props }) =>
      , }} className="markdown-content text-slate-900" > {content} ); }; const ChatWindow: React.FC = () => { const [isAuthenticated, setIsAuthenticated] = useState(null); const [messages, setMessages] = useState>([]); const [inputMessage, setInputMessage] = useState(''); const [running, setRunning] = useState(false); const messagesEndRef = useRef(null); const inputRef = useRef(null); const [client, setClient] = useState | null>(null); const [responseReceived, setResponseReceived] = useState(false); const createAuthenticatedClient = async () => { return new ChatApiClient(); }; useEffect(() => { initializeSessionId(); }, []); const initializeSessionId = () => { let storedSessionId = localStorage.getItem('sessionId'); if (!storedSessionId) { storedSessionId = uuidv4(); localStorage.setItem('sessionId', storedSessionId); } }; const resetSessionId = () => { const newSessionId = uuidv4(); localStorage.setItem('sessionId', newSessionId); setMessages([]); }; const initializeClient = async (): Promise => { try { await configureAmplify(); const newClient = await createAuthenticatedClient(); setIsAuthenticated(true); setClient(newClient); return newClient; } catch (error) { console.error('Error initializing client:', error); setIsAuthenticated(false); return null; } }; useEffect(() => { initializeClient(); }, []); const renderMessageContent = (content: string) => { const processedContent = replaceTextEmotesWithEmojis(content); return ; }; useEffect(() => { let session_id = localStorage.getItem('sessionId'); if (session_id == null) { session_id = uuidv4(); localStorage.setItem('sessionId', session_id); } }, []); useEffect(() => { if (responseReceived && inputRef.current) { inputRef.current.focus(); setResponseReceived(false); // Reset for next response } }, [responseReceived]); useEffect(() => { scrollToBottom(); }, [messages]); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (inputMessage.trim() === '') return; let currentClient = client; if (!currentClient) { setRunning(true); currentClient = await initializeClient(); setRunning(false); if (!currentClient) { setMessages(prevMessages => [ ...prevMessages, { content: "Failed to initialize the chat client. Please try again or refresh the page.", sender: 'System', timestamp: new Date().toISOString(), isWaiting: false } ]); return; } } const newMessage = { content: inputMessage, sender: 'You', timestamp: new Date().toISOString() }; setMessages(prevMessages => [...prevMessages, newMessage]); setInputMessage(''); setRunning(true); setMessages(prevMessages => [ ...prevMessages, { content: '', sender: '', timestamp: new Date().toISOString(), isWaiting: true } ]); try { const response = await client.query('chat/query', inputMessage); if (response.body) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let accumulatedContent = ""; let agentName = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); //console.log("chunk="+chunk) const lines = chunk.split('\n'); for (const line of lines) { try { const parsedLine = JSON.parse(line); switch (parsedLine.type) { case 'metadata': agentName = parsedLine.data.metadata.agentName; break; case 'chunk': case 'complete': accumulatedContent += parsedLine.data; break; case 'error': console.error('Error:', parsedLine.data); accumulatedContent += `Error: ${parsedLine.data}\n`; break; } setMessages(prevMessages => [ ...prevMessages.slice(0, -1), { content: accumulatedContent, sender: agentName, timestamp: new Date().toISOString(), isWaiting: false } ]); } catch (error) { console.error('Error parsing JSON:', error); } } } } } catch (error) { console.error('Error in API call:', error); setMessages(prevMessages => [ ...prevMessages.slice(0, -1), { content: `Error: ${(error as Error).message}`, sender: 'System', timestamp: new Date().toISOString(), isWaiting: false } ]); } finally { setResponseReceived(true); setRunning(false); } }; const handleSignOut = async () => { try { await signOut(); setIsAuthenticated(false); } catch (error) { console.error('Error signing out: ', error); } }; if (isAuthenticated === null) { return ; } return (
      {({ signOut: _signOut, user: _user }) => (

      Agent Squad Demo

      Experience the power of intelligent routing and context management across multiple AI agents.

      Type "hello" or "bonjour" to see the available agents, or ask questions like "How do I use agents?", "How can I use the framework to create a custom agent?", "What are the steps to customize an agent?"

      )}
      ); }; export default ChatWindow; ================================================ FILE: examples/chat-demo-app/ui/src/components/emojiHelper.ts ================================================ const emojiMap: Record = { // Smiles and positive emotions ':)': '😊', ':-)': '😊', ':D': '😄', ':-D': '😄', 'XD': '🤣', ';)': '😉', ';-)': '😉', ':>': '😃', ':->': '😃', // Negative emotions ':(': '😢', ':-(': '😢', ':/': '😕', ':-/': '😕', ':@': '😠', ':-@': '😠', // Surprise and shock ':o': '😮', ':-o': '😮', ':O': '😱', ':-O': '😱', // Other expressions ':p': '😛', ':-p': '😛', ':P': '😛', ':-P': '😛', ':|': '😐', ':-|': '😐', ':3': '😊', // Additional emotes '<3': '❤️', '^_^': '😊', '-_-': '😑', 'o_o': '😳', 'O_O': '😳', 'T_T': '😭', '¬_¬': '😒', }; export function replaceTextEmotesWithEmojis(text: string): string { const emoteRegex = /(?<=\s|^)[:;XD@OP3<>^T¬\-\/_o]+(?=\s|$)|(?<=\s|^)[()]+(?=\s|$)/g; return text.replace(emoteRegex, (match) => { return emojiMap[match] || match; }); } ================================================ FILE: examples/chat-demo-app/ui/src/components/loadingScreen.tsx ================================================ import { Loader2 } from 'lucide-react'; const LoadingScreen = ({ text = 'Loading...' }) => { return (

      {text}

      ); }; export default LoadingScreen; ================================================ FILE: examples/chat-demo-app/ui/src/pages/index.astro ================================================ --- import ChatWindow from '../components/ChatWindow'; --- Agent Squad Demo ================================================ FILE: examples/chat-demo-app/ui/src/utils/ApiClient.ts ================================================ import { getAwsExports } from './amplifyConfig'; import { fetchAuthSession } from "aws-amplify/auth"; class ApiClientBase { async getHeaders(): Promise> { return { Authorization: `Bearer ${await this.getAccessToken()}`, "Content-Type": "application/json", }; } async getAccessToken(): Promise { const session = await fetchAuthSession(); return session.tokens?.accessToken?.toString(); } async callStreamingAPI(resource: string, method: string = "GET", body: any = null): Promise { const awsExports = await getAwsExports(); if (!awsExports) { throw new Error("AWS exports not available"); } const url = `${awsExports.domainName}/${resource}`; try { const headers = await this.getHeaders(); const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : null, }); if (!response.ok) { const errorResponse = await response.json(); throw new Error(errorResponse.message || "Network response was not ok"); } return response; } catch (error) { throw error; } } } export class ChatApiClient extends ApiClientBase { async query(path: string, message: string): Promise { const body = { 'query': message, 'sessionId': localStorage.getItem('sessionId'), 'userId': localStorage.getItem('sessionId') }; return this.callStreamingAPI(path, "POST", body); } } ================================================ FILE: examples/chat-demo-app/ui/src/utils/amplifyConfig.ts ================================================ import { Amplify, ResourcesConfig } from 'aws-amplify'; import { fetchAuthSession } from 'aws-amplify/auth'; interface ExtendedResourcesConfig extends ResourcesConfig { region: string; domainName: string; } let awsExports: ExtendedResourcesConfig; export async function configureAmplify(): Promise { if (!awsExports) { try { const awsExportsUrl = new URL('/aws-exports.json', window.location.href).toString(); console.log("Fetching from:", awsExportsUrl); const response = await fetch(awsExportsUrl); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } awsExports = await response.json(); console.log("Fetched AWS exports:", awsExports); } catch (error) { console.error("Failed to fetch aws-exports.json:", error); throw error; } } if (!awsExports) { throw new Error("AWS exports configuration is not available"); } Amplify.configure(awsExports); } export async function getAuthToken(): Promise { try { const session = await fetchAuthSession(); return session.tokens?.idToken?.toString(); } catch (error) { console.error("Error getting auth token:", error); throw error; } } export async function getAwsExports(): Promise { if (!awsExports) { await configureAmplify(); } return awsExports; } ================================================ FILE: examples/chat-demo-app/ui/tailwind.config.cjs ================================================ /** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], theme: { extend: {}, }, plugins: [], } ================================================ FILE: examples/chat-demo-app/ui/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react", "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] } }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.astro"], "exclude": ["node_modules"] } ================================================ FILE: examples/ecommerce-support-simulator/.gitignore ================================================ !jest.config.js *.d.ts node_modules # CDK asset staging directory .cdk.staging cdk.out ================================================ FILE: examples/ecommerce-support-simulator/.npmignore ================================================ *.ts !*.d.ts # CDK asset staging directory .cdk.staging cdk.out ================================================ FILE: examples/ecommerce-support-simulator/README.md ================================================ # AI-Powered E-commerce Support Simulator A demonstration of how AI agents and human support can work together in an e-commerce customer service environment. This project showcases intelligent query routing, multi-agent collaboration, and seamless human integration for complex support scenarios. ## 🎯 Key Features - Multi-agent AI orchestration with specialized models - Real-time and asynchronous communication modes - Seamless integration with human support workflow - Tool-augmented AI interactions - Production-ready AWS architecture - Mock data for realistic scenarios ## 💻 Interface & Communication Modes The system provides two distinct interaction modes to accommodate different support scenarios and user preferences: ### Real-Time Chat Interface ![Chat Mode](./img/chat_mode.png) The chat interface provides immediate, conversational support: - Instant messaging-style communication - Live message streaming - Real-time agent responses - Automatic message routing - Separate chat windows for customer and support perspectives ### Email-Style Communication ![Email Mode](./img/email_mode.png) The email interface supports structured, asynchronous communication: - Email composition interfaces for both customer and support - Pre-defined templates for common scenarios - Response viewing areas for both parties - Asynchronous message handling - Template support for standardized responses ### Key Features Across Both Modes Both interfaces demonstrate: 1. **AI Capabilities**: Natural language understanding, context retention, appropriate tool usage 2. **Human Integration**: Seamless handoffs, verification workflows, complex case handling 3. **Tool Usage**: Order lookup, shipment tracking, return processing 4. **System Intelligence**: Query classification, routing decisions, escalation handling ## 🏗️ System Architecture ![E-commerce Email Simulator System](./img/ai_e-commerce_support_system.jpg) The system employs multiple specialized AI agents, each designed for specific tasks: #### 1. Order Management Agent - **Implementation**: `BedrockLLMAgent` - **Model**: Anthropic Claude 3 Sonnet (`anthropic.claude-3-sonnet-20240229-v1:0`) - **Purpose**: Handles all order-related inquiries and management tasks - **Tools**: - `orderlookup`: - Retrieves order details from database - Input: `orderId` (string) - Returns: Complete order information including status, items, and pricing - `shipmenttracker`: - Provides real-time shipping status updates - Input: `orderId` (string) - Returns: Current shipment status, location, and estimated delivery - `returnprocessor`: - Manages return request workflows - Input: `orderId` (string) - Returns: Return authorization and instructions #### 2. Product Information Agent - **Implementation**: `BedrockLLMAgent` - **Model**: Anthropic Claude 3 Haiku (`anthropic.claude-3-haiku-20240307-v1:0`) - **Purpose**: Provides comprehensive product information and specifications - **Integration**: - Connected to `AmazonKnowledgeBasesRetriever` for product data - Knowledge Base ID configuration required - **Key Features**: - Real-time product database access - Specification lookups - Availability checking - Compatibility information #### 3. Human Agent - **Implementation**: Custom `HumanAgent` class extending base `Agent` - **Purpose**: Handles complex cases requiring human intervention - **Integration**: - AWS SQS integration for message queuing - Requires queue URL configuration - **Features**: - Asynchronous message handling - Bi-directional communication support - Customer and support message routing ### AWS Infrastructure ![Infrastructure](./img/ai-powered_e-commerce_support_simulator.png) The system is built on AWS with the following key components: - **Frontend**: React-based web application served via CloudFront - **API Layer**: AppSync GraphQL API - **Message Routing**: SQS queues for reliable message delivery - **Processing**: Lambda functions for message handling - **Storage**: - DynamoDB for conversation history - S3 for static assets - **Authentication**: Cognito user pools and identity pools - **Monitoring**: Built-in logging and debugging capabilities ### Mock Data The system includes a comprehensive `mock_data.json` file that provides sample order information ## 📋 Deployment Before deploying the demo web app, ensure you have the following: 1. An AWS account with appropriate permissions 2. AWS CLI installed and configured with your credentials 3. Node.js and npm installed on your local machine 4. AWS CDK CLI installed (`npm install -g aws-cdk`) ## 🚀 Deployment Steps Follow these steps to deploy the demo chat web application: 1. **Clone the Repository**: ```bash git clone https://github.com/awslabs/agent-squad.git cd agent-squad/examples/ecommerce-support-simulator ``` 2. **Install Dependencies**: ```bash npm install ``` 3. **Bootstrap AWS CDK**: ```bash cdk bootstrap ``` 4. **Deploy the Application**: ```bash cdk deploy ``` 5. **Create a user in Amazon Cognito user pool**: ```bash aws cognito-idp admin-create-user \ --user-pool-id your-region_xxxxxxx \ --username your@email.com \ --user-attributes Name=email,Value=your@email.com \ --temporary-password "MyChallengingPassword" \ --message-action SUPPRESS \ --region your-region ``` ## 🌐 Accessing the Demo Web App Once deployment is complete: 1. Open the URL provided in the CDK outputs in your web browser 2. Log in with the created credentials ## 🧹 Cleaning Up To avoid incurring unnecessary AWS charges: ```bash cdk destroy ``` ## 🔧 Troubleshooting If you encounter issues during deployment: 1. Ensure your AWS credentials are correctly configured 2. Check that you have the necessary permissions in your AWS account 3. Verify that all dependencies are correctly installed 4. Review the AWS CloudFormation console for detailed error messages if the deployment fails ## ⚠️ Disclaimer This demo application is intended solely for demonstration purposes. It is not designed for handling, storing, or processing any kind of Personally Identifiable Information (PII) or personal data. Users are strongly advised not to enter, upload, or use any PII or personal data within this application. Any use of PII or personal data is at the user's own risk and the developers of this application shall not be held responsible for any data breaches, misuse, or any other related issues. Please ensure that all data used in this demo is non-sensitive and anonymized. For production usage, it is crucial to implement proper security measures to protect PII and personal data. This includes obtaining proper permissions from users, utilizing encryption for data both in transit and at rest, and adhering to industry standards and regulations to maximize security. Failure to do so may result in data breaches and other serious security issues. ================================================ FILE: examples/ecommerce-support-simulator/bin/ai-ecommerce-support-simulator.ts ================================================ #!/usr/bin/env node import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; import { AiEcommerceSupportSimulatorStack } from '../lib/ai-ecommerce-support-simulator-stack'; const app = new cdk.App(); new AiEcommerceSupportSimulatorStack(app, 'AiEcommerceSupportSimulatorStack', { /* If you don't specify 'env', this stack will be environment-agnostic. * Account/Region-dependent features and context lookups will not work, * but a single synthesized template can be deployed anywhere. */ /* Uncomment the next line to specialize this stack for the AWS Account * and Region that are implied by the current CLI configuration. */ // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, /* Uncomment the next line if you know exactly what Account and Region you * want to deploy the stack to. */ // env: { account: '123456789012', region: 'us-east-1' }, /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ }); ================================================ FILE: examples/ecommerce-support-simulator/cdk.json ================================================ { "app": "npx ts-node --prefer-ts-exts bin/ai-ecommerce-support-simulator.ts", "watch": { "include": [ "**" ], "exclude": [ "README.md", "cdk*.json", "**/*.d.ts", "**/*.js", "tsconfig.json", "package*.json", "yarn.lock", "node_modules", "test" ] }, "context": { "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, "@aws-cdk/core:target-partitions": [ "aws", "aws-cn" ], "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, "@aws-cdk/aws-iam:minimizePolicies": true, "@aws-cdk/core:validateSnapshotRemovalPolicy": true, "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, "@aws-cdk/core:enablePartitionLiterals": true, "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, "@aws-cdk/aws-route53-patters:useCertificate": true, "@aws-cdk/customresources:installLatestAwsSdkDefault": false, "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, "@aws-cdk/aws-redshift:columnId": true, "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, "@aws-cdk/aws-kms:aliasNameRef": true, "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, "@aws-cdk/aws-efs:denyAnonymousAccess": true, "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, "@aws-cdk/aws-eks:nodegroupNameAttribute": true, "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false } } ================================================ FILE: examples/ecommerce-support-simulator/graphql/Query.sendMessage.js ================================================ import { util } from '@aws-appsync/utils' export function request(ctx) { const { accountId, customerQueueUrl, customerQueueName, supportQueueUrl, supportQueueName } = ctx.prev.result; let body = 'Action=SendMessage&Version=2012-11-05'; console.log("ctx.args=" + JSON.stringify(ctx.args)); const source = ctx.args.source || 'unknown'; let queueUrl, queueName; if (source === "customer") { queueUrl = customerQueueUrl; queueName = customerQueueName; } else if (source === "support") { queueUrl = supportQueueUrl; queueName = supportQueueName; } else { // Default to customer queue if source is unknown queueUrl = customerQueueUrl; queueName = customerQueueName; } console.log(`Sending message to ${queueName} queue (${queueUrl})`); const messageBody = util.urlEncode(JSON.stringify(ctx.args)); const queueUrlEncoded = util.urlEncode(queueUrl); body = `${body}&MessageBody=${messageBody}&QueueUrl=${queueUrlEncoded}`; return { version: '2018-05-29', method: 'POST', resourcePath: `/${accountId}/${queueName}`, params: { body, headers: { 'content-type': 'application/x-www-form-urlencoded', }, }, }; } export function response(ctx) { if (ctx.result.statusCode === 200) { //if response is 200 // Because the response is of type XML, we are going to convert // the result body as a map and only get the User object. return util.xml.toMap(ctx.result.body).SendMessageResponse.SendMessageResult } else { //if response is not 200, append the response to error block. return util.appendError(ctx.result.body, ctx.result.statusCode) } } ================================================ FILE: examples/ecommerce-support-simulator/graphql/schema.graphql ================================================ # Directives for authentication directive @aws_iam on FIELD_DEFINITION | OBJECT directive @aws_cognito_user_pools(cognito_groups: [String!]) on FIELD_DEFINITION | OBJECT type Query @aws_iam @aws_cognito_user_pools { # Placeholder query _empty: String sendMessage(source: String, message: String, sessionId: String): Response } type Mutation @aws_iam { sendResponse(destination: String!, message: String!): Message! } type Subscription @aws_cognito_user_pools { onResponseReceived: Message @aws_subscribe(mutations: ["sendResponse"]) } type Message @aws_iam { destination: String! message: String! } type Response { MessageId: String! MD5OfMessageBody: String! MD5OfMessageAttributes: String MD5OfMessageSystemAttributes: String SequenceNumber: String } schema { query: Query mutation: Mutation subscription: Subscription } ================================================ FILE: examples/ecommerce-support-simulator/graphql/sendResponse.js ================================================ export function request(ctx) { return { payload: { destination: ctx.arguments.destination, message: ctx.arguments.message } }; } export function response(ctx) { if (!ctx.result) { util.error('No result returned from the previous step'); } const destination = ctx.result.destination || ctx.prev.result.destination; const message = ctx.result.message || ctx.prev.result.message; if (!destination || typeof destination !== 'string') { util.error('Invalid or missing destination'); } if (!message || typeof message !== 'string') { util.error('Invalid or missing message'); } return { destination: destination, message: message }; } ================================================ FILE: examples/ecommerce-support-simulator/graphql/sendResponsePipeline.js ================================================ export function request(ctx) { return {}; } export function response(ctx) { if (ctx.error) { util.error(ctx.error.message, ctx.error.type); } if (!ctx.result.message || typeof ctx.result.message !== 'string') { util.error('Invalid response: Message is missing or not a string'); } if (!ctx.result.destination || typeof ctx.result.destination !== 'string') { util.error('Invalid response: Destination is missing or not a string'); } return { destination: ctx.result.destination, message: ctx.result.message }; } ================================================ FILE: examples/ecommerce-support-simulator/jest.config.js ================================================ module.exports = { testEnvironment: 'node', roots: ['/test'], testMatch: ['**/*.test.ts'], transform: { '^.+\\.tsx?$': 'ts-jest' } }; ================================================ FILE: examples/ecommerce-support-simulator/lambda/customerMessage/agents.ts ================================================ import { AgentSquad, BedrockLLMAgent, BedrockLLMAgentOptions, AmazonBedrockAgent, AmazonBedrockAgentOptions, AgentResponse, AmazonKnowledgeBasesRetriever, Agent, ChainAgent, ChainAgentOptions, ConversationMessage, ParticipantRole, AnthropicClassifier, } from "agent-squad"; import { BedrockAgentRuntimeClient } from "@aws-sdk/client-bedrock-agent-runtime"; import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; import * as fs from 'fs'; // Load configuration from JSON file const config = JSON.parse(fs.readFileSync('mock_data.json', 'utf-8')); // Mock databases const orderDb: Record = config.orders; const shipmentDb: Record = config.shipments; // Mock functions for tools const orderLookup = (orderId: string): any => { console.log(`OrderLookup - Order ID: ${orderId}`); const result = orderDb[orderId] || "not found"; console.log(`OrderLookup - Result: ${JSON.stringify(result)}`); return result; }; const shipmentTracker = (orderId: string): any => { console.log(`ShipmentTracker - Order ID: ${orderId}`); const result = shipmentDb[orderId] || "not found"; console.log(`ShipmentTracker - Result: ${JSON.stringify(result)}`); return result; }; const returnProcessor = (orderId: string): string => { console.log(`ReturnProcessor - Order ID: ${orderId}`); const result = `Return initiated for order ${orderId}`; console.log(`ReturnProcessor - Result: ${result}`); return result; }; const orderManagementToolConfig = [ { toolSpec: { name: "orderlookup", description: "Retrieve order details from the database", inputSchema: { json: { type: "object", properties: { orderId: { type: "string", description: "The order ID to look up" }, }, required: ["orderId"], }, }, }, }, { toolSpec: { name: "shipmenttracker", description: "Get real-time shipping information", inputSchema: { json: { type: "object", properties: { orderId: { type: "string", description: "The order ID to track" }, }, required: ["orderId"], }, }, }, }, { toolSpec: { name: "returnprocessor", description: "Initiate and manage return requests", inputSchema: { json: { type: "object", properties: { orderId: { type: "string", description: "The order ID for the return", }, }, required: ["orderId"], }, }, }, }, ]; async function orderManagementToolHandler( response: ConversationMessage, conversation: ConversationMessage[] ) { console.log('Starting orderManagementToolHandler'); console.log('Response:', JSON.stringify(response, null, 2)); console.log('Conversation:', JSON.stringify(conversation, null, 2)); const responseContentBlocks = response.content as any[]; let toolResults: any = []; console.log('Response content blocks:', JSON.stringify(responseContentBlocks, null, 2)); if (!responseContentBlocks) { console.error('No content blocks in response'); throw new Error("No content blocks in response"); } for (const contentBlock of responseContentBlocks) { console.log('Processing content block:', JSON.stringify(contentBlock, null, 2)); if ("toolUse" in contentBlock) { const toolUseBlock = contentBlock.toolUse; const toolUseName = toolUseBlock.name; console.log('Tool use detected:', toolUseName); let result; switch (toolUseName) { case "orderlookup": console.log('Executing OrderLookup with orderId:', toolUseBlock.input.orderId); result = orderLookup(toolUseBlock.input.orderId); break; case "shipmenttracker": console.log('Executing ShipmentTracker with orderId:', toolUseBlock.input.orderId); result = shipmentTracker(toolUseBlock.input.orderId); break; case "returnprocessor": console.log('Executing ReturnProcessor with orderId:', toolUseBlock.input.orderId); result = returnProcessor(toolUseBlock.input.orderId); break; } console.log('Tool execution result:', JSON.stringify(result, null, 2)); if (result) { toolResults.push({ toolResult: { toolUseId: toolUseBlock.toolUseId, content: [{ json: { result } }], }, }); } } } console.log('Final tool results:', JSON.stringify(toolResults, null, 2)); const message: ConversationMessage = { role: ParticipantRole.USER, content: toolResults, }; console.log('New message to be added to conversation:', JSON.stringify(message, null, 2)); console.log('Updated conversation:', JSON.stringify(conversation, null, 2)); console.log('orderManagementToolHandler completed'); return message; } export const orderManagementAgent = new BedrockLLMAgent({ name: "Order Management Agent", streaming: false, description: "Handles order-related inquiries including order status, shipment tracking, returns, and refunds. Uses order database and shipment tracking tools.", modelId: "anthropic.claude-3-sonnet-20240229-v1:0", toolConfig: { useToolHandler: orderManagementToolHandler, tool: orderManagementToolConfig, toolMaxRecursions: 5, }, saveChat: false, } as BedrockLLMAgentOptions); const productInfoRetriever = new AmazonKnowledgeBasesRetriever( new BedrockAgentRuntimeClient({}), { knowledgeBaseId: "your-product-kb-id" } ); export const customerServiceAgent = new AmazonBedrockAgent({ name: "Customer Service Agent", streaming: false, description: "Handles general customer inquiries, account-related questions, and non-technical support requests. Uses comprehensive customer service knowledge base.", agentId: "your-agent-id", agentAliasId: "your-agent-alias-id", saveChat: false, } as AmazonBedrockAgentOptions); export const productInfoAgent = new BedrockLLMAgent({ name: "Product Information Agent", streaming: false, description: "Provides detailed product information, answers questions about specifications, compatibility, and availability.", modelId: "anthropic.claude-3-haiku-20240307-v1:0", retriever: productInfoRetriever, saveChat: false, } as BedrockLLMAgentOptions); export interface HumanAgentOptions { name: string; description: string; saveChat: boolean; queueUrl: string; } export class HumanAgent extends Agent { private sqsClient: SQSClient; private queueUrl: string; constructor(options: HumanAgentOptions) { super(options); console.log("Human agent init"); this.sqsClient = new SQSClient({}); this.queueUrl = options.queueUrl; if (!this.queueUrl) { throw new Error("Queue URL is not provided"); } } async processRequest( inputText: string, userId: string, sessionId: string, chatHistory: ConversationMessage[] ): Promise { console.log(`Human agent received request: ${inputText}`); const humanResponse = `Your request "${inputText}" has been received and will be processed by our customer service team. We'll get back to you as soon as possible.`; // Send messages to SQS //await this.sendSQSMessage("customer", humanResponse); await this.sendSQSMessage("support", inputText); return { role: ParticipantRole.ASSISTANT, content: [{ text: humanResponse }], }; } private async sendSQSMessage(destination: string, message: string) { const command = new SendMessageCommand({ QueueUrl: this.queueUrl, MessageBody: JSON.stringify({ destination, message: message, }), }); try { const response = await this.sqsClient.send(command); console.log(`Message sent to ${destination}:`, response.MessageId); } catch (error) { console.error(`Error sending message to ${destination}:`, error); } } } export const aiWithHumanVerificationAgent = new ChainAgent({ name: "AI with Human Verification Agent", description: "Handles high-priority or sensitive customer inquiries by generating AI responses and having them verified by a human before sending.", agents: [ customerServiceAgent, new HumanAgent({ name: "Human Verifier", description: "Verifies and potentially modifies AI-generated responses", saveChat: false, queueUrl: process.env.QUEUE_URL || "", }), ], saveChat: false, } as ChainAgentOptions); ================================================ FILE: examples/ecommerce-support-simulator/lambda/customerMessage/index.ts ================================================ import { SQSEvent, SQSHandler } from 'aws-lambda'; import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; import { AgentSquad, DynamoDbChatStorage, } from 'agent-squad'; import { HumanAgent, orderManagementAgent, productInfoAgent } from './agents'; import { SQSLogger } from './sqsLogger'; const sqs = new SQSClient({}); let orchestrator: AgentSquad | null = null; if (!process.env.QUEUE_URL) { throw new Error('QUEUE_URL not set'); } const storage = new DynamoDbChatStorage( process.env.HISTORY_TABLE_NAME!, process.env.AWS_REGION!, process.env.HISTORY_TABLE_TTL_KEY_NAME, Number(process.env.HISTORY_TABLE_TTL_DURATION), ); // Async initialization function for the orchestrator const initializeOrchestrator = async (): Promise => { if (!orchestrator) { const sqsLogger = new SQSLogger(process.env.QUEUE_URL!, "log"); orchestrator = new AgentSquad({ storage: storage, config: { LOG_AGENT_CHAT: true, LOG_CLASSIFIER_CHAT: true, LOG_CLASSIFIER_RAW_OUTPUT: true, LOG_CLASSIFIER_OUTPUT: true, LOG_EXECUTION_TIMES: true, MAX_MESSAGE_PAIRS_PER_AGENT: 10, USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED: false, }, logger: sqsLogger, }); const humanAgent = new HumanAgent({ name: "Human Agent", description: "Handles complex inquiries, complaints, or sensitive issues requiring human expertise.", saveChat: false, queueUrl: process.env.QUEUE_URL! }); // Add additional agents orchestrator.addAgent(orderManagementAgent); orchestrator.addAgent(productInfoAgent); orchestrator.addAgent(humanAgent); } return orchestrator; }; // Orchestrator initialization promise to be awaited before the handler is called let orchestratorPromise: Promise | null = null; export const handler: SQSHandler = async (event: SQSEvent) => { console.log('Received event:', JSON.stringify(event, null, 2)); if (!orchestratorPromise) { orchestratorPromise = initializeOrchestrator(); // Initialize if not done already } const orchestrator = await orchestratorPromise; // Get the queue URL from environment variables const queueUrl = process.env.QUEUE_URL; if (!queueUrl) { throw new Error("QUEUE_URL environment variable is not set"); } // Process each record in the SQS event for (const record of event.Records) { try { // Parse the message body const body = JSON.parse(record.body); const sessionId = body.sessionId; console.log(`Calling the orchestrator sessionId:${sessionId}, message: ${body.message}`) const orchestratorResponse = await orchestrator.routeRequest( body.message, sessionId, sessionId ); console.log("orchestratorResponse="+JSON.stringify(orchestratorResponse)); // Create the message object using data from the SQS event const message = { destination: "customer", message: orchestratorResponse.output, }; // Send the message to another SQS queue const command = new SendMessageCommand({ QueueUrl: queueUrl, MessageBody: JSON.stringify(message), }); const response = await sqs.send(command); console.log('Message sent to SQS:', response.MessageId); } catch (error) { console.error('Error processing record:', error); // You might want to handle this error differently depending on your requirements } } return { message: `Processed ${event.Records.length} messages` }; }; ================================================ FILE: examples/ecommerce-support-simulator/lambda/customerMessage/sqsLogger.ts ================================================ import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; interface LogMessage { destination: string; message: string; } export class SQSLogger { private sqsClient: SQSClient; private queueUrl: string; private destination: string; constructor(queueUrl: string, destination: string, region: string = "us-east-1") { this.sqsClient = new SQSClient({ region }); this.queueUrl = queueUrl; this.destination = destination; } private async sendToSQS(message: string): Promise { const logMessage: LogMessage = { destination: this.destination, message: message, }; const params = { QueueUrl: this.queueUrl, MessageBody: JSON.stringify(logMessage), }; try { await this.sqsClient.send(new SendMessageCommand(params)); } catch (error) { console.error("Error sending message to SQS:", error); } } private formatMessage(...args: any[]): string { return args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg) ).join(' '); } log(...args: any[]): void { const message = this.formatMessage(...args); this.sendToSQS(message); } info(...args: any[]): void { this.log('[INFO]', ...args); } warn(...args: any[]): void { this.log('[WARN]', ...args); } error(...args: any[]): void { this.log('[ERROR]', ...args); } debug(...args: any[]): void { this.log('[DEBUG]', ...args); } } ================================================ FILE: examples/ecommerce-support-simulator/lambda/sendResponse/index.ts ================================================ import { SQSEvent, SQSHandler } from 'aws-lambda'; import axios from 'axios'; import { SignatureV4 } from "@aws-sdk/signature-v4"; import { Sha256 } from "@aws-crypto/sha256-js"; import { defaultProvider } from "@aws-sdk/credential-provider-node"; import { HttpRequest } from "@aws-sdk/protocol-http"; const APPSYNC_API_URL = process.env.APPSYNC_API_URL; const REGION = process.env.REGION; if (!APPSYNC_API_URL) { throw new Error("APPSYNC_API_URL environment variable is not set"); } if (!REGION) { throw new Error("AWS_REGION environment variable is not set"); } const sendResponseMutation = ` mutation SendResponse($destination: String!, $message: String!) { sendResponse(destination: $destination, message: $message) { destination message } } `; const signer = new SignatureV4({ credentials: defaultProvider(), region: REGION, service: 'appsync', sha256: Sha256 }); async function sendSignedRequest(variables: { destination: string; message: string }) { const endpoint = new URL(APPSYNC_API_URL!); const request = new HttpRequest({ method: 'POST', headers: { 'Content-Type': 'application/json', host: endpoint.hostname }, hostname: endpoint.hostname, path: endpoint.pathname, body: JSON.stringify({ query: sendResponseMutation, variables: variables }) }); console.log('Request before signing:', JSON.stringify(request, null, 2)); const signedRequest = await signer.sign(request); console.log('Signed request:', JSON.stringify(signedRequest, null, 2)); try { const response = await axios({ method: signedRequest.method, url: APPSYNC_API_URL, headers: signedRequest.headers, data: signedRequest.body }); console.log('AppSync response:', JSON.stringify(response.data, null, 2)); return response; } catch (error) { console.error('Error details:', error.response?.data); throw error; } } export const handler: SQSHandler = async (event: SQSEvent) => { console.log('Received event:', JSON.stringify(event, null, 2)); for (const record of event.Records) { try { const body = JSON.parse(record.body); console.log('Processing message:', body); const variables = { destination: body.destination, message: body.message }; const response = await sendSignedRequest(variables); console.log('AppSync response:', JSON.stringify(response.data, null, 2)); if (response.data.errors) { console.error(`GraphQL errors: ${JSON.stringify(response.data.errors)}`); // Log the error but don't throw, allowing the message to be deleted from SQS } } catch (error) { console.error('Error processing SQS message:', error); // Log the error but don't throw, allowing the message to be deleted from SQS } } console.log('All messages processed'); return { statusCode: 200, body: JSON.stringify({ message: 'Messages processed' }) }; }; ================================================ FILE: examples/ecommerce-support-simulator/lambda/supportMessage/index.ts ================================================ import { SQSEvent, SQSHandler } from 'aws-lambda'; import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; const sqs = new SQSClient({}); export const handler: SQSHandler = async (event: SQSEvent) => { console.log('Received event:', JSON.stringify(event, null, 2)); // Get the queue URL from environment variables const queueUrl = process.env.QUEUE_URL; if (!queueUrl) { throw new Error("QUEUE_URL environment variable is not set"); } // Process each record in the SQS event for (const record of event.Records) { try { // Parse the message body const body = JSON.parse(record.body); // Create the message object using data from the SQS event const message = { destination: "customer", // This lambda always sends to 'customer' message: body.message, }; // Send the message to another SQS queue const command = new SendMessageCommand({ QueueUrl: queueUrl, MessageBody: JSON.stringify(message), }); const response = await sqs.send(command); console.log('Message sent to SQS:', response.MessageId); } catch (error) { console.error('Error processing record:', error); // You might want to handle this error differently depending on your requirements } } return { message: `Processed ${event.Records.length} messages` }; }; ================================================ FILE: examples/ecommerce-support-simulator/lib/ai-ecommerce-support-simulator-stack.ts ================================================ import path from "path"; import { aws_lambda as lambda, aws_sqs as sqs, aws_lambda_nodejs as nodejs, aws_iam as iam, aws_appsync as appsync, aws_dynamodb as dynamodb, aws_s3 as s3, aws_cloudfront_origins as origins, aws_cloudfront as cloudfront, aws_s3_deployment as s3deploy, aws_cognito as cognito } from "aws-cdk-lib"; import { ExecSyncOptionsWithBufferEncoding, execSync, } from "node:child_process"; import * as cognitoIdentityPool from "@aws-cdk/aws-cognito-identitypool-alpha"; import { Utils } from "./utils/utils"; import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; export class AiEcommerceSupportSimulatorStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const websiteBucket = new s3.Bucket(this, "WebsiteBucket", { enforceSSL: true, encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: new s3.BlockPublicAccess({ blockPublicPolicy: true, blockPublicAcls: true, ignorePublicAcls: true, restrictPublicBuckets: true, }), }); const hostingOrigin = origins.S3BucketOrigin.withOriginAccessControl(websiteBucket); const myResponseHeadersPolicy = new cloudfront.ResponseHeadersPolicy( this, "ResponseHeadersPolicy", { responseHeadersPolicyName: "ResponseHeadersPolicy" + cdk.Aws.STACK_NAME + "-" + cdk.Aws.REGION, comment: "ResponseHeadersPolicy" + cdk.Aws.STACK_NAME + "-" + cdk.Aws.REGION, securityHeadersBehavior: { contentTypeOptions: { override: true }, frameOptions: { frameOption: cloudfront.HeadersFrameOption.DENY, override: true, }, referrerPolicy: { referrerPolicy: cloudfront.HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN, override: false, }, strictTransportSecurity: { accessControlMaxAge: cdk.Duration.seconds(31536000), includeSubdomains: true, override: true, }, xssProtection: { protection: true, modeBlock: true, override: true }, }, } ); const distribution = new cloudfront.Distribution( this, "Distribution", { comment: "AI-Powered E-commerce Support Simulator", defaultRootObject: "index.html", httpVersion: cloudfront.HttpVersion.HTTP2_AND_3, minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, defaultBehavior:{ origin: hostingOrigin, responseHeadersPolicy: myResponseHeadersPolicy, cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, } } ); const userPool = new cognito.UserPool(this, "UserPool", { removalPolicy: cdk.RemovalPolicy.DESTROY, selfSignUpEnabled: false, autoVerify: { email: true, phone: true }, signInAliases: { email: true, }, }); const userPoolClient = userPool.addClient("UserPoolClient", { generateSecret: false, authFlows: { adminUserPassword: true, userPassword: true, userSrp: true, }, }); const identityPool = new cognitoIdentityPool.IdentityPool( this, "IdentityPool", { authenticationProviders: { userPools: [ new cognitoIdentityPool.UserPoolAuthenticationProvider({ userPool, userPoolClient, }), ], }, } ); const appPath = path.join(__dirname, "../resources/ui"); const buildPath = path.join(appPath, "dist"); const asset = s3deploy.Source.asset(appPath, { bundling: { image: cdk.DockerImage.fromRegistry( "public.ecr.aws/sam/build-nodejs20.x:latest" ), command: [ "sh", "-c", [ "npm --cache /tmp/.npm install", `npm --cache /tmp/.npm run build`, "cp -aur /asset-input/dist/* /asset-output/", ].join(" && "), ], local: { tryBundle(outputDir: string) { try { const options: ExecSyncOptionsWithBufferEncoding = { stdio: "inherit", env: { ...process.env, NODE_ENV: 'production', // Ensure production build npm_config_cache: `${process.env.HOME}/.npm`, // Use home directory for npm cache }, }; console.log(`Installing dependencies in ${appPath}...`); execSync(`npm --silent --prefix "${appPath}" install`, options); console.log(`Building project in ${appPath}...`); execSync(`npm --silent --prefix "${appPath}" run build`, options); console.log(`Copying build output from ${buildPath} to ${outputDir}...`); Utils.copyDirRecursive(buildPath, outputDir); return true; } catch (e) { if (e instanceof Error) { console.error('Error during local bundling:', e.message); console.error('Stack trace:', e.stack); } else { console.error('An unknown error occurred during local bundling'); } return false; } }, }, }, }); // Create the AppSync API const api = new appsync.GraphqlApi(this, "AiSupportApi", { name: "ai-support-api", definition: appsync.Definition.fromFile( path.join(__dirname, "../", "graphql", "schema.graphql") ), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.USER_POOL, userPoolConfig: { userPool: userPool, appIdClientRegex: userPoolClient.userPoolClientId, defaultAction: appsync.UserPoolDefaultAction.ALLOW, }, }, additionalAuthorizationModes: [ { authorizationType: appsync.AuthorizationType.IAM, }, ], }, logConfig: { fieldLogLevel: appsync.FieldLogLevel.ALL, }, xrayEnabled: true, }); const apiPolicyStatement = new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ "appsync:GraphQL", ], resources: [ `${api.arn}/*`, `${api.arn}/types/Mutation/*`, `${api.arn}/types/Subscription/*`, ], }); identityPool.authenticatedRole.addToPrincipalPolicy(apiPolicyStatement); const exportsAsset = s3deploy.Source.jsonData("aws-exports.json", { API: { GraphQL: { endpoint: api.graphqlUrl, region: cdk.Aws.REGION, defaultAuthMode: appsync.AuthorizationType.USER_POOL }, }, Auth: { Cognito: { userPoolClientId: userPoolClient.userPoolClientId, userPoolId: userPool.userPoolId, identityPoolId: identityPool.identityPoolId, }, } }); new s3deploy.BucketDeployment(this, "UserInterfaceDeployment", { prune: false, sources: [asset, exportsAsset], destinationBucket: websiteBucket, distribution, }); const customerIncommingMessagesQueue = new sqs.Queue(this, "CustomerMessagesQueue", { visibilityTimeout: cdk.Duration.minutes(10), }); const supportIncommingMessagestMessagesQueue = new sqs.Queue(this, "SupportMessagesQueue"); const outgoingMessagesQueue = new sqs.Queue(this, "OutgoingMessagesQueue", { //visibilityTimeout: cdk.Duration.minutes(10), }); const datasource = api.addHttpDataSource( "sqs", `https://sqs.${cdk.Aws.REGION}.amazonaws.com`, { authorizationConfig: { signingRegion: cdk.Aws.REGION, signingServiceName: "sqs", }, } ); customerIncommingMessagesQueue.grantSendMessages(datasource.grantPrincipal); supportIncommingMessagestMessagesQueue.grantSendMessages(datasource.grantPrincipal); const myJsFunction = new appsync.AppsyncFunction(this, 'function', { name: 'my_js_function', api, dataSource: datasource, code: appsync.Code.fromAsset( path.join(__dirname, '../graphql/Query.sendMessage.js') ), runtime: appsync.FunctionRuntime.JS_1_0_0, }); const sendResponseFunction = new appsync.AppsyncFunction(this, 'SendResponseFunction', { api: api, dataSource: api.addNoneDataSource('NoneDataSource'), name: 'SendResponseFunction', code: appsync.Code.fromAsset(path.join(__dirname, '../graphql/sendResponse.js')), runtime: appsync.FunctionRuntime.JS_1_0_0, }); const pipelineVars = JSON.stringify({ accountId: cdk.Aws.ACCOUNT_ID, customerQueueUrl: customerIncommingMessagesQueue.queueUrl, customerQueueName: customerIncommingMessagesQueue.queueName, supportQueueUrl: supportIncommingMessagestMessagesQueue.queueUrl, supportQueueName: supportIncommingMessagestMessagesQueue.queueName, }); // Create the pipeline resolver new appsync.Resolver(this, 'SendResponseResolver', { api: api, typeName: 'Mutation', fieldName: 'sendResponse', code: appsync.Code.fromAsset(path.join(__dirname, '../graphql/sendResponsePipeline.js')), runtime: appsync.FunctionRuntime.JS_1_0_0, pipelineConfig: [sendResponseFunction], }); new appsync.Resolver(this, 'PipelineResolver', { api, typeName: 'Query', fieldName: 'sendMessage', code: appsync.Code.fromInline(` // The before step export function request(...args) { console.log(args); return ${pipelineVars} } // The after step export function response(ctx) { return ctx.prev.result } `), runtime: appsync.FunctionRuntime.JS_1_0_0, pipelineConfig: [myJsFunction], }); const sessionTable = new dynamodb.Table(this, "SessionTable", { partitionKey: { name: "PK", type: dynamodb.AttributeType.STRING }, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, sortKey: { name: "SK", type: dynamodb.AttributeType.STRING }, timeToLiveAttribute: "TTL", removalPolicy: cdk.RemovalPolicy.DESTROY, }); // Create the initial processing Lambda const customerMessageLambda = new nodejs.NodejsFunction( this, "CustomerMessageLambda", { entry: path.join(__dirname, "../lambda/customerMessage/index.ts"), handler: "handler", timeout: cdk.Duration.minutes(9), runtime: lambda.Runtime.NODEJS_18_X, memorySize: 2048, architecture: lambda.Architecture.ARM_64, environment: { QUEUE_URL: outgoingMessagesQueue.queueUrl, HISTORY_TABLE_NAME: sessionTable.tableName, HISTORY_TABLE_TTL_KEY_NAME: 'TTL', HISTORY_TABLE_TTL_DURATION: '3600', }, bundling: { commandHooks: { afterBundling: (inputDir: string, outputDir: string): string[] => [ `cp ${inputDir}/resources/ui/public/mock_data.json ${outputDir}` ], beforeBundling: (inputDir: string, outputDir: string): string[] => [], beforeInstall: (inputDir: string, outputDir: string): string[] => [], }, }, } ); sessionTable.grantReadWriteData(customerMessageLambda); customerMessageLambda.addToRolePolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["bedrock:InvokeModel"], resources: ["*"], }) ); customerMessageLambda.addToRolePolicy(new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ 'ssm:*' ], resources: [ `arn:aws:ssm:${this.region}:${this.account}:parameter/*` ] })); const supportMessageLambda = new nodejs.NodejsFunction( this, "SupportMessageLambda", { entry: path.join(__dirname, "../lambda/supportMessage/index.ts"), handler: "handler", architecture: lambda.Architecture.ARM_64, timeout: cdk.Duration.seconds(29), runtime: lambda.Runtime.NODEJS_18_X, environment: { QUEUE_URL: outgoingMessagesQueue.queueUrl, }, } ); new lambda.EventSourceMapping(this, "CustomerEventSourceMapping", { target: customerMessageLambda, batchSize: 1, eventSourceArn: customerIncommingMessagesQueue.queueArn, }); new lambda.EventSourceMapping(this, "SupportEventSourceMapping", { target: supportMessageLambda, batchSize: 1, eventSourceArn: supportIncommingMessagestMessagesQueue.queueArn, }); customerIncommingMessagesQueue.grantConsumeMessages(customerMessageLambda); supportIncommingMessagestMessagesQueue.grantConsumeMessages(supportMessageLambda); outgoingMessagesQueue.grantSendMessages(customerMessageLambda); outgoingMessagesQueue.grantSendMessages(supportMessageLambda); // Create the response Lambda const sendResponse = new nodejs.NodejsFunction( this, "SendResponseLambda", { entry: path.join(__dirname, "../lambda/sendResponse/index.ts"), handler: "handler", runtime: lambda.Runtime.NODEJS_18_X, architecture: lambda.Architecture.ARM_64, environment: { REGION: cdk.Aws.REGION, APPSYNC_API_URL: api.graphqlUrl, }, } ); outgoingMessagesQueue.grantConsumeMessages(sendResponse); sendResponse.addToRolePolicy( new iam.PolicyStatement({ actions: ["appsync:GraphQL"], resources: [`${api.arn}/*`], }) ); new lambda.EventSourceMapping(this, "ResponseEventSourceMapping", { target: sendResponse, batchSize: 1, eventSourceArn: outgoingMessagesQueue.queueArn, }); new cdk.CfnOutput(this, "CloudfrontDomainName", { value: distribution.domainName }); } } ================================================ FILE: examples/ecommerce-support-simulator/lib/utils/utils.ts ================================================ import * as fs from "node:fs"; import * as path from "node:path"; export abstract class Utils { static copyDirRecursive(sourceDir: string, targetDir: string): void { if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir); } const files = fs.readdirSync(sourceDir); for (const file of files) { const sourceFilePath = path.join(sourceDir, file); const targetFilePath = path.join(targetDir, file); const stats = fs.statSync(sourceFilePath); if (stats.isDirectory()) { Utils.copyDirRecursive(sourceFilePath, targetFilePath); } else { fs.copyFileSync(sourceFilePath, targetFilePath); } } } } ================================================ FILE: examples/ecommerce-support-simulator/package.json ================================================ { "name": "ai-ecommerce-support-simulator", "version": "0.1.0", "bin": { "ai-ecommerce-support-simulator": "bin/ai-ecommerce-support-simulator.js" }, "scripts": { "build": "tsc", "watch": "tsc -w", "test": "jest", "cdk": "cdk" }, "devDependencies": { "@types/jest": "^29.5.12", "@types/node": "20.14.9", "jest": "^29.7.0", "ts-jest": "^29.1.5", "ts-node": "^10.9.2", "typescript": "~5.5.3" }, "dependencies": { "@astrojs/tailwind": "^5.1.1", "@aws-cdk/aws-cognito-identitypool-alpha": "^2.160.0-alpha.0", "@aws-cdk/aws-lambda": "^1.204.0", "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/client-appsync": "^3.658.1", "@aws-sdk/client-sqs": "^3.658.1", "@aws-sdk/client-ssm": "^3.658.1", "@aws-sdk/credential-provider-node": "^3.662.0", "@aws-sdk/signature-v4": "^3.374.0", "aws-cdk-lib": "2.195.0", "axios": "^1.12.2", "constructs": "^10.0.0", "esbuild": "^0.24.0", "agent-squad": "^0.0.14", "source-map-support": "^0.5.21" } } ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/.gitignore ================================================ # build output dist/ # generated types .astro/ # dependencies node_modules/ # logs npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* # environment variables .env .env.production # macOS-specific files .DS_Store # jetbrains setting folder .idea/ ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/.vscode/extensions.json ================================================ { "recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"], "unwantedRecommendations": [] } ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "command": "./node_modules/.bin/astro dev", "name": "Development server", "request": "launch", "type": "node-terminal" } ] } ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/README.md ================================================ # Astro Starter Kit: Blog ```sh npm create astro@latest -- --template blog ``` [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog) [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json) > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! ![blog](https://github.com/withastro/astro/assets/2244813/ff10799f-a816-4703-b967-c78997e8323d) Features: - ✅ Minimal styling (make it your own!) - ✅ 100/100 Lighthouse performance - ✅ SEO-friendly with canonical URLs and OpenGraph data - ✅ Sitemap support - ✅ RSS Feed support - ✅ Markdown & MDX support ## 🚀 Project Structure Inside of your Astro project, you'll see the following folders and files: ```text ├── public/ ├── src/ │   ├── components/ │   ├── content/ │   ├── layouts/ │   └── pages/ ├── astro.config.mjs ├── README.md ├── package.json └── tsconfig.json ``` Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more. Any static assets, like images, can be placed in the `public/` directory. ## 🧞 Commands All commands are run from the root of the project, from a terminal: | Command | Action | | :------------------------ | :----------------------------------------------- | | `npm install` | Installs dependencies | | `npm run dev` | Starts local dev server at `localhost:4321` | | `npm run build` | Build your production site to `./dist/` | | `npm run preview` | Preview your build locally, before deploying | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | | `npm run astro -- --help` | Get help using the Astro CLI | ## 👀 Want to learn more? Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). ## Credit This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/). ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/astro.config.mjs ================================================ import { defineConfig } from 'astro/config'; import react from "@astrojs/react"; import tailwind from "@astrojs/tailwind"; export default defineConfig({ integrations: [react(), tailwind()] }); ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/package.json ================================================ { "name": "ai-ecommerce-support-simulator", "type": "module", "version": "0.0.1", "scripts": { "dev": "astro dev", "start": "astro dev", "build": "astro check && astro build", "preview": "astro preview", "astro": "astro" }, "dependencies": { "@astrojs/check": "^0.9.3", "@astrojs/mdx": "^3.1.7", "@astrojs/react": "^3.6.2", "@astrojs/rss": "^4.0.7", "@astrojs/sitemap": "^3.1.6", "@aws-amplify/api": "^6.0.51", "@aws-amplify/ui-react": "^6.5.2", "astro": "^4.16.18", "aws-amplify": "^6.6.3", "lucide-react": "^0.446.0", "react": "^18.3.1", "react-markdown": "^9.0.1", "typescript": "^5.6.2" }, "devDependencies": { "@astrojs/tailwind": "^5.1.1", "tailwindcss": "^3.4.13" } } ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/public/mock_data.json ================================================ { "orders": { "12345": { "status": "Shipped", "items": ["Widget A", "Gadget B"], "total": 150.0 }, "67890": { "status": "Processing", "items": ["Gizmo C"], "total": 75.5 } }, "shipments": { "12345": { "carrier": "FastShip", "trackingNumber": "FS123456789", "status": "In Transit" } } } ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/src/components/ChatMode.tsx ================================================ import React, { useEffect, useRef } from 'react'; import { Send } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import type { Message } from '../types'; const ChatMode = ({ messages, customerMessage, setCustomerMessage, supportMessage, setSupportMessage, handleCustomerSubmit, handleSupportSubmit, }) => { const customerChatRef = useRef(null); const supportChatRef = useRef(null); const customerInputRef = useRef(null); const supportInputRef = useRef(null); useEffect(() => { if (customerChatRef.current) { customerChatRef.current.scrollTop = customerChatRef.current.scrollHeight; } if (supportChatRef.current) { supportChatRef.current.scrollTop = supportChatRef.current.scrollHeight; } if (messages[messages.length - 1]?.destination === 'customer') { customerInputRef.current?.focus(); } else { supportInputRef.current?.focus(); } }, [messages]); const renderMessages = (isCustomer: boolean) => { return messages.map((msg: Message, index: number) => { const isVisible = isCustomer ? msg.destination === 'customer' : msg.destination === 'support'; const isFromUI = msg.source === 'ui'; let senderLabel: string; if (isCustomer) { senderLabel = isFromUI ? 'Me' : 'Sam from Support'; } else { senderLabel = isFromUI ? 'Me' : 'Alex'; } if (!isVisible) return null; return (

      {senderLabel}

      , pre: ({node, ...props}) =>

                    }}
                  >
                    {msg.content}
                  
                  

      {new Date(msg.timestamp).toLocaleTimeString()}

      ); }); }; const submitMessage = (e: React.FormEvent, isCustomer: boolean) => { e.preventDefault(); const message = isCustomer ? customerMessage : supportMessage; if (!message.trim()) return; const newMessage = { content: message, destination: isCustomer ? 'customer' : 'support', source: 'ui', timestamp: new Date().toISOString() }; if (isCustomer) { handleCustomerSubmit(newMessage); setCustomerMessage(''); } else { handleSupportSubmit(newMessage); setSupportMessage(''); } }; return (
      {/* Customer Chat */}

      Customer Chat

      {renderMessages(true)}
      submitMessage(e, true)} className="flex mt-auto"> setCustomerMessage(e.target.value)} className="flex-grow mr-2 p-2 rounded-lg border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-gray-900 placeholder-gray-500" placeholder="Type a message..." />
      {/* Support Chat */}

      Support Chat

      {renderMessages(false)}
      submitMessage(e, false)} className="flex mt-auto"> setSupportMessage(e.target.value)} className="flex-grow mr-2 p-2 rounded-lg border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 text-gray-900 placeholder-gray-500" placeholder="Type a response..." />
      ); }; export default ChatMode; ================================================ FILE: examples/ecommerce-support-simulator/resources/ui/src/components/EmailMode.tsx ================================================ import React from 'react'; import { Send } from 'lucide-react'; const EmailMode = ({ fromEmail, setFromEmail, selectedTemplate, handleTemplateChange, customerMessage, setCustomerMessage, supportMessage, setSupportMessage, customerResponse, supportResponse, handleCustomerSubmit, handleSupportSubmit, emailTemplates }) => { const submitCustomerEmail = (e: any) => { e.preventDefault(); if (customerMessage.trim() === '') return; const newMessage = { content: customerMessage, destination: 'customer', source: 'ui', timestamp: new Date().toISOString() }; handleCustomerSubmit(newMessage); }; const submitSupportEmail = (e: any) => { e.preventDefault(); if (supportMessage.trim() === '') return; const newMessage = { content: supportMessage, destination: 'support', source: 'ui', timestamp: new Date().toISOString() }; handleSupportSubmit(newMessage); }; const isCustomerMessageEmpty = customerMessage.trim() === ''; const isSupportMessageEmpty = supportMessage.trim() === ''; return (
      {/* Customer Email */}

      Customer Email

      setFromEmail(e.target.value)} className="w-full p-2 rounded-lg border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200" required />