diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 23e66eb..dba7511 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -71,6 +71,14 @@ "changelog-path": "CHANGELOG.md", "release-type": "simple", "package-name": "file-api" + }, + "execution-engines/langchain-executor": { + "changelog-path": "CHANGELOG.md", + "release-type": "helm", + "package-name": "langchain-executor", + "extra-files": [ + "chart/Chart.yaml" + ] } } } diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index 29f43af..2fefb6b 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -8,5 +8,6 @@ "services/ark-sandbox": "0.1.2", "agents/noah": "0.1.8", "services/file-gateway": "0.1.3", - "services/file-gateway/services/file-api": "0.1.1" + "services/file-gateway/services/file-api": "0.1.1", + "execution-engines/langchain-executor": "0.1.0" } \ No newline at end of file diff --git a/.github/workflows/main-push.yaml b/.github/workflows/main-push.yaml index 1992d96..0d00fcb 100644 --- a/.github/workflows/main-push.yaml +++ b/.github/workflows/main-push.yaml @@ -27,6 +27,7 @@ jobs: - { name: noah, path: agents/noah } - { name: ark-sandbox, path: services/ark-sandbox } - { name: file-api, path: services/file-gateway/services/file-api } + - { name: langchain-executor, path: execution-engines/langchain-executor } uses: ./.github/workflows/_reusable-docker-cicd.yaml with: service-name: ${{ matrix.service.name }} @@ -52,6 +53,8 @@ jobs: - { name: noah, path: agents/noah, namespace: noah, run-deployment-test: false } # disable file-gateway deployment tests for PRs to avoid hitting 5m limit - { name: file-gateway, path: services/file-gateway, namespace: default, run-deployment-test: false } + # disable langchain-executor deployment tests for PRs to avoid hitting 5m limit + - { name: langchain-executor, path: execution-engines/langchain-executor, namespace: default, run-deployment-test: false } uses: ./.github/workflows/_reusable-charts-cicd.yaml with: chart-name: ${{ matrix.chart.name }} @@ -138,6 +141,7 @@ jobs: - { name: noah, path: agents/noah } - { name: ark-sandbox, path: services/ark-sandbox } - { name: file-api, path: services/file-gateway/services/file-api } + - { name: langchain-executor, path: execution-engines/langchain-executor } uses: ./.github/workflows/_reusable-docker-cicd.yaml with: service-name: ${{ matrix.service.name }} @@ -159,6 +163,7 @@ jobs: - { name: ark-sandbox, path: services/ark-sandbox, namespace: default } - { name: noah, path: agents/noah, namespace: noah } - { name: file-gateway, path: services/file-gateway, namespace: default } + - { name: langchain-executor, path: execution-engines/langchain-executor, namespace: default } uses: ./.github/workflows/_reusable-charts-cicd.yaml with: chart-name: ${{ matrix.chart.name }} diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index ebc3c0c..5185d12 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -25,6 +25,7 @@ jobs: - { name: noah, path: agents/noah } - { name: ark-sandbox, path: services/ark-sandbox } - { name: file-api, path: services/file-gateway/services/file-api } + - { name: langchain-executor, path: execution-engines/langchain-executor } uses: ./.github/workflows/_reusable-docker-cicd.yaml with: service-name: ${{ matrix.service.name }} @@ -50,6 +51,8 @@ jobs: - { name: noah, path: agents/noah, namespace: noah, run-deployment-test: false } # disable file-gateway deployment tests for PRs to avoid hitting 5m limit - { name: file-gateway, path: services/file-gateway, namespace: default, run-deployment-test: false } + # disable langchain-executor deployment tests for PRs to avoid hitting 5m limit + - { name: langchain-executor, path: execution-engines/langchain-executor, namespace: default, run-deployment-test: false } uses: ./.github/workflows/_reusable-charts-cicd.yaml with: chart-name: ${{ matrix.chart.name }} diff --git a/README.md b/README.md index 6c19f16..d609bf3 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,14 @@ Services are designed to integrate seamlessly with the [ARK platform](https://gi | [`mcp-inspector`](./services/mcp-inspector) | Developer tool for testing and debugging MCP servers | [Chart](./services/mcp-inspector/chart) | | [`phoenix`](./services/phoenix) | AI/ML observability and evaluation platform with OpenTelemetry integration | [Chart](./services/phoenix/chart) | +## Execution Engines + +Alternative execution engines for running Ark agents with different frameworks. + +| Execution Engine | Description | Chart | +| ------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------ | +| [`langchain-executor`](./execution-engines/langchain-executor) | LangChain-based execution engine with RAG support | [Chart](./execution-engines/langchain-executor/chart) | + ## Agents Pre-built agents that can be deployed to your ARK cluster for various operational tasks. @@ -62,6 +70,9 @@ ark install marketplace/services/langfuse ark install marketplace/services/mcp-inspector ark install marketplace/services/phoenix +# Install execution engines +ark install marketplace/execution-engines/langchain-executor + # Install agents ark install marketplace/agents/noah ``` @@ -130,6 +141,7 @@ This marketplace will include: - Additional observability services - Pre-built agents and agent templates +- Additional execution engines (AutoGen, CrewAI, etc.) - Reusable tools and utilities ## Related Projects diff --git a/docs/content/_meta.js b/docs/content/_meta.js index 4140d9a..55455d8 100644 --- a/docs/content/_meta.js +++ b/docs/content/_meta.js @@ -1,6 +1,7 @@ export default { index: 'Introduction', services: 'Services', + 'execution-engines': 'Execution Engines', agents: 'Agents', contributors: 'Contributors', } diff --git a/docs/content/execution-engines/_meta.js b/docs/content/execution-engines/_meta.js new file mode 100644 index 0000000..71f57c7 --- /dev/null +++ b/docs/content/execution-engines/_meta.js @@ -0,0 +1,3 @@ +export default { + 'langchain-executor': 'LangChain Executor' +} diff --git a/docs/content/execution-engines/langchain-executor.mdx b/docs/content/execution-engines/langchain-executor.mdx new file mode 100644 index 0000000..8cf4a3d --- /dev/null +++ b/docs/content/execution-engines/langchain-executor.mdx @@ -0,0 +1,107 @@ +--- +title: LangChain Executor +description: Reference implementation of a LangChain-based execution engine for Ark agents +--- + +# LangChain Executor + +A reference implementation demonstrating how to build an execution engine for Ark using the LangChain framework. + +## Overview + +This execution engine serves as an example of how to run Ark agents with an alternative framework. It demonstrates: + +- **LangChain Integration**: Using LangChain to process agent queries +- **RAG Support**: Optional retrieval-augmented generation with embeddings +- **Model Compatibility**: Works with Azure OpenAI, OpenAI, and Ollama + +Use this as a starting point for building custom execution engines or for workloads that benefit from LangChain's capabilities. + +## Installation + +**Using ARK CLI (Recommended):** +```bash +ark install marketplace/execution-engines/langchain-executor +``` + +**Using Helm:** +```bash +cd execution-engines/langchain-executor +helm dependency update chart/ +helm install langchain-executor ./chart --create-namespace +``` + +**Using DevSpace:** +```bash +cd execution-engines/langchain-executor +devspace deploy +``` + +### Uninstallation + +**Using Helm:** +```bash +helm uninstall langchain-executor +``` + +**Using DevSpace:** +```bash +cd execution-engines/langchain-executor +devspace purge +``` + +## Usage + +### Basic Agent + +Reference the execution engine in your Agent: + +```yaml +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: langchain-agent +spec: + executionEngine: + name: langchain-executor + modelRef: + name: default + prompt: | + You are a helpful AI assistant. +``` + +### RAG-Enabled Agent + +Enable RAG by adding the `langchain: rag` label: + +```yaml +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: rag-agent + labels: + langchain: rag +spec: + executionEngine: + name: langchain-executor + modelRef: + name: default + prompt: | + You are a code expert. Use the provided context to answer questions. +``` + +### Custom Embeddings Model + +Specify a custom embeddings model: + +```yaml +metadata: + labels: + langchain: rag + langchain-embeddings-model: text-embedding-3-small +``` + +## Resources + +- [README](https://github.com/mckinsey/agents-at-scale-marketplace/tree/main/execution-engines/langchain-executor) - Technical details, configuration options, and development setup +- [Ark Documentation](https://mckinsey.github.io/agents-at-scale-ark/) - Complete platform documentation diff --git a/execution-engines/langchain-executor/.dockerignore b/execution-engines/langchain-executor/.dockerignore new file mode 100644 index 0000000..a06ea90 --- /dev/null +++ b/execution-engines/langchain-executor/.dockerignore @@ -0,0 +1,13 @@ +.venv/ +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +.coverage +htmlcov/ +coverage/ +*.egg-info/ +build/ +dist/ +.DS_Store \ No newline at end of file diff --git a/execution-engines/langchain-executor/.gitignore b/execution-engines/langchain-executor/.gitignore new file mode 100644 index 0000000..0ac1a5f --- /dev/null +++ b/execution-engines/langchain-executor/.gitignore @@ -0,0 +1,169 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ +artifacts/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be added to the global gitignore or merged into this project gitignore. For a PyCharm +# project, it is recommended to exclude the complete .idea/ directory. +# See https://intellij-support.jetbrains.com/hc/en/articles/206544839 +.idea/ + +# UV lock files (optional - can be committed for reproducible builds) +uv.lock + +# Local ark-sdk-python directory (should use the one in services/ark-sdk-python) +ark-sdk-python/ +.wheels/ +build-context/ \ No newline at end of file diff --git a/execution-engines/langchain-executor/CHANGELOG.md b/execution-engines/langchain-executor/CHANGELOG.md new file mode 100644 index 0000000..f5866c1 --- /dev/null +++ b/execution-engines/langchain-executor/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +## [0.1.0](https://github.com/mckinsey/agents-at-scale-marketplace/releases/tag/execution-engines/langchain-executor-v0.1.0) (2025-01-15) + +### Features + +* Initial release of LangChain Executor in marketplace +* LangChain framework integration for agent execution +* Optional RAG (Retrieval-Augmented Generation) support via agent labels +* Compatible with Azure OpenAI, OpenAI, and Ollama models +* Memory persistence support for stateful conversations diff --git a/execution-engines/langchain-executor/Dockerfile b/execution-engines/langchain-executor/Dockerfile new file mode 100644 index 0000000..32c5f79 --- /dev/null +++ b/execution-engines/langchain-executor/Dockerfile @@ -0,0 +1,34 @@ +FROM python:3.12.9-slim + +WORKDIR /app + +# Create non-root user +RUN adduser --system --uid 1001 --home /home/executor executor && \ + apt-get update && \ + apt-get upgrade -y libpam0g libsqlite3-0 zlib1g libgnutls30 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /home/executor/.cache/uv && \ + chown -R executor:nogroup /home/executor + +# Install uv +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Copy project files +COPY pyproject.toml README.md ./ +COPY src/ ./src/ + +# Sync dependencies (uses published ark-sdk from PyPI) +RUN uv sync --frozen --no-install-project || uv sync --no-install-project + +# Set up directories for non-root user +RUN chown -R executor:nogroup /app + +USER executor + +ENV HOME=/home/executor +ENV XDG_CACHE_HOME=/home/executor/.cache + +EXPOSE 8000 + +CMD ["uv", "run", "python", "-m", "langchain_executor"] diff --git a/execution-engines/langchain-executor/Makefile b/execution-engines/langchain-executor/Makefile new file mode 100644 index 0000000..41e8576 --- /dev/null +++ b/execution-engines/langchain-executor/Makefile @@ -0,0 +1,28 @@ +.PHONY: help install dev test lint clean + +help: + @echo "Available commands:" + @echo " make install - Install dependencies" + @echo " make dev - Run in development mode" + @echo " make test - Run tests" + @echo " make lint - Run linter" + @echo " make clean - Clean up generated files" + +install: + uv sync + +dev: + uv run python -m langchain_executor + +test: + @echo "Running tests..." + uv run pytest tests/ -v + +lint: + uv run ruff check src/ + +clean: + find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true + find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true + find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true + find . -type f -name "*.pyc" -delete diff --git a/execution-engines/langchain-executor/README.md b/execution-engines/langchain-executor/README.md new file mode 100644 index 0000000..06ccaee --- /dev/null +++ b/execution-engines/langchain-executor/README.md @@ -0,0 +1,91 @@ +# LangChain Executor + +LangChain-based execution engine for Ark agents with optional RAG support. + +## Quickstart + +```bash +# Install with ARK CLI (recommended) +ark install marketplace/execution-engines/langchain-executor + +# Or deploy with Helm +helm dependency update chart/ +helm install langchain-executor ./chart --create-namespace + +# Or deploy with DevSpace +devspace deploy +``` + +## Features + +- LangChain framework integration +- Optional RAG (Retrieval-Augmented Generation) via agent labels +- Compatible with all Ark Model providers (Azure OpenAI, OpenAI, Ollama) +- Memory persistence for stateful conversations + +## Usage + +Reference the execution engine in your Agent: + +```yaml +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: my-langchain-agent +spec: + executionEngine: + name: langchain-executor + modelRef: + name: default + prompt: | + You are a helpful assistant. +``` + +### Enable RAG + +Add the `langchain: rag` label to enable retrieval-augmented generation: + +```yaml +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: rag-agent + labels: + langchain: rag +spec: + executionEngine: + name: langchain-executor + modelRef: + name: default + prompt: | + You are a code expert. Use the provided context to answer questions. +``` + +### Custom Embeddings Model + +Specify a custom embeddings model via label: + +```yaml +metadata: + labels: + langchain: rag + langchain-embeddings-model: text-embedding-3-small +``` + +## Development + +```bash +make help # Show available commands +make init # Install dependencies +make dev # Run in development mode +make test # Run tests +``` + +## Configuration + +See [chart/values.yaml](./chart/values.yaml) for all Helm configuration options. + +Key settings: +- `replicaCount`: Number of replicas (default: 1) +- `resources`: CPU/memory limits +- `executionEngine.description`: Description shown in Ark diff --git a/execution-engines/langchain-executor/chart/Chart.yaml b/execution-engines/langchain-executor/chart/Chart.yaml new file mode 100644 index 0000000..03ac715 --- /dev/null +++ b/execution-engines/langchain-executor/chart/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: langchain-executor +description: >- + LangChain Executor for Ark - Web server that executes agents using LangChain + with RAG support +type: application +version: 0.1.0 +appVersion: 0.1.0 +keywords: + - langchain + - ai + - agents + - execution-engine + - rag +maintainers: + - name: ARK Marketplace +sources: + - https://github.com/mckinsey/agents-at-scale-marketplace diff --git a/execution-engines/langchain-executor/chart/templates/_helpers.tpl b/execution-engines/langchain-executor/chart/templates/_helpers.tpl new file mode 100644 index 0000000..8080329 --- /dev/null +++ b/execution-engines/langchain-executor/chart/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "langchain-executor.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "langchain-executor.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "langchain-executor.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "langchain-executor.labels" -}} +helm.sh/chart: {{ include "langchain-executor.chart" . }} +{{ include "langchain-executor.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: ark +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "langchain-executor.selectorLabels" -}} +app.kubernetes.io/name: {{ include "langchain-executor.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "langchain-executor.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "langchain-executor.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/execution-engines/langchain-executor/chart/templates/deployment.yaml b/execution-engines/langchain-executor/chart/templates/deployment.yaml new file mode 100644 index 0000000..e803e61 --- /dev/null +++ b/execution-engines/langchain-executor/chart/templates/deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "langchain-executor.fullname" . }} + labels: + {{- include "langchain-executor.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "langchain-executor.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "langchain-executor.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "langchain-executor.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + {{- if .Values.image.pullPolicy }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- end }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + env: + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.healthCheck.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.healthCheck.path }} + port: http + initialDelaySeconds: {{ .Values.healthCheck.initialDelaySeconds }} + periodSeconds: {{ .Values.healthCheck.periodSeconds }} + timeoutSeconds: {{ .Values.healthCheck.timeoutSeconds }} + failureThreshold: {{ .Values.healthCheck.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.path }} + port: http + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/execution-engines/langchain-executor/chart/templates/executionengine.yaml b/execution-engines/langchain-executor/chart/templates/executionengine.yaml new file mode 100644 index 0000000..e7714d6 --- /dev/null +++ b/execution-engines/langchain-executor/chart/templates/executionengine.yaml @@ -0,0 +1,22 @@ +apiVersion: ark.mckinsey.com/v1prealpha1 +kind: ExecutionEngine +metadata: + name: {{ include "langchain-executor.fullname" . }} + labels: + {{- include "langchain-executor.labels" . | nindent 4 }} +spec: + type: "langchain" + address: + valueFrom: + serviceRef: + name: {{ include "langchain-executor.fullname" . }} + {{- if .Values.executionEngine.address.serviceRef.namespace }} + namespace: {{ .Values.executionEngine.address.serviceRef.namespace }} + {{- end }} + {{- if .Values.executionEngine.address.serviceRef.port }} + port: "{{ .Values.executionEngine.address.serviceRef.port }}" + {{- end }} + {{- if .Values.executionEngine.address.serviceRef.path }} + path: {{ .Values.executionEngine.address.serviceRef.path }} + {{- end }} + description: {{ .Values.executionEngine.description | quote }} \ No newline at end of file diff --git a/execution-engines/langchain-executor/chart/templates/service.yaml b/execution-engines/langchain-executor/chart/templates/service.yaml new file mode 100644 index 0000000..28a317c --- /dev/null +++ b/execution-engines/langchain-executor/chart/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "langchain-executor.fullname" . }} + labels: + {{- include "langchain-executor.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + {{- include "langchain-executor.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/execution-engines/langchain-executor/chart/templates/serviceaccount.yaml b/execution-engines/langchain-executor/chart/templates/serviceaccount.yaml new file mode 100644 index 0000000..15db554 --- /dev/null +++ b/execution-engines/langchain-executor/chart/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "langchain-executor.serviceAccountName" . }} + labels: + {{- include "langchain-executor.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: false +{{- end }} \ No newline at end of file diff --git a/execution-engines/langchain-executor/chart/values.yaml b/execution-engines/langchain-executor/chart/values.yaml new file mode 100644 index 0000000..5c7905f --- /dev/null +++ b/execution-engines/langchain-executor/chart/values.yaml @@ -0,0 +1,113 @@ +# Default values for langchain-executor +replicaCount: 1 + +image: + repository: ghcr.io/mckinsey/agents-at-scale-marketplace/langchain-executor + pullPolicy: IfNotPresent + # tag defaults to .Chart.AppVersion if not specified + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +# L2 FIX: Pod security context for hardening +podSecurityContext: + runAsNonRoot: true + runAsUser: 1001 + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault + +# L2 FIX: Container security context for hardening +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + +service: + type: ClusterIP + port: 8000 + targetPort: http + name: http + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: langchain-executor.local + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "500m" + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Environment variables for the container +env: + HOST: "0.0.0.0" + PORT: "8000" + +# Health check configuration +healthCheck: + enabled: true + path: /health + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +# Readiness probe configuration +readinessProbe: + enabled: true + path: /health + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + +# ExecutionEngine CRD configuration +executionEngine: + # Description of the execution engine + description: "LangChain Executor - Web server that executes agents using LangChain with RAG support" + # Address configuration for the execution engine + address: + serviceRef: + # Service name - defaults to the service created by this chart + # name: langchain-executor # This will be set to the fullname template + # Optional: specify namespace if different from chart namespace + namespace: "" + # Optional: specify port name or number (defaults to service port) + port: "" + # Optional: specify path to append to service address + path: "" \ No newline at end of file diff --git a/execution-engines/langchain-executor/devspace.yaml b/execution-engines/langchain-executor/devspace.yaml new file mode 100644 index 0000000..4b6ac70 --- /dev/null +++ b/execution-engines/langchain-executor/devspace.yaml @@ -0,0 +1,57 @@ +# DevSpace configuration for langchain-executor +version: v2beta1 + +images: + langchain-executor: + image: langchain-executor + dockerfile: Dockerfile + context: . + +pipelines: + deploy: |- + build_images --all + create_deployments --all + dev: |- + create_deployments --all + start_dev --all + +deployments: + langchain-executor: + namespace: default + helm: + chart: + path: ./chart + values: + image: + repository: ${runtime.images.langchain-executor.image} + tag: ${runtime.images.langchain-executor.tag} + resources: + limits: + cpu: 1000m + memory: 1Gi + ephemeral-storage: 1Gi + requests: + cpu: 500m + memory: 512Mi + ephemeral-storage: 100Mi + +dev: + langchain-executor: + imageSelector: langchain-executor + devImage: python:3.12-slim + command: + [ + "sh", + "-c", + "cd /app && pip install uv && uv sync && uv run python -m langchain_executor", + ] + namespace: default + logs: + lastLines: 50 + sync: + - path: .:/app + startContainer: true + disableDownload: true + uploadExcludeFile: .dockerignore + ssh: + enabled: true diff --git a/execution-engines/langchain-executor/examples/demo.yaml b/execution-engines/langchain-executor/examples/demo.yaml new file mode 100644 index 0000000..1261cc0 --- /dev/null +++ b/execution-engines/langchain-executor/examples/demo.yaml @@ -0,0 +1,85 @@ +# LangChain Execution Engine Demo +# This demo shows how to use the LangChain execution engine with both regular and RAG agents + +# ExecutionEngine CRD - defines the LangChain execution engine +--- +apiVersion: ark.mckinsey.com/v1prealpha1 +kind: ExecutionEngine +metadata: + name: langchain-executor-engine +spec: + type: external + description: "LangChain execution engine with RAG support" + +# Regular LangChain Agent +--- +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: langchain-executor-demo-agent +spec: + prompt: | + You are a helpful assistant powered by the LangChain Execution Engine! 🚀 + + You process queries through an external LangChain execution engine operator + rather than the built-in OpenAI engine. + + Always be helpful and mention that you're running on the LangChain Execution Engine! + + # Use the LangChain execution engine + executionEngine: + name: langchain-executor-engine + + # Use the default model + modelRef: + name: default + +# RAG-enabled LangChain Agent +--- +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: langchain-executor-demo-rag-agent + labels: + langchain: rag # This enables RAG support + langchain-embeddings-model: azure-openai-embeddings # Specifies which model to use for embeddings +spec: + prompt: | + You are a code-aware assistant powered by the LangChain Execution Engine with RAG! 🔍🚀 + + You have access to a code knowledge base and can answer questions about the codebase. + You process queries through an external LangChain execution engine with RAG capabilities. + + When answering questions, use the retrieved context from the codebase to provide + accurate and detailed responses about the code structure, functions, and implementation. + + # Use the LangChain execution engine + executionEngine: + name: langchain-executor-engine + + # Use the chat model (embeddings model specified in label) + modelRef: + name: default + +# Example queries to test both agents +--- +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Query +metadata: + name: langchain-executor-demo-query +spec: + input: "Hi! Can you tell me about yourself and how the LangChain execution engine works?" + targets: + - type: agent + name: langchain-executor-demo-agent + +--- +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Query +metadata: + name: langchain-executor-demo-rag-query +spec: + input: "What is the main function in the langchain execution engine code?" + targets: + - type: agent + name: langchain-executor-demo-rag-agent \ No newline at end of file diff --git a/execution-engines/langchain-executor/examples/sample-agent.yaml b/execution-engines/langchain-executor/examples/sample-agent.yaml new file mode 100644 index 0000000..588cc19 --- /dev/null +++ b/execution-engines/langchain-executor/examples/sample-agent.yaml @@ -0,0 +1,26 @@ +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: langchain-executor-sample-agent +spec: + prompt: | + You are a helpful AI assistant powered by LangChain. + You can help with various tasks including answering questions, + providing explanations, and having conversations. + Be friendly and informative in your responses. + description: "Sample agent demonstrating LangChain executor usage" + executionEngine: + name: langchain-executor + modelRef: + name: default + tools: [] +--- +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Query +metadata: + name: test-langchain-executor-agent +spec: + input: "Hello! Can you tell me what execution engine you're using and how you work?" + targets: + - type: agent + name: langchain-executor-sample-agent \ No newline at end of file diff --git a/execution-engines/langchain-executor/examples/sample-rag-agent.yaml b/execution-engines/langchain-executor/examples/sample-rag-agent.yaml new file mode 100644 index 0000000..7c5fd07 --- /dev/null +++ b/execution-engines/langchain-executor/examples/sample-rag-agent.yaml @@ -0,0 +1,34 @@ +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Agent +metadata: + name: langchain-executor-rag-agent + labels: + example: langchain-executor + langchain: rag # Enable RAG functionality +spec: + prompt: | + You are a code-aware AI assistant powered by LangChain with RAG capabilities. + You have access to a knowledge base of code files and can answer questions + about code structure, implementation details, and provide guidance based on + the existing codebase. + + Use the provided code context to give accurate and helpful responses. + Be specific and reference actual code when possible. + description: "Sample RAG-enabled agent demonstrating LangChain executor with code understanding" + executionEngine: + name: langchain-executor + modelRef: + name: default + tools: [] +--- +apiVersion: ark.mckinsey.com/v1alpha1 +kind: Query +metadata: + name: test-langchain-executor-rag-agent + labels: + example: langchain-executor +spec: + input: "Can you explain how the LangChain executor processes requests? What are the main components?" + targets: + - type: agent + name: langchain-executor-rag-agent \ No newline at end of file diff --git a/execution-engines/langchain-executor/pyproject.toml b/execution-engines/langchain-executor/pyproject.toml new file mode 100644 index 0000000..c70153e --- /dev/null +++ b/execution-engines/langchain-executor/pyproject.toml @@ -0,0 +1,87 @@ +[project] +name = "langchain-executor" +# Version is managed via Release Please +version = "0.1.0" +description = "LangChain execution engine for Ark agents with RAG support" +authors = [ + {name = "McKinsey Agent Platform Team"} +] +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "fastapi>=0.104.0", + "uvicorn[standard]>=0.24.0", + "pydantic>=2.0.0", + "langchain>=0.1.0", + "langchain-openai>=0.1.0", + "langchain-community>=0.0.20", + "faiss-cpu>=1.7.4", + "tiktoken>=0.5.0", + "ark-sdk>=0.1.0", + "urllib3>=2.6.0", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +[project.urls] +Repository = "https://github.com/mckinsey/agents-at-scale-marketplace" +Documentation = "https://mckinsey.github.io/agents-at-scale-marketplace/execution-engines/langchain-executor/" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project.optional-dependencies] +dev = [ + "pyright>=1.1.402", +] + +[tool.hatch.metadata] +allow-direct-references = true + +[project.scripts] +executor-langchain = "langchain_executor.__main__:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/langchain_executor"] + + +# Black configuration - follow AI developer guide for 120 char line length +[tool.black] +line-length = 120 +target-version = ['py311'] +include = '\.pyi?$' + +# Flake8 configuration - allow 120 character lines per AI developer guide +[tool.flake8] +max-line-length = 120 +extend-ignore = ["E203", "W503"] + +# isort configuration +[tool.isort] +profile = "black" +line_length = 120 +multi_line_output = 3 +include_trailing_comma = true + +# MyPy configuration +[tool.mypy] +python_version = "3.11" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true + +# Pytest configuration +[tool.pytest.ini_options] +pythonpath = [ + "." +] +testpaths = [ + "tests" +] + diff --git a/execution-engines/langchain-executor/src/langchain_executor/__init__.py b/execution-engines/langchain-executor/src/langchain_executor/__init__.py new file mode 100644 index 0000000..06283a8 --- /dev/null +++ b/execution-engines/langchain-executor/src/langchain_executor/__init__.py @@ -0,0 +1,3 @@ +"""LangChain Executor for ark.mckinsey.com Kubernetes operator.""" + +__version__ = "0.1.0" diff --git a/execution-engines/langchain-executor/src/langchain_executor/__main__.py b/execution-engines/langchain-executor/src/langchain_executor/__main__.py new file mode 100644 index 0000000..7ff64fa --- /dev/null +++ b/execution-engines/langchain-executor/src/langchain_executor/__main__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +""" +Main entry point for the langchain executor. + +This module starts the FastAPI web server that listens for ExecutionEngine requests +and processes them using LangChain, returning messages in the expected format. +""" + +import logging +import os + +from .app import app_instance + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def main() -> None: + """Main entry point.""" + # Get host and port from environment variables + host = os.getenv("HOST", "0.0.0.0") + port = int(os.getenv("PORT", "8000")) + + # Start the web server + app_instance.run(host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/execution-engines/langchain-executor/src/langchain_executor/app.py b/execution-engines/langchain-executor/src/langchain_executor/app.py new file mode 100644 index 0000000..5f84aa1 --- /dev/null +++ b/execution-engines/langchain-executor/src/langchain_executor/app.py @@ -0,0 +1,11 @@ +from fastapi import FastAPI +from ark_sdk.executor_app import ExecutorApp +from .executor import LangChainExecutor + +# Create the executor and app +executor = LangChainExecutor() +app_instance = ExecutorApp(executor, "LangChain") + + +def create_app() -> FastAPI: + return app_instance.create_app() \ No newline at end of file diff --git a/execution-engines/langchain-executor/src/langchain_executor/executor.py b/execution-engines/langchain-executor/src/langchain_executor/executor.py new file mode 100644 index 0000000..d4666df --- /dev/null +++ b/execution-engines/langchain-executor/src/langchain_executor/executor.py @@ -0,0 +1,164 @@ +"""LangChain execution logic.""" + +import logging +from typing import List, Optional +from langchain.schema import Document, HumanMessage, AIMessage, SystemMessage +from langchain_community.vectorstores import FAISS +from ark_sdk.executor import BaseExecutor, Message +from .utils import ( + create_chat_client, + create_embeddings_client, + should_use_rag, + index_code_files, + create_vector_store, + build_rag_context, +) + +logger = logging.getLogger(__name__) + + +class LangChainExecutor(BaseExecutor): + """Handles LangChain agent execution with optional RAG support.""" + + def __init__(self): + super().__init__("LangChain") + self.vector_store: Optional[FAISS] = None + self._indexed = False + self.code_directory = "." + self.code_chunks: List[Document] = [] + + async def execute_agent(self, request) -> List: + """Execute agent with LangChain and return response messages.""" + try: + logger.info(f"Executing LangChain query for agent {request.agent.name}") + + # Create LangChain ChatOpenAI client + chat_client = create_chat_client(request.agent.model) + + # Check if this agent should use RAG + use_rag = should_use_rag(request.agent) + + # Get RAG context if enabled + rag_context = None + if use_rag: + logger.info(f"Using RAG for agent: {request.agent.name}") + embeddings_model_name = request.agent.labels.get("langchain-embeddings-model") if request.agent.labels else None + rag_context = await self._get_code_context(request.userInput.content, request.agent.model, embeddings_model_name) + else: + logger.info(f"Standard LangChain execution (no RAG) for agent: {request.agent.name}") + + # Convert message history to LangChain format + langchain_messages = [] + for msg in request.history: + if msg.role == "user": + langchain_messages.append(HumanMessage(content=msg.content)) + elif msg.role == "assistant": + langchain_messages.append(AIMessage(content=msg.content)) + elif msg.role == "system": + langchain_messages.insert(0, SystemMessage(content=msg.content)) + + # Add current user message + if use_rag and rag_context: + # For RAG, include context in the user message + rag_instruction = "Use this code context to answer the user's question accurately!" + user_content = f"🔥 RELEVANT CODE CONTEXT:\n\n{rag_context}\n\n{rag_instruction}\n\nUser: {request.userInput.content}" + else: + user_content = request.userInput.content + + langchain_messages.append(HumanMessage(content=user_content)) + + # If this is the first message, prepend the agent prompt as a system message + if len(request.history) == 0: + resolved_prompt = self._resolve_prompt(request.agent) + langchain_messages.insert(0, SystemMessage(content=resolved_prompt)) + + response = await chat_client.ainvoke(langchain_messages) + + # Handle response content + if hasattr(response, "content"): + result = str(response.content) + else: + result = str(response) + + # Create response messages + response_messages = [] + + if result: + assistant_message = Message( + role="assistant", + content=result, + name=request.agent.name, + ) + response_messages.append(assistant_message) + else: + error_message = Message( + role="assistant", + content="Error: No response generated from LangChain", + name=request.agent.name, + ) + response_messages.append(error_message) + + logger.info(f"LangChain execution completed successfully for agent {request.agent.name}") + return response_messages + + except Exception as e: + logger.error(f"Error in LangChain processing: {str(e)}", exc_info=True) + raise + + async def _get_code_context(self, query_input: str, model_config, embeddings_model_name: Optional[str] = None) -> str: + """Get relevant code context for a query using embeddings and vector search.""" + logger.info(f"Getting code context for query: {query_input}") + + # Index code if not already done + if not self._indexed: + await self._index_code(model_config, embeddings_model_name) + + # Find relevant code sections using vector search + relevant_docs = self._retrieve_relevant_code(query_input, k=5) + + # Create context from relevant code + context = build_rag_context(relevant_docs) + + logger.info(f"Generated code context with {len(relevant_docs)} relevant sections") + return context + + async def _index_code(self, model_config, embeddings_model_name: Optional[str] = None) -> None: + """Index Python files from local code using embeddings.""" + logger.info(f"Indexing Python files with embeddings from {self.code_directory}") + + # Get document chunks + self.code_chunks = index_code_files(self.code_directory) + + if not self.code_chunks: + self._indexed = True + return + + # Create embeddings + try: + embeddings = create_embeddings_client(model_config, embeddings_model_name) + self.vector_store = create_vector_store(self.code_chunks, embeddings) + except Exception as e: + logger.error(f"Failed to create embeddings: {e}") + logger.info("Falling back to simple approach without embeddings") + + self._indexed = True + logger.info("Code indexing completed") + + def _retrieve_relevant_code(self, query: str, k: int = 5) -> List[Document]: + """Retrieve relevant code sections using vector similarity search.""" + if self.vector_store is None: + # Fallback to simple approach if vector store creation failed + if self.code_chunks: + logger.debug(f"Vector store not available, providing all {len(self.code_chunks)} chunks") + return self.code_chunks[:k] # Limit to k chunks + return [] + + try: + # Use vector similarity search + docs = self.vector_store.similarity_search(query, k=k) + logger.debug(f"Found {len(docs)} relevant code sections using vector search") + return docs + except Exception as e: + logger.error(f"Vector search failed: {e}") + return [] + diff --git a/execution-engines/langchain-executor/src/langchain_executor/types.py b/execution-engines/langchain-executor/src/langchain_executor/types.py new file mode 100644 index 0000000..63537ff --- /dev/null +++ b/execution-engines/langchain-executor/src/langchain_executor/types.py @@ -0,0 +1,20 @@ +# Re-export types from ark-sdk for compatibility +from ark_sdk import ( + Parameter, + Model, + AgentConfig, + ToolDefinition, + Message, + ExecutionEngineRequest, + ExecutionEngineResponse, +) + +__all__ = [ + "Parameter", + "Model", + "AgentConfig", + "ToolDefinition", + "Message", + "ExecutionEngineRequest", + "ExecutionEngineResponse", +] \ No newline at end of file diff --git a/execution-engines/langchain-executor/src/langchain_executor/utils.py b/execution-engines/langchain-executor/src/langchain_executor/utils.py new file mode 100644 index 0000000..7fd4c7a --- /dev/null +++ b/execution-engines/langchain-executor/src/langchain_executor/utils.py @@ -0,0 +1,225 @@ +"""Shared utilities for LangChain executor.""" + +import logging +from pathlib import Path +from typing import List, Optional +from langchain.schema import Document +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_community.vectorstores import FAISS +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from pydantic import SecretStr + +logger = logging.getLogger(__name__) + + +def create_chat_client(model) -> ChatOpenAI: + """Create a ChatOpenAI client based on the model configuration.""" + config = model.config + + if model.type == "azure": + azure_config = config.get("azure", {}) + api_key = azure_config.get("apiKey", "") + base_url = azure_config.get("baseUrl", "") + api_version = azure_config.get("apiVersion", "") + properties = azure_config.get("properties", {}) + + if not api_key or not base_url: + raise ValueError("Azure OpenAI requires apiKey and baseUrl") + + # Get properties with defaults + temperature = float(properties.get("temperature", "0.7")) + max_tokens = properties.get("max_tokens") + top_p = properties.get("top_p") + frequency_penalty = properties.get("frequency_penalty") + presence_penalty = properties.get("presence_penalty") + + # Azure OpenAI: construct full deployment URL + full_base_url = f"{base_url.rstrip('/')}/openai/deployments/{model.name}/" + + kwargs = { + "model": model.name, + "api_key": SecretStr(api_key), + "base_url": full_base_url, + "default_query": {"api-version": api_version} if api_version else {}, + "temperature": temperature, + } + + if max_tokens: + kwargs["max_tokens"] = int(max_tokens) + if top_p: + kwargs["top_p"] = float(top_p) + if frequency_penalty: + kwargs["frequency_penalty"] = float(frequency_penalty) + if presence_penalty: + kwargs["presence_penalty"] = float(presence_penalty) + + return ChatOpenAI(**kwargs) + + elif model.type == "openai": + openai_config = config.get("openai", {}) + api_key = openai_config.get("apiKey", "") + base_url = openai_config.get("baseUrl", "") + properties = openai_config.get("properties", {}) + + if not api_key: + raise ValueError("OpenAI requires apiKey") + + # Get properties with defaults + temperature = float(properties.get("temperature", "0.7")) + max_tokens = properties.get("max_tokens") + top_p = properties.get("top_p") + frequency_penalty = properties.get("frequency_penalty") + presence_penalty = properties.get("presence_penalty") + + kwargs = { + "model": model.name, + "api_key": SecretStr(api_key), + "base_url": base_url or None, + "temperature": temperature, + } + + if max_tokens: + kwargs["max_tokens"] = int(max_tokens) + if top_p: + kwargs["top_p"] = float(top_p) + if frequency_penalty: + kwargs["frequency_penalty"] = float(frequency_penalty) + if presence_penalty: + kwargs["presence_penalty"] = float(presence_penalty) + + return ChatOpenAI(**kwargs) + + elif model.type == "bedrock": + bedrock_config = config.get("bedrock", {}) + temperature = bedrock_config.get("temperature") + max_tokens = bedrock_config.get("maxTokens") + + # For Bedrock, we'd need to use a different client + # This is a placeholder - actual Bedrock integration would be different + raise NotImplementedError("Bedrock support not implemented in LangChain executor") + + else: + raise ValueError(f"Unsupported model type: {model.type}") + + +def create_embeddings_client(model, embeddings_model_name: Optional[str] = None) -> OpenAIEmbeddings: + """Create OpenAI embeddings client.""" + config = model.config + model_name = embeddings_model_name or model.name + + if model.type == "azure": + azure_config = config.get("azure", {}) + api_key = azure_config.get("apiKey", "") + base_url = azure_config.get("baseUrl", "") + api_version = azure_config.get("apiVersion", "") + + if not api_key or not base_url: + raise ValueError("Azure OpenAI requires apiKey and baseUrl") + + # Azure OpenAI embeddings + full_base_url = f"{base_url.rstrip('/')}/openai/deployments/{model_name}/" + return OpenAIEmbeddings( + model=model_name, + api_key=SecretStr(api_key), + base_url=full_base_url, + api_version=api_version, + ) + + elif model.type == "openai": + openai_config = config.get("openai", {}) + api_key = openai_config.get("apiKey", "") + base_url = openai_config.get("baseUrl", "") + + if not api_key: + raise ValueError("OpenAI requires apiKey") + + return OpenAIEmbeddings( + model=model_name, + api_key=SecretStr(api_key), + base_url=base_url or None + ) + + else: + raise ValueError(f"Unsupported model type for embeddings: {model.type}") + + +def should_use_rag(agent_config) -> bool: + """Check if the agent should use RAG based on labels.""" + if not hasattr(agent_config, "labels") or not agent_config.labels: + return False + return agent_config.labels.get("langchain") == "rag" + + +def index_code_files(code_directory: str = ".") -> List[Document]: + """Index Python files from local code using text splitting.""" + base_path = Path(code_directory) + logger.info(f"Indexing Python files from {base_path}") + + # Collect Python files from current directory and subdirectories + python_files = [] + for py_file in base_path.rglob("*.py"): + # Skip dependencies and cache directories + if not any(part in py_file.parts for part in ["__pycache__", ".git", "node_modules", "venv", ".env", "site-packages", ".venv"]): + python_files.append(py_file) + + # Read and process files + documents = [] + for file_path in python_files: + try: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + # Create document with metadata + doc = Document( + page_content=content, + metadata={ + "file_path": str(file_path), + "file_name": file_path.name, + "relative_path": str(file_path.relative_to(code_directory)), + }, + ) + documents.append(doc) + + except Exception as e: + logger.warning(f"Failed to read {file_path}: {e}") + + if not documents: + logger.warning("No Python files found to index") + return [] + + # Split documents into chunks + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=1000, + chunk_overlap=200, + separators=["\n\nclass ", "\n\ndef ", "\n\nasync def ", "\n\n", "\n", " "], + ) + + chunks = text_splitter.split_documents(documents) + logger.info(f"Created {len(chunks)} code chunks from {len(documents)} files") + + return chunks + + +def create_vector_store(chunks: List[Document], embeddings: OpenAIEmbeddings) -> Optional[FAISS]: + """Create FAISS vector store from document chunks.""" + try: + vector_store = FAISS.from_documents(chunks, embeddings) + logger.info(f"Created FAISS vector store with {len(chunks)} chunks") + return vector_store + except Exception as e: + logger.error(f"Failed to create FAISS vector store: {e}") + return None + + +def build_rag_context(docs: List[Document]) -> str: + """Build context string from retrieved documents.""" + if not docs: + return "No relevant code context found." + + context_parts = [] + for doc in docs: + file_path = doc.metadata.get("relative_path", "unknown") + content = doc.page_content + context_parts.append(f"## File: {file_path}\n```python\n{content}\n```\n") + + return "\n".join(context_parts) \ No newline at end of file diff --git a/marketplace.json b/marketplace.json index 8cb2849..ea2cf28 100644 --- a/marketplace.json +++ b/marketplace.json @@ -190,6 +190,33 @@ "k8sServicePort": 8080, "k8sDeploymentName": "file-gateway-file-api" } + }, + { + "name": "langchain-executor", + "type": "execution-engine", + "displayName": "LangChain Executor", + "description": "LangChain-based execution engine for Ark agents with RAG support", + "version": "0.1.0", + "author": "ARK Marketplace", + "homepage": "https://github.com/mckinsey/agents-at-scale-marketplace", + "repository": "https://github.com/mckinsey/agents-at-scale-marketplace", + "license": "Apache-2.0", + "tags": ["langchain", "execution-engine", "rag", "ai", "agents"], + "category": "execution-engines", + "icon": "https://example.com/langchain-executor-icon.png", + "documentation": "https://mckinsey.github.io/agents-at-scale-marketplace/execution-engines/langchain-executor/", + "support": { + "url": "https://github.com/mckinsey/agents-at-scale-marketplace/issues" + }, + "ark": { + "chartPath": "oci://ghcr.io/mckinsey/agents-at-scale-marketplace/charts/langchain-executor", + "namespace": "default", + "helmReleaseName": "langchain-executor", + "installArgs": ["--create-namespace"], + "k8sServiceName": "langchain-executor", + "k8sServicePort": 8000, + "k8sDeploymentName": "langchain-executor" + } } ] }