AI agents working ON this repository (Codex, Claude Code, Cursor, ...): the repo meta-index — what foundry is, repo map, commands, and hard-won rules — is in CLAUDE.md. Installable skills:
npx skills add infernet-org/foundry. This file is about pointing agent frameworks AT the served API.
This guide covers how to connect AI agents, coding assistants, and multi-agent frameworks to a Foundry inference server. Foundry exposes a standard OpenAI-compatible API at http://localhost:8080/v1.
- API Reference
- Coding Agents
- Multi-Agent Frameworks
- Chat Interfaces
- Direct API Usage
- Choosing a Model
- Performance Considerations
- Structured Output
- Tool Calling / Function Calling
- Thinking / Reasoning Mode
- Streaming
- Multi-GPU Agent Routing
- Troubleshooting
Foundry serves the full OpenAI Chat Completions API:
| Endpoint | Method | Description |
|---|---|---|
/v1/chat/completions |
POST | Chat completion (streaming and non-streaming) |
/v1/models |
GET | List available models |
/health |
GET | Health check |
/metrics |
GET | Prometheus metrics |
Base URL: http://localhost:8080 (configurable via FOUNDRY_PORT)
No API key is required by default. If your client demands one, any non-empty string works (e.g. sk-local).
OpenCode connects via the @ai-sdk/openai-compatible provider.
Important: Use
@ai-sdk/openai-compatible, not@ai-sdk/openai. The latter crashes on models that emit<think>tokens (see Troubleshooting).
// opencode.json (project root or ~/.config/opencode/opencode.json)
{
"$schema": "https://opencode.ai/config.json",
"model": "foundry/qwen3.6-35b-a3b-nvfp4",
"provider": {
"foundry": {
"npm": "@ai-sdk/openai-compatible",
"name": "Foundry",
"options": {
"baseURL": "http://localhost:8080/v1",
"apiKey": "sk-local"
},
"models": {
"qwen3.6-35b-a3b-nvfp4": {
"name": "Qwen 3.5 9B",
"limit": {
"context": 229376,
"output": 32768
}
}
}
}
}
}The model ID must match what /v1/models returns (check with curl http://localhost:8080/v1/models).
Settings > Models > OpenAI API Base:
Base URL: http://localhost:8080/v1
API Key: sk-local
Model: qwen3.6-35b-a3b-nvfp4
Cursor uses streaming by default. Foundry supports SSE streaming natively. vLLM's continuous batching runs Cursor's background indexing and active chat simultaneously without blocking.
// ~/.continue/config.json
{
"models": [
{
"title": "Foundry Qwen",
"provider": "openai",
"model": "qwen3.6-35b-a3b-nvfp4",
"apiBase": "http://localhost:8080/v1",
"apiKey": "sk-local"
}
]
}aider --openai-api-base http://localhost:8080/v1 \
--openai-api-key sk-local \
--model openai/qwen3.6-35b-a3b-nvfp4Or set environment variables:
export OPENAI_API_BASE=http://localhost:8080/v1
export OPENAI_API_KEY=sk-local
aider --model openai/qwen3.6-35b-a3b-nvfp4Settings > Cline > API Provider: OpenAI Compatible
Base URL: http://localhost:8080/v1
API Key: sk-local
Model ID: qwen3.6-35b-a3b-nvfp4
vLLM's continuous batching makes Foundry particularly suited for multi-agent workflows where multiple agents share one model: up to 8 concurrent sequences at ~1,228 tok/s aggregate on RTX 5090.
import os
os.environ["OPENAI_API_BASE"] = "http://localhost:8080/v1"
os.environ["OPENAI_API_KEY"] = "sk-local"
os.environ["OPENAI_MODEL_NAME"] = "qwen3.6-35b-a3b-nvfp4"
from crewai import Agent, Task, Crew
researcher = Agent(
role="Research Analyst",
goal="Find and analyze information",
backstory="Expert at finding and synthesizing information.",
verbose=True,
)
writer = Agent(
role="Technical Writer",
goal="Write clear technical documentation",
backstory="Expert at turning research into documentation.",
verbose=True,
)
research_task = Task(
description="Research the topic: {topic}",
expected_output="Detailed research notes",
agent=researcher,
)
writing_task = Task(
description="Write documentation based on the research",
expected_output="A well-structured technical document",
agent=writer,
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, writing_task],
verbose=True,
)
result = crew.kickoff(inputs={"topic": "GPU inference optimization"})CrewAI can run 4 agents simultaneously at ~307 tok/s each (RTX 5090, MTP x4 speculative decoding).
from autogen import AssistantAgent, UserProxyAgent
config_list = [
{
"model": "qwen3.6-35b-a3b-nvfp4",
"base_url": "http://localhost:8080/v1",
"api_key": "sk-local",
}
]
assistant = AssistantAgent(
name="assistant",
llm_config={"config_list": config_list},
)
user_proxy = UserProxyAgent(
name="user_proxy",
human_input_mode="NEVER",
code_execution_config={"work_dir": "coding"},
)
user_proxy.initiate_chat(
assistant,
message="Write a Python function to calculate fibonacci numbers efficiently.",
)from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="http://localhost:8080/v1",
api_key="sk-local",
model="qwen3.6-35b-a3b-nvfp4",
streaming=True,
)
response = llm.invoke("Explain quantum computing in simple terms.")
print(response.content)from smolagents import ToolCallingAgent, OpenAIServerModel
model = OpenAIServerModel(
model_id="qwen3.6-35b-a3b-nvfp4",
api_base="http://localhost:8080/v1",
api_key="sk-local",
)
agent = ToolCallingAgent(tools=[], model=model)
agent.run("What is the capital of France?")docker run -d -p 3000:8080 \
-e OPENAI_API_BASE_URL=http://host.docker.internal:8080/v1 \
-e OPENAI_API_KEY=sk-local \
--add-host host.docker.internal:host-gateway \
ghcr.io/open-webui/open-webui:mainOpen WebUI supports multi-user chat with conversation history. Concurrent user sessions are batched by vLLM automatically.
Settings > Model > OpenAI:
API URL: http://localhost:8080
API Key: sk-local
Settings > API > Chat Completion (OpenAI):
API URL: http://localhost:8080
API Key: sk-local
Model: qwen3.6-35b-a3b-nvfp4
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="sk-local",
)
response = client.chat.completions.create(
model="qwen3.6-35b-a3b-nvfp4",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
],
)
print(response.choices[0].message.content)curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.6-35b-a3b-nvfp4",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 256
}'import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://localhost:8080/v1",
apiKey: "sk-local",
});
const response = await client.chat.completions.create({
model: "qwen3.6-35b-a3b-nvfp4",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);Foundry ships one model: qwen3.6-35b-a3b-nvfp4 (MoE, ~3B active). It covers coding agents, multi-agent orchestration, tool calling, and long-context work (224K) in a single deployment; thinking mode (reasoning_content) is available per request for reasoning-heavy tasks.
Single-stream decode latency (time to generate one token):
| Model | Latency per token | Tokens per second |
|---|---|---|
| qwen3.6-35b-a3b-nvfp4 | ~2.6 ms | ~384 tok/s (MTP x4) |
At ~384 tok/s single-stream the typing experience is instant for interactive agents; concurrent agent fleets aggregate to ~1,228 tok/s.
Prefill processes a ~1K-token prompt in ~0.11 s on RTX 5090. Keep system prompts concise to minimize time-to-first-token.
qwen3.6-35b-a3b-nvfp4 (RTX 5090, MTP x4):
- 1 agent: ~384 tok/s
- 4 agents: ~1,228 tok/s aggregate (~307 tok/s each)
vLLM batches up to 8 concurrent sequences (--max-num-seqs); beyond that, requests queue. Consider multi-GPU routing (below) for higher concurrency.
VRAM scales with context usage. The default RTX 5090 profiles are tuned for maximum context:
| Model | Default context | VRAM at idle | VRAM at full context |
|---|---|---|---|
| qwen3.6-35b-a3b-nvfp4 | 224K | 22 GB | ~29.0 GB |
To reduce VRAM usage, lower the context window:
docker run --gpus all -p 8080:8080 \
-v ~/.cache/foundry:/models \
-e FOUNDRY_CTX_LENGTH=32768 \
ghcr.io/infernet-org/foundry/qwen3.6-35b-a3b-nvfp4:latestThe model supports JSON mode for structured outputs:
response = client.chat.completions.create(
model="qwen3.6-35b-a3b-nvfp4",
messages=[{
"role": "user",
"content": "List 3 programming languages with their year of creation. Respond in JSON."
}],
response_format={"type": "json_object"},
)For grammar-constrained generation (guaranteed schema compliance), use the grammar parameter:
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.6-35b-a3b-nvfp4",
"messages": [{"role": "user", "content": "Generate a person record"}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "person",
"strict": true,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}
}
}
}'Qwen3.6 supports tool calling via its chat template (--jinja-style templating is built into vLLM serving).
response = client.chat.completions.create(
model="qwen3.6-35b-a3b-nvfp4",
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
},
"required": ["location"],
},
},
}],
)
# The model will respond with a tool_call if it decides to use the function
tool_calls = response.choices[0].message.tool_calls
if tool_calls:
print(f"Function: {tool_calls[0].function.name}")
print(f"Args: {tool_calls[0].function.arguments}")All models support Jinja chat templates for tool calling. The entrypoint enables --jinja by default.
qwen3.6-35b-a3b-nvfp4 supports a thinking mode: reasoning is returned separately in the reasoning_content field (qwen3 reasoning parser).
The server returns thinking content in the reasoning_content field:
response = client.chat.completions.create(
model="qwen3.6-35b-a3b-nvfp4",
messages=[{"role": "user", "content": "What is 127 * 389?"}],
max_tokens=512,
)
msg = response.choices[0].message
if hasattr(msg, "reasoning_content") and msg.reasoning_content:
print(f"Thinking: {msg.reasoning_content}")
print(f"Answer: {msg.content}")All endpoints support Server-Sent Events (SSE) streaming for real-time token delivery:
stream = client.chat.completions.create(
model="qwen3.6-35b-a3b-nvfp4",
messages=[{"role": "user", "content": "Write a poem about GPUs."}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)The host tuning script (sudo ./scripts/host-setup.sh) configures BBR congestion control and busy polling for minimal streaming latency. Time-to-first-token is typically ~50-200 ms depending on prompt length.
For workloads requiring more concurrency than one GPU provides, run multiple Foundry instances and load-balance across them.
upstream foundry {
server localhost:8080; # GPU 0
server localhost:8081; # GPU 1
}
server {
listen 80;
location /v1/ {
proxy_pass http://foundry;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # Required for SSE streaming
}
}For deterministic routing (each agent always hits the same GPU):
import os
# Route based on agent ID (match --max-num-seqs, default 8)
seqs_per_gpu = 8
gpu_endpoints = [
"http://localhost:8080/v1", # GPU 0
"http://localhost:8081/v1", # GPU 1
]
def get_client(agent_id: int) -> OpenAI:
endpoint = gpu_endpoints[agent_id // seqs_per_gpu]
return OpenAI(base_url=endpoint, api_key="sk-local")For a comprehensive troubleshooting guide, see TROUBLESHOOTING.md.
Quick reference for the most common issues:
If you see no devices with dedicated memory found in the logs, the CUDA backend failed to load. Check:
- NVIDIA driver:
nvidia-smishould work on the host - Container GPU access:
docker run --gpus all nvidia/cuda:12.9.1-base-ubuntu24.04 nvidia-smi - CUDA libraries: The image must contain
libcudart.so.12,libcublas.so.12, andlibcublasLt.so.12in/app/
- Check if all layers are on GPU: look for
offloaded N/N layers to GPUin container logs - Check VRAM:
nvidia-smi-- if VRAM is full, reduce context withFOUNDRY_CTX_LENGTH - Check queue depth:
curl http://localhost:8080/metrics | grep vllm:num_requests
Use @ai-sdk/openai-compatible (not @ai-sdk/openai) and ensure the server has --reasoning-format none. See TROUBLESHOOTING.md for details.
- Container might still be loading the model. Check
docker logs <container>for progress. - First run downloads the model (~22 GB), then startup takes 2-4 minutes (weight load + CUDA graph capture).
- Port conflict: use
-p 8081:8080to map to a different host port.
Reduce the context window:
docker run --gpus all -p 8080:8080 \
-v ~/.cache/foundry:/models \
-e FOUNDRY_CTX_LENGTH=16384 \
ghcr.io/infernet-org/foundry/qwen3.6-35b-a3b-nvfp4:latestThis model requires an NVFP4-capable GPU (Blackwell RTX 50xx or Hopper) with 32 GB+ VRAM; there is no smaller variant in this repo.