Use the API with LangChain
Point LangChain's ChatOpenAI at api.ismscopilot.com/v1, pin framework modules with extra_body, and keep tool-using agents on another model.
LangChain's ChatOpenAI talks to any endpoint that speaks the OpenAI chat completions API. Give it the ISMS Copilot base URL and an sk-isms- key, and your chains get compliance answers with the framework modules you pin.
No tool calling. The API is a text-only subset: it rejects tools with a 400 (see Errors). Do not call bind_tools(), with_structured_output() or create_agent() / AgentExecutor with tools on this model. Keep a general model for the agent loop and use ISMS Copilot as a plain chat step, or wrap a chain that calls it as one of your agent's tools.
Install
pip install langchain-openai
export ISMS_API_KEY=sk-isms-...First call with a pinned module
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="isms-fast",
base_url="https://api.ismscopilot.com/v1",
api_key=os.environ["ISMS_API_KEY"],
extra_body={"ismscopilot": {"frameworks": ["NIS_2_NL"]}},
)
answer = llm.invoke("We are a managed IT service provider in the Netherlands. Which NIS 2 incident reports do we owe, and to whom?")
print(answer.content)extra_body adds the ismscopilot extension to every request this model sends. Pinned modules are injected regardless of what the message mentions. Without extra_body, the API uses auto: it detects named frameworks in the user messages and the last assistant turn, not in system messages. Details: Framework knowledge.
To change the pin per call, bind it:
it_llm = llm.bind(extra_body={"ismscopilot": {"frameworks": ["NIS_2_IT", "GDPR"]}})In a chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "You review change requests for a compliance team. Answer in five bullet points."),
("user", "{change}"),
])
chain = prompt | llm | StrOutputParser()
print(chain.invoke({"change": "We are moving production logs to a new SaaS provider outside the EU."}))Your system message is kept and labelled. To drop the ISMS Copilot persona and keep only yours, add "system": "none" to the extension: extra_body={"ismscopilot": {"system": "none", "frameworks": ["GDPR"]}}. See The system prompt, in full.
As a step inside a tool-using agent
The pattern that works: your agent runs on a model with tool calling, and one of its tools calls ISMS Copilot.
from langchain_core.tools import tool
compliance_llm = ChatOpenAI(
model="isms-thinking",
base_url="https://api.ismscopilot.com/v1",
api_key=os.environ["ISMS_API_KEY"],
extra_body={"ismscopilot": {"frameworks": ["ISO_27001"]}},
)
@tool
def ask_iso27001(question: str) -> str:
"""Answer an ISO 27001:2022 question, citing Annex A control ids."""
return compliance_llm.invoke(question).contentGive ask_iso27001 to your agent like any other tool. The agent's own model sends the tool definitions; ISMS Copilot only ever receives plain text.
Reading what ran
The response names the injected modules in the x-isms-frameworks header and the ismscopilot body object. ChatOpenAI does not surface custom headers on the message. When you need the disclosure for your audit trail, call the OpenAI SDK directly with with_raw_response (example in Serve customers in many countries).
Limits
- No tool calling, no JSON mode (
response_formatother than text),nis 1, no logprobs, no images. Streaming works (llm.stream(...)). - Thinking aliases raise
max_tokensbelow 1024 to 1024. Omitmax_tokensunless you need a cap. - Checked with
langchain-openai0.3.35 on 2026-10-28: the request body carriesismscopilotat the top level, andbind_tools()sendstools, which the API rejects.