diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1c461ef --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.11-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl gosu ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd -u 1000 -m -s /bin/bash agent +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY adapter.py . +COPY __init__.py . + +ENV ADAPTER_MODULE=adapter + +ENTRYPOINT ["molecule-runtime"] diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..9a8354f --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .adapter import LangGraphAdapter + +Adapter = LangGraphAdapter diff --git a/adapter.py b/adapter.py new file mode 100644 index 0000000..7b63b87 --- /dev/null +++ b/adapter.py @@ -0,0 +1,50 @@ +"""LangGraph adapter — Python-based ReAct agent with skills, tools, and plugins.""" + +import os +import logging + +from molecule_runtime.adapters.base import BaseAdapter, AdapterConfig +from a2a.server.agent_execution import AgentExecutor + +logger = logging.getLogger(__name__) + + +class LangGraphAdapter(BaseAdapter): + + @staticmethod + def name() -> str: + return "langgraph" + + @staticmethod + def display_name() -> str: + return "LangGraph" + + @staticmethod + def description() -> str: + return "LangGraph ReAct agent — Python-based with skills, tools, plugins, and peer coordination" + + @staticmethod + def get_config_schema() -> dict: + return { + "model": {"type": "string", "description": "LangChain model string (e.g. openrouter:google/gemini-2.5-flash)"}, + "skills": {"type": "array", "items": {"type": "string"}, "description": "Skill folder names to load"}, + "tools": {"type": "array", "items": {"type": "string"}, "description": "Built-in tools (web_search, filesystem, etc.)"}, + } + + def __init__(self): + self.loaded_skills = [] + self.all_tools = [] + self.system_prompt = None + + async def setup(self, config: AdapterConfig) -> None: + result = await self._common_setup(config) + self.loaded_skills = result.loaded_skills + self.all_tools = result.langchain_tools + self.system_prompt = result.system_prompt + + async def create_executor(self, config: AdapterConfig) -> AgentExecutor: + from agent import create_agent + from a2a_executor import LangGraphA2AExecutor + + agent = create_agent(config.model, self.all_tools, self.system_prompt) + return LangGraphA2AExecutor(agent, heartbeat=config.heartbeat, model=config.model) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..749aa37 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +# Molecule AI workspace runtime — shared infrastructure +molecule-ai-workspace-runtime>=0.1.0 + +# LangGraph adapter specific deps +langchain-core==1.2.26 +langchain-anthropic==1.4.0 +langchain-openai==1.1.12 +langchain-google-genai>=2.1.0 +langgraph==1.1.6 +langfuse==4.0.6 +e2b-code-interpreter==1.0.3