Skip to content

Commit 63cc68e

Browse files
committed
init commit
1 parent 9baec9a commit 63cc68e

File tree

8 files changed

+548
-0
lines changed

8 files changed

+548
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
## SPDX-License-Identifier: Apache-2.0
3+
4+
name: Python EC2 LangChain Service Deployment
5+
on:
6+
push:
7+
branches:
8+
- gen-ai-sample-app
9+
10+
permissions:
11+
id-token: write
12+
contents: read
13+
14+
env:
15+
E2E_TEST_AWS_REGION: 'us-west-2'
16+
# E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }}
17+
# E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }}
18+
E2E_TEST_ACCOUNT_ID: 571600841604
19+
E2E_TEST_ROLE_NAME: github
20+
METRIC_NAMESPACE: genesis
21+
LOG_GROUP_NAME: test/genesis
22+
TEST_RESOURCES_FOLDER: ${GITHUB_WORKSPACE}
23+
24+
jobs:
25+
python-ec2-adot-langchain:
26+
runs-on: ubuntu-latest
27+
timeout-minutes: 30
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- name: Initiate Gradlew Daemon
32+
uses: ./.github/workflows/actions/execute_and_retry
33+
continue-on-error: true
34+
with:
35+
command: "./gradlew :validator:build"
36+
cleanup: "./gradlew clean"
37+
max_retry: 3
38+
sleep_time: 60
39+
40+
- name: Generate testing id
41+
run: echo TESTING_ID="${{ github.run_id }}-${{ github.run_number }}-${RANDOM}" >> $GITHUB_ENV
42+
43+
- name: Configure AWS Credentials
44+
uses: aws-actions/configure-aws-credentials@v4
45+
with:
46+
role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }}
47+
aws-region: ${{ env.E2E_TEST_AWS_REGION }}
48+
49+
- name: Set up terraform
50+
uses: ./.github/workflows/actions/execute_and_retry
51+
with:
52+
command: "wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg"
53+
post-command: 'echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
54+
&& sudo apt update && sudo apt install terraform'
55+
sleep_time: 60
56+
57+
- name: Initiate Terraform
58+
uses: ./.github/workflows/actions/execute_and_retry
59+
with:
60+
command: "cd ${{ env.TEST_RESOURCES_FOLDER }}/terraform/python/ec2/langchain && terraform init && terraform validate"
61+
cleanup: "rm -rf .terraform && rm -rf .terraform.lock.hcl"
62+
max_retry: 6
63+
sleep_time: 60
64+
65+
- name: Deploy LangChain service via terraform
66+
working-directory: terraform/python/ec2/langchain
67+
run: |
68+
terraform apply -auto-approve \
69+
-var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \
70+
-var="test_id=${{ env.TESTING_ID }}" \
71+
-var="service_zip_url=s3://sigv4perfresults/langchain-service.zip"
72+
73+
- name: Get deployment info
74+
working-directory: terraform/python/ec2/langchain
75+
run: |
76+
echo "INSTANCE_IP=$(terraform output public_ip)" >> $GITHUB_ENV
77+
echo "INSTANCE_ID=$(terraform output instance_id)" >> $GITHUB_ENV
78+
79+
- name: Test LangChain service
80+
run: |
81+
echo "Testing LangChain service at ${{ env.INSTANCE_IP }}:8000"
82+
curl -f http://${{ env.INSTANCE_IP }}:8000/health
83+
curl -f http://${{ env.INSTANCE_IP }}:8000/
84+
85+
- name: Generate traffic
86+
run: |
87+
cd sample-apps/traffic-generator/genai
88+
chmod +x generate_traffic.sh
89+
./generate_traffic.sh http://${{ env.INSTANCE_IP }}:8000
90+
91+
- name: Validate generated logs
92+
run: ./gradlew validator:run --args='-c python/ec2/default/log-validation.yml
93+
--testing-id ${{ env.TESTING_ID }}
94+
--endpoint http://${{ env.INSTANCE_IP }}:8000
95+
--region ${{ env.E2E_TEST_AWS_REGION }}
96+
--metric-namespace ${{ env.METRIC_NAMESPACE }}
97+
--log-group ${{ env.LOG_GROUP_NAME }}
98+
--service-name langchain-traceloop-app
99+
--instance-id ${{ env.INSTANCE_ID }}'
100+
101+
- name: Validate generated metrics
102+
if: (success() || failure()) && !cancelled()
103+
run: ./gradlew validator:run --args='-c python/ec2/default/metric-validation.yml
104+
--testing-id ${{ env.TESTING_ID }}
105+
--endpoint http://${{ env.INSTANCE_IP }}:8000
106+
--region ${{ env.E2E_TEST_AWS_REGION }}
107+
--metric-namespace ${{ env.METRIC_NAMESPACE }}
108+
--service-name langchain-traceloop-app
109+
--instance-id ${{ env.INSTANCE_ID }}'
110+
111+
- name: Validate generated traces
112+
if: (success() || failure()) && !cancelled()
113+
run: ./gradlew validator:run --args='-c python/ec2/default/trace-validation.yml
114+
--testing-id ${{ env.TESTING_ID }}
115+
--endpoint http://${{ env.INSTANCE_IP }}:8000
116+
--region ${{ env.E2E_TEST_AWS_REGION }}
117+
--account-id ${{ env.E2E_TEST_ACCOUNT_ID }}
118+
--service-name langchain-traceloop-app
119+
--instance-id ${{ env.INSTANCE_ID }}'
120+
121+
- name: Cleanup
122+
if: always()
123+
continue-on-error: true
124+
working-directory: terraform/python/ec2/langchain
125+
run: |
126+
terraform destroy -auto-approve \
127+
-var="aws_region=${{ env.E2E_TEST_AWS_REGION }}" \
128+
-var="test_id=${{ env.TESTING_ID }}" \
129+
-var="service_zip_url=s3://sigv4perfresults/langchain-service.zip"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
COPY . .
6+
7+
RUN pip install --no-cache-dir -r ec2-requirements.txt
8+
9+
EXPOSE 8000
10+
11+
CMD ["opentelemetry-instrument", "python", "server.py"]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
langchain
2+
langchain-community
3+
opentelemetry-sdk
4+
opentelemetry-api
5+
opentelemetry-semantic-conventions
6+
python-dotenv
7+
openlit
8+
botocore
9+
setuptools
10+
boto3
11+
./aws_opentelemetry_distro_genai_beta-0.1.0b8-py3-none-any.whl
12+
fastapi
13+
uvicorn[standard]
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import os
2+
from typing import Dict, List
3+
from dotenv import load_dotenv
4+
from fastapi import FastAPI, HTTPException
5+
from pydantic import BaseModel
6+
from langchain_aws import ChatBedrock
7+
from langchain.prompts import ChatPromptTemplate
8+
from langchain.chains import LLMChain
9+
from opentelemetry import trace
10+
from opentelemetry.sdk.trace import TracerProvider
11+
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
12+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
13+
from openinference.instrumentation.langchain import LangChainInstrumentor
14+
15+
# Load environment variables
16+
load_dotenv()
17+
18+
# Set up OpenTelemetry with BOTH exporters
19+
tracer_provider = TracerProvider()
20+
21+
# Add Console exporter
22+
console_exporter = ConsoleSpanExporter()
23+
console_processor = BatchSpanProcessor(console_exporter)
24+
tracer_provider.add_span_processor(console_processor)
25+
26+
# Add OTLP exporter
27+
otlp_exporter = OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")
28+
otlp_processor = BatchSpanProcessor(otlp_exporter)
29+
tracer_provider.add_span_processor(otlp_processor)
30+
31+
# Set as global provider
32+
trace.set_tracer_provider(tracer_provider)
33+
34+
# Instrument LangChain with OpenInference
35+
LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
36+
37+
# Initialize FastAPI app
38+
app = FastAPI(title="LangChain Bedrock OpenInference API", version="1.0.0")
39+
40+
# Initialize the LLM with AWS Bedrock
41+
llm = ChatBedrock(
42+
model_id="anthropic.claude-3-haiku-20240307-v1:0",
43+
model_kwargs={
44+
"temperature": 0.7,
45+
"max_tokens": 500
46+
},
47+
region_name=os.getenv("AWS_DEFAULT_REGION", "us-west-2")
48+
)
49+
50+
# Create a prompt template
51+
prompt = ChatPromptTemplate.from_template(
52+
"You are a helpful assistant. The user says: {input}. Provide a helpful response."
53+
)
54+
55+
# Create a chain
56+
chain = LLMChain(llm=llm, prompt=prompt)
57+
58+
# Request models
59+
class ChatRequest(BaseModel):
60+
message: str
61+
62+
class BatchChatRequest(BaseModel):
63+
messages: List[str]
64+
65+
class ChatResponse(BaseModel):
66+
response: str
67+
68+
class BatchChatResponse(BaseModel):
69+
responses: List[Dict[str, str]]
70+
71+
# Sample prompts for testing
72+
SAMPLE_PROMPTS = [
73+
"What is the capital of France?",
74+
"How do I make a cup of coffee?",
75+
"What are the benefits of exercise?",
76+
"Explain quantum computing in simple terms",
77+
"What's the best way to learn programming?"
78+
]
79+
80+
@app.get("/")
81+
async def root():
82+
return {
83+
"message": "LangChain Bedrock OpenInference API is running!",
84+
"endpoints": {
85+
"/chat": "Single message chat endpoint",
86+
"/batch": "Batch message processing endpoint",
87+
"/sample": "Run sample prompts"
88+
}
89+
}
90+
91+
@app.post("/chat", response_model=ChatResponse)
92+
async def chat(request: ChatRequest):
93+
"""
94+
Chat endpoint that processes a single user message through AWS Bedrock
95+
"""
96+
try:
97+
# Process the input through the chain
98+
result = await chain.ainvoke({"input": request.message})
99+
return ChatResponse(response=result["text"])
100+
except Exception as e:
101+
raise HTTPException(status_code=500, detail=str(e))
102+
103+
@app.post("/batch", response_model=BatchChatResponse)
104+
async def batch_chat(request: BatchChatRequest):
105+
"""
106+
Batch endpoint that processes multiple messages
107+
"""
108+
try:
109+
responses = []
110+
for message in request.messages:
111+
result = await chain.ainvoke({"input": message})
112+
responses.append({
113+
"message": message,
114+
"response": result["text"]
115+
})
116+
return BatchChatResponse(responses=responses)
117+
except Exception as e:
118+
raise HTTPException(status_code=500, detail=str(e))
119+
120+
@app.get("/sample", response_model=BatchChatResponse)
121+
async def run_samples():
122+
"""
123+
Run the predefined sample prompts
124+
"""
125+
try:
126+
responses = []
127+
for prompt in SAMPLE_PROMPTS:
128+
result = await chain.ainvoke({"input": prompt})
129+
responses.append({
130+
"message": prompt,
131+
"response": result["text"]
132+
})
133+
return BatchChatResponse(responses=responses)
134+
except Exception as e:
135+
raise HTTPException(status_code=500, detail=str(e))
136+
137+
@app.get("/health")
138+
async def health():
139+
"""Health check endpoint"""
140+
return {"status": "healthy", "llm": "AWS Bedrock Claude 3 Haiku"}
141+
142+
if __name__ == "__main__":
143+
import uvicorn
144+
print("Starting FastAPI server with AWS Bedrock and OpenInference instrumentation...")
145+
print("Make sure AWS credentials are configured")
146+
print("Server will run on http://localhost:8000")
147+
print("API docs available at http://localhost:8000/docs")
148+
uvicorn.run(app, host="0.0.0.0", port=8000)

0 commit comments

Comments
 (0)