Skip to content

Commit ba5fd0f

Browse files
brendan-myerssteveandroulakiscretz
authored
Bedrock sample (#116)
* init + fix: logging * workflow 2 now gives a chat summary at the end. Added python typing * formatting * fix: workflow params, localized try/catch, types, tidying * refactor: activities to fit blog, workers to single aync function * feat: sync return result * refactor: tidy * refactor: only generate summary at wf end * fix: query named * docs: add bedrock link to readme * refactor: rename bedrock sample directories * build: use poetry to run bedrock samples * refactor: rename dirs to allow poe to run without modification * refactor: shared activity, shared boto3 client, separate workflow ids * refactor: remove unneeded kwargs * refactor: minor tidyups * fix: linting * fix: start workflow with empty params * refactor: only summarize once * refactor: linting * refactor * log warning if messages received while chat is closed * chore: linting * Update bedrock/basic/README.md Co-authored-by: Chad Retz <[email protected]> * Update bedrock/basic/send_message.py Co-authored-by: Chad Retz <[email protected]> * Update bedrock/entity/workflows.py Co-authored-by: Chad Retz <[email protected]> * chad changes * prerequisites in README * refactor: mionr changes --------- Co-authored-by: Steve Androulakis <[email protected]> Co-authored-by: Chad Retz <[email protected]>
1 parent ce12576 commit ba5fd0f

25 files changed

+1189
-521
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Some examples require extra dependencies. See each sample's directory for specif
5252
* [hello_signal](hello/hello_signal.py) - Send signals to a workflow.
5353
<!-- Keep this list in alphabetical order -->
5454
* [activity_worker](activity_worker) - Use Python activities from a workflow in another language.
55+
* [bedrock](bedrock) - Orchestrate a chatbot with Amazon Bedrock.
5556
* [cloud_export_to_parquet](cloud_export_to_parquet) - Set up schedule workflow to process exported files on an hourly basis
5657
* [context_propagation](context_propagation) - Context propagation through workflows/activities via interceptor.
5758
* [custom_converter](custom_converter) - Use a custom payload converter to handle custom types.

Diff for: bedrock/README.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# AI Chatbot example using Amazon Bedrock
2+
3+
Demonstrates how Temporal and Amazon Bedrock can be used to quickly build bulletproof AI applications.
4+
5+
## Samples
6+
7+
* [basic](basic) - A basic Bedrock workflow to process a single prompt.
8+
* [signals_and_queries](signals_and_queries) - Extension to the basic workflow to allow multiple prompts through signals & queries.
9+
* [entity](entity) - Full multi-Turn chat using an entity workflow..
10+
11+
## Pre-requisites
12+
13+
1. An AWS account with Bedrock enabled.
14+
2. A machine that has access to Bedrock.
15+
3. A local Temporal server running on the same machine. See [Temporal's dev server docs](https://docs.temporal.io/cli#start-dev-server) for more information.
16+
17+
These examples use Amazon's Python SDK (Boto3). To configure Boto3 to use your AWS credentials, follow the instructions in [the Boto3 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html).
18+
19+
## Running the samples
20+
21+
For these sample, the optional `bedrock` dependency group must be included. To include, run:
22+
23+
poetry install --with bedrock
24+
25+
There are 3 Bedrock samples, see the README.md in each sub-directory for instructions on running each.

Diff for: bedrock/__init__.py

Whitespace-only changes.

Diff for: bedrock/basic/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Basic Amazon Bedrock workflow
2+
3+
A basic Bedrock workflow. Starts a workflow with a prompt, generates a response and ends the workflow.
4+
5+
To run, first see `samples-python` [README.md](../../README.md), and `bedrock` [README.md](../README.md) for prerequisites specific to this sample. Once set up, run the following from this directory:
6+
7+
1. Run the worker: `poetry run python run_worker.py`
8+
2. In another terminal run the client with a prompt:
9+
10+
e.g. `poetry run python send_message.py 'What animals are marsupials?'`

Diff for: bedrock/basic/__init__.py

Whitespace-only changes.

Diff for: bedrock/basic/run_worker.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import asyncio
2+
import concurrent.futures
3+
import logging
4+
5+
from temporalio.client import Client
6+
from temporalio.worker import Worker
7+
from workflows import BasicBedrockWorkflow
8+
9+
from bedrock.shared.activities import BedrockActivities
10+
11+
12+
async def main():
13+
# Create client connected to server at the given address
14+
client = await Client.connect("localhost:7233")
15+
activities = BedrockActivities()
16+
17+
# Run the worker
18+
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as activity_executor:
19+
worker = Worker(
20+
client,
21+
task_queue="bedrock-task-queue",
22+
workflows=[BasicBedrockWorkflow],
23+
activities=[activities.prompt_bedrock],
24+
activity_executor=activity_executor,
25+
)
26+
await worker.run()
27+
28+
29+
if __name__ == "__main__":
30+
print("Starting worker")
31+
print("Then run 'python send_message.py \"<prompt>\"'")
32+
33+
logging.basicConfig(level=logging.INFO)
34+
35+
asyncio.run(main())

Diff for: bedrock/basic/send_message.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import asyncio
2+
import sys
3+
4+
from temporalio.client import Client
5+
from workflows import BasicBedrockWorkflow
6+
7+
8+
async def main(prompt: str) -> str:
9+
# Create client connected to server at the given address
10+
client = await Client.connect("localhost:7233")
11+
12+
# Start the workflow
13+
workflow_id = "basic-bedrock-workflow"
14+
handle = await client.start_workflow(
15+
BasicBedrockWorkflow.run,
16+
prompt, # Initial prompt
17+
id=workflow_id,
18+
task_queue="bedrock-task-queue",
19+
)
20+
return await handle.result()
21+
22+
23+
if __name__ == "__main__":
24+
if len(sys.argv) != 2:
25+
print("Usage: python send_message.py '<prompt>'")
26+
print("Example: python send_message.py 'What animals are marsupials?'")
27+
else:
28+
result = asyncio.run(main(sys.argv[1]))
29+
print(result)

Diff for: bedrock/basic/workflows.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from datetime import timedelta
2+
3+
from temporalio import workflow
4+
5+
with workflow.unsafe.imports_passed_through():
6+
from bedrock.shared.activities import BedrockActivities
7+
8+
9+
@workflow.defn
10+
class BasicBedrockWorkflow:
11+
@workflow.run
12+
async def run(self, prompt: str) -> str:
13+
14+
workflow.logger.info("Prompt: %s" % prompt)
15+
16+
response = await workflow.execute_activity_method(
17+
BedrockActivities.prompt_bedrock,
18+
prompt,
19+
schedule_to_close_timeout=timedelta(seconds=20),
20+
)
21+
22+
workflow.logger.info("Response: %s" % response)
23+
24+
return response

Diff for: bedrock/entity/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Multi-turn chat with Amazon Bedrock Entity Workflow
2+
3+
Multi-Turn Chat using an Entity Workflow. The workflow runs forever unless explicitly ended. The workflow continues as new after a configurable number of chat turns to keep the prompt size small and the Temporal event history small. Each continued-as-new workflow receives a summary of the conversation history so far for context.
4+
5+
To run, first see `samples-python` [README.md](../../README.md), and `bedrock` [README.md](../README.md) for prerequisites specific to this sample. Once set up, run the following from this directory:
6+
7+
1. Run the worker: `poetry run python run_worker.py`
8+
2. In another terminal run the client with a prompt.
9+
10+
Example: `poetry run python send_message.py 'What animals are marsupials?'`
11+
12+
3. View the worker's output for the response.
13+
4. Give followup prompts by signaling the workflow.
14+
15+
Example: `poetry run python send_message.py 'Do they lay eggs?'`
16+
5. Get the conversation history summary by querying the workflow.
17+
18+
Example: `poetry run python get_history.py`
19+
6. To end the chat session, run `poetry run python end_chat.py`

Diff for: bedrock/entity/__init__.py

Whitespace-only changes.

Diff for: bedrock/entity/end_chat.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import asyncio
2+
import sys
3+
4+
from temporalio.client import Client
5+
from workflows import EntityBedrockWorkflow
6+
7+
8+
async def main():
9+
# Create client connected to server at the given address
10+
client = await Client.connect("localhost:7233")
11+
12+
workflow_id = "entity-bedrock-workflow"
13+
14+
handle = client.get_workflow_handle_for(EntityBedrockWorkflow.run, workflow_id)
15+
16+
# Sends a signal to the workflow
17+
await handle.signal(EntityBedrockWorkflow.end_chat)
18+
19+
20+
if __name__ == "__main__":
21+
print("Sending signal to end chat.")
22+
asyncio.run(main())

Diff for: bedrock/entity/get_history.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import asyncio
2+
3+
from temporalio.client import Client
4+
from workflows import EntityBedrockWorkflow
5+
6+
7+
async def main():
8+
# Create client connected to server at the given address
9+
client = await Client.connect("localhost:7233")
10+
workflow_id = "entity-bedrock-workflow"
11+
12+
handle = client.get_workflow_handle(workflow_id)
13+
14+
# Queries the workflow for the conversation history
15+
history = await handle.query(EntityBedrockWorkflow.get_conversation_history)
16+
17+
print("Conversation History")
18+
print(
19+
*(f"{speaker.title()}: {message}\n" for speaker, message in history), sep="\n"
20+
)
21+
22+
# Queries the workflow for the conversation summary
23+
summary = await handle.query(EntityBedrockWorkflow.get_summary_from_history)
24+
25+
if summary is not None:
26+
print("Conversation Summary:")
27+
print(summary)
28+
29+
30+
if __name__ == "__main__":
31+
asyncio.run(main())

Diff for: bedrock/entity/run_worker.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import asyncio
2+
import concurrent.futures
3+
import logging
4+
5+
from temporalio.client import Client
6+
from temporalio.worker import Worker
7+
from workflows import EntityBedrockWorkflow
8+
9+
from bedrock.shared.activities import BedrockActivities
10+
11+
12+
async def main():
13+
# Create client connected to server at the given address
14+
client = await Client.connect("localhost:7233")
15+
activities = BedrockActivities()
16+
17+
# Run the worker
18+
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as activity_executor:
19+
worker = Worker(
20+
client,
21+
task_queue="bedrock-task-queue",
22+
workflows=[EntityBedrockWorkflow],
23+
activities=[activities.prompt_bedrock],
24+
activity_executor=activity_executor,
25+
)
26+
await worker.run()
27+
28+
29+
if __name__ == "__main__":
30+
print("Starting worker")
31+
print("Then run 'python send_message.py \"<prompt>\"'")
32+
33+
logging.basicConfig(level=logging.INFO)
34+
35+
asyncio.run(main())

Diff for: bedrock/entity/send_message.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import asyncio
2+
import sys
3+
4+
from temporalio.client import Client
5+
from workflows import BedrockParams, EntityBedrockWorkflow
6+
7+
8+
async def main(prompt):
9+
# Create client connected to server at the given address
10+
client = await Client.connect("localhost:7233")
11+
12+
workflow_id = "entity-bedrock-workflow"
13+
14+
# Sends a signal to the workflow (and starts it if needed)
15+
await client.start_workflow(
16+
EntityBedrockWorkflow.run,
17+
BedrockParams(None, None),
18+
id=workflow_id,
19+
task_queue="bedrock-task-queue",
20+
start_signal="user_prompt",
21+
start_signal_args=[prompt],
22+
)
23+
24+
25+
if __name__ == "__main__":
26+
if len(sys.argv) != 2:
27+
print("Usage: python send_message.py '<prompt>'")
28+
print("Example: python send_message.py 'What animals are marsupials?'")
29+
else:
30+
asyncio.run(main(sys.argv[1]))

0 commit comments

Comments
 (0)