LangChain
Use XField models in LangChain and LangGraph with ChatOpenAI or ChatAnthropic, in Python and JavaScript.
LangChain needs no special integration. Point ChatOpenAI or ChatAnthropic at XField and everything built on chat models, including LangGraph agents, works as usual.
Install
pip install langchain langchain-openai langchain-anthropicChatOpenAI
The simplest route. It uses the Chat Completions API at https://xfield.cloud.brutal.ai/api/v1.
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="deepseek-v4.1-flash-1017-uncensored",
base_url="https://xfield.cloud.brutal.ai/api/v1",
api_key=os.environ["XFIELD_API_KEY"],
reasoning_effort="high",
timeout=300,
max_retries=4,
)
reply = llm.invoke("Give me five test cases for a password-reset flow.")
print(reply.content)
for chunk in llm.stream("Summarize OWASP ASVS level 1 in one paragraph."):
print(chunk.content, end="", flush=True)reasoning_effort maps to each model's levels; "none" turns thinking off. ChatOpenAI does not surface the reasoning text from Chat Completions. If you need it, use ChatAnthropic.
ChatAnthropic
Use this when you want thinking blocks in the message, or when the rest of your stack already expects Anthropic-style content.
import os
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(
model="glm-5.3-uncensored",
base_url="https://xfield.cloud.brutal.ai/api",
api_key=os.environ["XFIELD_API_KEY"],
max_tokens=8000,
)
reply = llm.invoke("Rank these log sources by value for detecting lateral movement: DNS, NetFlow, Sysmon.")
for block in reply.content:
if isinstance(block, dict) and block.get("type") == "thinking":
print("[thinking]", block["thinking"][:200])
print(reply.text)The base URL has no /v1; the Anthropic client adds it. When a model thinks, content is a list of blocks, so read the answer through reply.text rather than reply.content.
Tools
Both classes support bind_tools with plain functions, Pydantic models or JSON schemas.
from langchain_core.tools import tool
@tool
def lookup_host(hostname: str) -> dict:
"""Look up a host in the lab inventory."""
return {"hostname": hostname, "os": "Ubuntu 24.04", "owner": "red-team-lab"}
msg = llm.bind_tools([lookup_host]).invoke("What OS is lab-web-01?")
print(msg.tool_calls)Agents with LangGraph
LangChain's agent builder runs on LangGraph and takes any chat model, so a tool-using agent on XField is a few lines:
from langchain.agents import create_agent
agent = create_agent(
model=ChatOpenAI(
model="deepseek-v4.1-flash-1017-uncensored",
base_url="https://xfield.cloud.brutal.ai/api/v1",
api_key=os.environ["XFIELD_API_KEY"],
),
tools=[lookup_host],
system_prompt="You triage hosts in an isolated lab. Be brief.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "Who owns lab-web-01?"}]})
print(result["messages"][-1].content)On older versions, langgraph.prebuilt.create_react_agent does the same job with the same model object. DeepSeek V4.1 Flash is the usual pick for long agent loops; switch to glm-5.3-uncensored for planning-heavy graphs. Keep the system prompt and tool list fixed between turns so prompt caching does its work.
JavaScript
The JS packages take the same settings. In @langchain/openai, the base URL goes under configuration:
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "deepseek-v4.1-flash-1017-uncensored",
apiKey: process.env.XFIELD_API_KEY,
configuration: { baseURL: "https://xfield.cloud.brutal.ai/api/v1" },
});
const reply = await llm.invoke("Name three signs of credential stuffing in auth logs.");
console.log(reply.content);Model ids are exact; see models for the list and limits.