mirror of
https://github.com/langchain-ai/control-plane-api-demo.git
synced 2026-06-30 20:47:56 -04:00
first commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
OPENAI_API_KEY=<your-openai-api-key>
|
||||
LANGSMITH_API_KEY=<your-langsmith-api-key>
|
||||
@@ -0,0 +1,170 @@
|
||||
name: Create New Deployment
|
||||
|
||||
# Trigger the workflow on push or pull request merge to main
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
types: [ closed ]
|
||||
|
||||
# Set environment variables
|
||||
env:
|
||||
PYTHON_VERSION: '3.11'
|
||||
REGISTRY: docker.io # Change to ghcr.io for GitHub Container Registry
|
||||
IMAGE_NAME: davidxu33/control-plane-api-demo # Replace YOUR_DOCKERHUB_USERNAME with your actual Docker Hub username
|
||||
|
||||
jobs:
|
||||
# Job 1: Run custom logic and tests
|
||||
run-custom-logic:
|
||||
if: false && (github.ref == 'refs/heads/main' || (github.event.pull_request.merged == true))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
should-deploy: ${{ steps.validation.outputs.deploy }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f requirements.txt ]; then
|
||||
pip install -r requirements.txt
|
||||
fi
|
||||
|
||||
- name: Run validation and tests
|
||||
id: validation
|
||||
run: |
|
||||
echo "✅ Skipping validation tests - proceeding with deployment"
|
||||
# Set output to indicate that deployment should proceed
|
||||
echo "deploy=true" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Job 2: Build and push Docker image
|
||||
build-and-push:
|
||||
needs: run-custom-logic
|
||||
if: needs.run-custom-logic.outputs.should-deploy == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # Required for pushing to GitHub Container Registry
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Step 1: Set up Docker Buildx (advanced Docker build features)
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Step 2: Log in to Docker registry
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
# Step 3: Extract metadata for Docker tags and labels
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
# Branch-based tags
|
||||
type=ref,event=branch
|
||||
# SHA-based tags
|
||||
type=sha,prefix={{branch}}-
|
||||
# Latest tag for main branch
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
# Semantic versioning (if you use git tags)
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=LangGraph AI Agent
|
||||
org.opencontainers.image.description=AI Agent built with LangGraph
|
||||
org.opencontainers.image.vendor=YourCompany
|
||||
|
||||
# Step 4: Build and push Docker image
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile # Path to your Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64 # Multi-platform build
|
||||
cache-from: type=gha # Use GitHub Actions cache
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
|
||||
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
|
||||
# Step 5: Output image details
|
||||
- name: Output image details
|
||||
run: |
|
||||
echo "🐳 Docker image built and pushed successfully!"
|
||||
echo "📦 Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||
echo "🏷️ Tags: ${{ steps.meta.outputs.tags }}"
|
||||
echo "📋 Digest: ${{ steps.build.outputs.digest }}"
|
||||
|
||||
# Job 3: Post-deployment actions (optional)
|
||||
post-deployment:
|
||||
needs: [run-custom-logic, build-and-push]
|
||||
if: success()
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Deploy notification
|
||||
run: |
|
||||
echo "✅ Deployment completed successfully!"
|
||||
echo "🚀 New image available at: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
||||
|
||||
# Deploy to LangChain hosted platform
|
||||
- name: Trigger LangChain deployment
|
||||
run: |
|
||||
echo "🚀 Triggering deployment to LangChain hosted platform..."
|
||||
|
||||
response=$(curl -s -w "HTTPSTATUS:%{http_code}" \
|
||||
https://gtm.smith.langchain.dev/api-host/v1/projects \
|
||||
--request POST \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'X-Api-Key: ${{ secrets.LANGSMITH_API_KEY }}' \
|
||||
--data '{
|
||||
"name": "multi-agent-${{ github.sha }}",
|
||||
"lc_hosted": false,
|
||||
"env_vars": [{"name": "OPENAI_API_KEY", "value": "${{ secrets.OPENAI_API_KEY }}", "type": "secret"}],
|
||||
"deployment_type": "dev",
|
||||
"shareable": false,
|
||||
"image_path": "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest",
|
||||
"build_on_push": false
|
||||
}')
|
||||
|
||||
# Extract HTTP status and response body
|
||||
http_status=$(echo $response | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
|
||||
response_body=$(echo $response | sed -e 's/HTTPSTATUS\:.*//g')
|
||||
|
||||
# Check if deployment was successful
|
||||
if [ $http_status -eq 200 ] || [ $http_status -eq 201 ]; then
|
||||
echo "✅ LangChain deployment triggered successfully!"
|
||||
echo "📋 Response: $response_body"
|
||||
else
|
||||
echo "❌ LangChain deployment failed with status: $http_status"
|
||||
echo "📋 Response: $response_body"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,161 @@
|
||||
name: Create New Revision
|
||||
|
||||
# Trigger the workflow on push to main
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
types: [ closed ]
|
||||
|
||||
# Set environment variables
|
||||
env:
|
||||
PYTHON_VERSION: '3.11'
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME: davidxu33/control-plane-api-demo
|
||||
PROJECT_ID: 'd6cee583-a32a-4342-a748-3dfc857ba964' # Replace with your actual project ID
|
||||
|
||||
jobs:
|
||||
# Job 1: Run validation
|
||||
validate:
|
||||
if: github.ref == 'refs/heads/main' || (github.event.pull_request.merged == true)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
should-deploy: ${{ steps.validation.outputs.deploy }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f requirements.txt ]; then
|
||||
pip install -r requirements.txt
|
||||
fi
|
||||
|
||||
- name: Run validation
|
||||
id: validation
|
||||
run: |
|
||||
echo "✅ Validation passed - proceeding with revision update"
|
||||
echo "deploy=true" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Job 2: Build and push Docker image
|
||||
build-and-push:
|
||||
needs: validate
|
||||
if: needs.validate.outputs.should-deploy == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=LangGraph AI Agent
|
||||
org.opencontainers.image.description=AI Agent built with LangGraph
|
||||
org.opencontainers.image.vendor=YourCompany
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
|
||||
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
|
||||
- name: Output image details
|
||||
run: |
|
||||
echo "🐳 Docker image built and pushed successfully!"
|
||||
echo "📦 Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
||||
echo "🏷️ Tags: ${{ steps.meta.outputs.tags }}"
|
||||
|
||||
# Job 3: Update project revision
|
||||
update-revision:
|
||||
needs: [validate, build-and-push]
|
||||
if: success()
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Update notification
|
||||
run: |
|
||||
echo "🔄 Updating project revision..."
|
||||
echo "📦 Project ID: ${{ env.PROJECT_ID }}"
|
||||
echo "🚀 New image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
||||
|
||||
- name: Update LangChain project revision
|
||||
run: |
|
||||
echo "🔄 Triggering revision update for project ${{ env.PROJECT_ID }}..."
|
||||
|
||||
response=$(curl -s -w "HTTPSTATUS:%{http_code}" \
|
||||
https://gtm.smith.langchain.dev/api-host/v1/projects/${{ env.PROJECT_ID }}/revisions \
|
||||
--request POST \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'X-Api-Key: ${{ secrets.LANGSMITH_API_KEY }}' \
|
||||
--data '{
|
||||
"image_path": "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest",
|
||||
"env_vars": [{"name": "OPENAI_API_KEY", "value": "${{ secrets.OPENAI_API_KEY }}", "type": "secret"}]
|
||||
}')
|
||||
|
||||
# Extract HTTP status and response body
|
||||
http_status=$(echo $response | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
|
||||
response_body=$(echo $response | sed -e 's/HTTPSTATUS\:.*//g')
|
||||
|
||||
# Check if revision update was successful
|
||||
if [ $http_status -eq 200 ] || [ $http_status -eq 201 ]; then
|
||||
echo "✅ Project revision updated successfully!"
|
||||
echo "📋 Response: $response_body"
|
||||
else
|
||||
echo "❌ Project revision update failed with status: $http_status"
|
||||
echo "📋 Response: $response_body"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Success notification
|
||||
run: |
|
||||
echo "🎉 Revision update completed successfully!"
|
||||
echo "📦 Project: ${{ env.PROJECT_ID }}"
|
||||
echo "🚀 Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
||||
@@ -0,0 +1 @@
|
||||
.env
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
FROM langchain/langgraph-api:3.11
|
||||
|
||||
|
||||
|
||||
# -- Installing local requirements --
|
||||
ADD requirements.txt /deps/__outer_control-plane-api-demo/src/requirements.txt
|
||||
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -r /deps/__outer_control-plane-api-demo/src/requirements.txt
|
||||
# -- End of local requirements install --
|
||||
|
||||
# -- Adding non-package dependency control-plane-api-demo --
|
||||
ADD . /deps/__outer_control-plane-api-demo/src
|
||||
RUN set -ex && \
|
||||
for line in '[project]' \
|
||||
'name = "control-plane-api-demo"' \
|
||||
'version = "0.1"' \
|
||||
'[tool.setuptools.package-data]' \
|
||||
'"*" = ["**/*"]'; do \
|
||||
echo "$line" >> /deps/__outer_control-plane-api-demo/pyproject.toml; \
|
||||
done
|
||||
# -- End of non-package dependency control-plane-api-demo --
|
||||
|
||||
# -- Installing all local dependencies --
|
||||
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/*
|
||||
# -- End of local dependencies install --
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_control-plane-api-demo/src/agent.py:graph"}'
|
||||
|
||||
|
||||
|
||||
# -- Ensure user deps didn't inadvertently overwrite langgraph-api
|
||||
RUN mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license && touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py
|
||||
RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir --no-deps -e /api
|
||||
# -- End of ensuring user deps didn't inadvertently overwrite langgraph-api --
|
||||
# -- Removing pip from the final image ~<:===~~~ --
|
||||
RUN pip uninstall -y pip setuptools wheel && rm -rf /usr/local/lib/python*/site-packages/pip* /usr/local/lib/python*/site-packages/setuptools* /usr/local/lib/python*/site-packages/wheel* && find /usr/local/bin -name "pip*" -delete
|
||||
# -- End of pip removal --
|
||||
|
||||
WORKDIR /deps/__outer_control-plane-api-demo/src
|
||||
@@ -0,0 +1,28 @@
|
||||
from typing import Annotated
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
class State(TypedDict):
|
||||
# Messages have the type "list". The `add_messages` function
|
||||
# in the annotation defines how this state key should be updated
|
||||
# (in this case, it appends messages to the list, rather than overwriting them)
|
||||
messages: Annotated[list, add_messages]
|
||||
|
||||
graph_builder = StateGraph(State)
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
llm = ChatOpenAI(model="o3-mini")
|
||||
|
||||
def chatbot(state: State):
|
||||
return {"messages": [llm.invoke(state["messages"])]}
|
||||
|
||||
graph_builder.add_node("chatbot", chatbot)
|
||||
|
||||
graph_builder.add_edge(START, "chatbot")
|
||||
graph_builder.add_edge("chatbot", END)
|
||||
|
||||
graph = graph_builder.compile()
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
langgraph
|
||||
langchain-openai
|
||||
typing_extensions
|
||||
Reference in New Issue
Block a user