コンテンツにスキップ

LangChain / LangGraph 連携

このガイドでは、AI アプリケーションやエージェントを構築するために、clinvk を LangChain / LangGraph と連携させる方法を説明します。

概要

clinvk の OpenAI 互換エンドポイント(/openai/v1/*)により、OpenAI API 形式をサポートする LangChain などの AI フレームワークとシームレスに連携できます。

flowchart LR
    A["LangChain app"] --> B["ChatOpenAI"]
    B --> C["clinvk /openai/v1"]
    C --> D["Backend CLI (claude/codex/gemini)"]

    style A fill:#e3f2fd,stroke:#1976d2
    style B fill:#fff3e0,stroke:#f57c00
    style C fill:#ffecb3,stroke:#ffa000
    style D fill:#f3e5f5,stroke:#7b1fa2

OpenAI SDK Compatibility

clinvk は OpenAI Python SDK と互換です。

from openai import OpenAI

# Configure clinvk as the base URL
client = OpenAI(
    base_url="http://localhost:8080/openai/v1",
    api_key="not-needed"  # only required if API keys are enabled
)

# Use any backend by setting the model name
response = client.chat.completions.create(
    model="claude",  # Backend name: claude, codex, or gemini
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain this code snippet."}
    ]
)

print(response.choices[0].message.content)

LangChain Integration

Basic Chat Model

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage

# Initialize with clinvk endpoint
llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="claude",
    api_key="not-needed",
    temperature=0.7
)

# Simple invocation
messages = [
    SystemMessage(content="You are a code review expert."),
    HumanMessage(content="Review this function for bugs.")
]

response = llm.invoke(messages)
print(response.content)

Using with Chains

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# Setup
llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="claude",
    api_key="not-needed"
)

# Create a chain
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a code documentation expert."),
    ("user", "Generate documentation for this code:\n{code}")
])

chain = prompt | llm | StrOutputParser()

# Execute
result = chain.invoke({"code": "def hello(): return 'world'"})
print(result)

Multiple Backends in One Application

from langchain_openai import ChatOpenAI

# Create multiple LLM instances for different backends
claude_llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="claude",
    api_key="not-needed"
)

codex_llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="codex",
    api_key="not-needed"
)

gemini_llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="gemini",
    api_key="not-needed"
)

# Use different backends for different tasks
architecture_review = claude_llm.invoke("Review the architecture...")
code_generation = codex_llm.invoke("Generate a function that...")
data_analysis = gemini_llm.invoke("Analyze this dataset...")

LangGraph Agent Integration

Basic Agent with Tools

from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langgraph.prebuilt import create_react_agent
import httpx

# Define a tool that uses clinvk's parallel execution
@tool
def parallel_code_review(code: str) -> dict:
    """Review code using multiple AI backends in parallel."""
    response = httpx.post(
        "http://localhost:8080/api/v1/parallel",
        json={
            "tasks": [
                {
                    "backend": "claude",
                    "prompt": f"Review architecture and design:\n{code}"
                },
                {
                    "backend": "codex",
                    "prompt": f"Review for performance issues:\n{code}"
                },
                {
                    "backend": "gemini",
                    "prompt": f"Review for security vulnerabilities:\n{code}"
                }
            ]
        },
        timeout=120
    )
    return response.json()

@tool
def chain_documentation(code: str) -> str:
    """Generate documentation through a multi-step pipeline."""
    response = httpx.post(
        "http://localhost:8080/api/v1/chain",
        json={
            "steps": [
                {
                    "name": "analyze",
                    "backend": "claude",
                    "prompt": f"Analyze this code structure:\n{code}"
                },
                {
                    "name": "document",
                    "backend": "codex",
                    "prompt": "Generate API docs based on: {{previous}}"
                }
            ]
        },
        timeout=120
    )
    results = response.json()
    return results["results"][-1]["output"]

# Create the agent
llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="claude",
    api_key="not-needed"
)

agent = create_react_agent(llm, [parallel_code_review, chain_documentation])

# Run the agent
result = agent.invoke({
    "messages": [{"role": "user", "content": "Review this code: def add(a, b): return a + b"}]
})

Custom Agent Graph

from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI

class AgentState(TypedDict):
    code: str
    architecture_review: str
    performance_review: str
    security_review: str
    final_report: str

def review_architecture(state: AgentState) -> AgentState:
    llm = ChatOpenAI(
        base_url="http://localhost:8080/openai/v1",
        model="claude",
        api_key="not-needed"
    )
    result = llm.invoke(f"Review architecture:\n{state['code']}")
    return {"architecture_review": result.content}

def review_performance(state: AgentState) -> AgentState:
    llm = ChatOpenAI(
        base_url="http://localhost:8080/openai/v1",
        model="codex",
        api_key="not-needed"
    )
    result = llm.invoke(f"Review performance:\n{state['code']}")
    return {"performance_review": result.content}

def review_security(state: AgentState) -> AgentState:
    llm = ChatOpenAI(
        base_url="http://localhost:8080/openai/v1",
        model="gemini",
        api_key="not-needed"
    )
    result = llm.invoke(f"Review security:\n{state['code']}")
    return {"security_review": result.content}

def generate_report(state: AgentState) -> AgentState:
    llm = ChatOpenAI(
        base_url="http://localhost:8080/openai/v1",
        model="claude",
        api_key="not-needed"
    )
    prompt = f"""Generate a final report based on these reviews:

Architecture: {state['architecture_review']}
Performance: {state['performance_review']}
Security: {state['security_review']}
"""
    result = llm.invoke(prompt)
    return {"final_report": result.content}

# Build the graph
workflow = StateGraph(AgentState)

workflow.add_node("architecture", review_architecture)
workflow.add_node("performance", review_performance)
workflow.add_node("security", review_security)
workflow.add_node("report", generate_report)

workflow.set_entry_point("architecture")
workflow.add_edge("architecture", "performance")
workflow.add_edge("performance", "security")
workflow.add_edge("security", "report")
workflow.add_edge("report", END)

app = workflow.compile()

# Run
result = app.invoke({"code": "def process(data): return data * 2"})
print(result["final_report"])

Streaming Responses

OpenAI SDK Streaming

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8080/openai/v1",
    api_key="not-needed"
)

# Stream the response
stream = client.chat.completions.create(
    model="claude",
    messages=[{"role": "user", "content": "Write a long explanation..."}],
    stream=True
)

for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

LangChain Streaming

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="claude",
    api_key="not-needed",
    streaming=True
)

for chunk in llm.stream([HumanMessage(content="Explain recursion...")]):
    print(chunk.content, end="", flush=True)

Async Support

import asyncio
from openai import AsyncOpenAI

async def main():
    client = AsyncOpenAI(
        base_url="http://localhost:8080/openai/v1",
        api_key="not-needed"
    )

    # Async completion
    response = await client.chat.completions.create(
        model="claude",
        messages=[{"role": "user", "content": "Hello!"}]
    )
    print(response.choices[0].message.content)

    # Async streaming
    stream = await client.chat.completions.create(
        model="claude",
        messages=[{"role": "user", "content": "Tell me a story..."}],
        stream=True
    )

    async for chunk in stream:
        if chunk.choices[0].delta.content:
            print(chunk.choices[0].delta.content, end="")

asyncio.run(main())

Error Handling

from openai import OpenAI, APIError, APIConnectionError

client = OpenAI(
    base_url="http://localhost:8080/openai/v1",
    api_key="not-needed"
)

try:
    response = client.chat.completions.create(
        model="claude",
        messages=[{"role": "user", "content": "Hello"}]
    )
except APIConnectionError:
    print("Could not connect to clinvk server. Is it running?")
except APIError as e:
    print(f"API error: {e.message}")

Best Practices

1. Connection Pooling

import httpx

# Reuse client for multiple requests
client = httpx.Client(
    base_url="http://localhost:8080",
    timeout=120
)

# Use in your application
response = client.post("/api/v1/prompt", json={...})

2. Timeout Configuration

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    base_url="http://localhost:8080/openai/v1",
    model="claude",
    api_key="not-needed",
    request_timeout=120  # 2 minutes for long tasks
)

3. Backend Selection Strategy

タスク 推奨バックエンド 理由
複雑な推論 claude 分析能力が強い
コード生成 codex コード向けに最適化
データ分析 gemini 構造化データに強い
一般的なタスク claude 汎用的なデフォルトとして有用

Next Steps