Deploying Any LangChain as a ChatGPT Plugin

April 9, 2023

|repo-review

by Florian Narr

Deploying Any LangChain as a ChatGPT Plugin

langchain-aiplugin wraps any LangChain chain or agent into a FastAPI server that exposes the ChatGPT plugin manifest. Point it at a folder containing two Python files and you get a fully compliant AI plugin.

Why I starred it

April 2023. OpenAI had just opened the ChatGPT plugin waitlist and every team was scrambling to figure out what the plugin protocol actually meant for existing LLM tooling. The obvious friction: LangChain had a rich ecosystem of chains and agents, but none of it talked the plugin JSON manifest format.

This repo is the bridge — or really, it's a tiny runtime that converts the plugin contract into LangChain's Chain interface. What caught my eye was how it handled the plugin discovery mechanism: the generate_plugin_docs() function in app/main.py dynamically generates the /.well-known/ai-plugin.json manifest and the matching OpenAPI YAML from FastAPI's own schema introspection. No handwritten manifests.

How it works

The entry point is app/main.py. On startup it does two things with Python's importlib.machinery.SourceFileLoader: it loads constants.py and chain.py from whatever directory LANGCHAIN_DIRECTORY_PATH points to at runtime.

loader = SourceFileLoader(
    "_langchain_constants", str(Path(DIRECTORY_PATH) / "constants.py")
)
spec = importlib.util.spec_from_loader("_langchain_constants", loader)
CONSTANTS = importlib.util.module_from_spec(spec)
spec.loader.exec_module(CONSTANTS)

This is a plugin pattern: the app binary stays fixed, the chain implementation ships separately as files on disk. You swap chains without redeploying the server — just change the env var.

The plugin manifest is assembled entirely from constants.py values and the FastAPI schema. generate_plugin_docs() calls app.openapi() to get the current OpenAPI spec, writes it to .well-known/openapi.yaml, and dumps the assembled manifest JSON to .well-known/ai-plugin.json. Then the static files directory gets mounted at /.well-known — which is exactly where ChatGPT looks for it.

The actual endpoint is a single POST route, dynamically named from CONSTANTS.ENDPOINT_NAME, accepting a {"message": "..."} body and returning {"response": "..."}. The chain call in app/api.py tries acall() first (async), falls back to sync chain(chain_input) if NotImplementedError is raised — a pragmatic workaround for LangChain integrations that hadn't implemented async yet.

The two included examples make the plugin–chain interface concrete. retrieval_qa/chain.py loads a pickled FAISS vectorstore and returns a RetrievalQA chain:

def get_chain():
    with open(DIR_PATH / "vectorstore.pkl", "rb") as f:
        vectorstore = pickle.load(f)
    return RetrievalQA.from_chain_type(
        llm=OpenAI(temperature=0),
        chain_type="stuff",
        retriever=vectorstore.as_retriever(),
    )

agent/chain.py wraps a zero-shot ReAct agent with a calculator tool. Both demonstrate that the plugin server is chain-agnostic — anything implementing Chain fits.

Using it

git clone git@github.com:langchain-ai/langchain-aiplugin.git
cd langchain-aiplugin
poetry install

export LANGCHAIN_DIRECTORY_PATH=retrieval_qa
export OPENAI_API_KEY=sk-...
export BEARER_TOKEN=local-dev-token

poetry run app --port 8080

That starts the FastAPI server, generates .well-known/ai-plugin.json and .well-known/openapi.yaml in the project root, and begins serving. Hit http://0.0.0.0:8080/docs to see the auto-generated Swagger UI with your chain's endpoint.

To verify the manifest:

curl http://localhost:8080/.well-known/ai-plugin.json | jq .

You'll get the full plugin descriptor including the OpenAPI URL, auth config, and your chain's descriptions — exactly what ChatGPT needs to discover and call the plugin.

Rough edges

Zero tests. The repo has no test suite — not a single file in a tests/ directory. The pyproject.toml includes black and ruff as dev dependencies but no test runner.

The async fallback in app/main.py is a known tech-debt item — the comment says "In production, this exception block should be removed" but the repo stopped receiving commits after a contact email update. Six total commits in the history, with the last substantive change being the initial examples PR.

The vectorstore for retrieval_qa requires a pre-built vectorstore.pkl file that you have to generate yourself — there's a README in that folder explaining how, but it's easy to miss. First run fails silently with a FileNotFoundError if you forget.

The chain abstraction also doesn't address multi-user state. The comment in app/main.py is honest about it: "This chain doesn't disambiguate between users. When using a chain with memory, it would be important to route requests to user-specific chain." That's a real limitation for anything beyond stateless Q&A.

langchain = "^0.0.126" pins a very early version of the library. The langchain.llms, langchain.chains, and initialize_agent imports in the examples have all been reorganized in langchain 0.1+ — you'd need to update these before running against a current LangChain install.

Bottom line

If you were building something for the April 2023 ChatGPT plugin rush, this was the fastest path from a LangChain chain to a working plugin endpoint. Today it's primarily useful as a reference for how the plugin manifest protocol assembles — the dynamic OpenAPI generation pattern in generate_plugin_docs() is still worth reading.

langchain-ai/langchain-aiplugin on GitHub
langchain-ai/langchain-aiplugin