Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cf95a4c
fix: correct provider identity, reasoning, and streaming before launch
Khurdhula-Harshavardhan Aug 6, 2026
374fc6f
fix: reasoning precedence, streamed side-field duplication, provider id
Abhinavexist Aug 7, 2026
6ace3ec
fix: tomllib test for py 3.10
Abhinavexist Aug 7, 2026
8c27113
fix: reasoning kwarg crash, side-field corruption, role-less delta leak
Abhinavexist Aug 7, 2026
59e84cc
fix: side-field dedupe, usage-frame delivery, QA contract coverage
Khurdhula-Harshavardhan Aug 8, 2026
1e6d62a
fix: normalize delta role and observe raw frames instead of reshaping…
Khurdhula-Harshavardhan Aug 8, 2026
ffde030
fix: recover truncated responses, drop the admin-key option
Khurdhula-Harshavardhan Aug 8, 2026
42ef8dc
fix: stop mangling answers that mention a side-channel tag
Khurdhula-Harshavardhan Aug 8, 2026
0782076
fix: match the interfaze SDK's side-channel contract exactly
Khurdhula-Harshavardhan Aug 8, 2026
8be78c2
fix: preserve every side field the server sends
Khurdhula-Harshavardhan Aug 8, 2026
00a3a7b
refactor: collapse the stream tail to one mechanism
Khurdhula-Harshavardhan Aug 8, 2026
bc23248
fix: header shapes, cache key, and release metadata
Khurdhula-Harshavardhan Aug 8, 2026
5ced99e
fix: restore the header normalization lost to a stray checkout
Khurdhula-Harshavardhan Aug 8, 2026
9fb328a
fix: stop leaking tool metadata, and align the two packages
Khurdhula-Harshavardhan Aug 8, 2026
a4497e0
fix: close the remaining cross-package gaps
Khurdhula-Harshavardhan Aug 8, 2026
1e2cdb3
fix: truncation-aware side channels, beta-stream parity, redacted cac…
Khurdhula-Harshavardhan Aug 8, 2026
6986656
ci: gate publishing on matching versions and passing tests
Khurdhula-Harshavardhan Aug 8, 2026
d55662d
fix(python): declare the readme so the pypi page is not blank
Khurdhula-Harshavardhan Aug 8, 2026
be67088
fix: real v3 event stream, empty-think fallthrough, per-key cache sep…
Khurdhula-Harshavardhan Aug 8, 2026
1a9d3b1
fix: mypy narrowing, empty mime fallthrough, v3 metadata merge; docx …
Khurdhula-Harshavardhan Aug 8, 2026
7ade8af
fix: do not merge the raw metadata the server restates per frame
Khurdhula-Harshavardhan Aug 8, 2026
b4503a2
fix: split truncated side channels at the earliest tag, not the first…
Abhinavexist Aug 10, 2026
e3785bb
docs: raise comment signal, restore the run_manager rationale
Abhinavexist Aug 10, 2026
bf2b84b
refactor: name the stream bookkeeping, drop positional tuple indexing
Abhinavexist Aug 10, 2026
f9e4139
fix(qa): the streaming leak check could not fail, and the router was …
Abhinavexist Aug 10, 2026
3c74fb3
refactor: publish as interfaze/langchain across all three registries
Khurdhula-Harshavardhan Aug 10, 2026
6d95185
fix(js): restore the multi-platform lockfile the rename regenerated
Khurdhula-Harshavardhan Aug 10, 2026
b42dcd7
test: filter the upstream pydantic warning by module, not by wording
Khurdhula-Harshavardhan Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ jobs:
if: matrix.python-version == '3.12'
run: uv run mypy
- name: Unit tests
run: uv run pytest tests/unit_tests/
run: uv run pytest tests/unit_tests/ --disable-socket --allow-unix-socket --cov=interfaze_langchain --cov-report=term-missing --cov-fail-under=95

versions:
name: versions agree
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: node scripts/check-versions.mjs

secret-scan:
name: secret scan (gitleaks)
Expand Down
33 changes: 31 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,32 @@ permissions:
contents: read

jobs:
# Five files carry the version and two of them reach users as a User-Agent. A release
# cut from the wrong commit would otherwise publish the previous version, silently.
verify:
name: verify (versions match the tag · tests pass)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Versions match the release tag
run: node scripts/check-versions.mjs "$GITHUB_REF_NAME"
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.12"
- name: Python tests
working-directory: python
run: uv run --all-groups pytest tests/unit_tests/ --disable-socket --allow-unix-socket
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache-dependency-path: js/package-lock.json
- name: JS tests
working-directory: js
run: npm ci && npm test

build:
needs: verify
runs-on: ubuntu-latest
defaults:
run:
Expand All @@ -33,7 +58,7 @@ jobs:
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/p/langchain-interfaze
url: https://test.pypi.org/p/interfaze-langchain
permissions:
id-token: write
steps:
Expand All @@ -51,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/langchain-interfaze
url: https://pypi.org/p/interfaze-langchain
permissions:
id-token: write
steps:
Expand All @@ -63,7 +88,9 @@ jobs:

npm-publish:
name: Publish to npm
needs: build
runs-on: ubuntu-latest
if: github.event.release.prerelease == false
permissions:
contents: read
id-token: write
Expand All @@ -83,7 +110,9 @@ jobs:

jsr-publish:
name: Publish to JSR
needs: build
runs-on: ubuntu-latest
if: github.event.release.prerelease == false
permissions:
contents: read
id-token: write
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/qa-live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Live QA

on:
workflow_dispatch:
schedule:
- cron: "0 6 * * 1" # weekly, Monday 06:00 UTC

concurrency:
group: live-qa-${{ github.ref }}
cancel-in-progress: true

jobs:
python:
name: live QA (python)
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.repository_owner == 'InterfazeAI'
defaults:
run:
working-directory: python
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v9.0.0
with:
python-version: "3.12"
enable-cache: true
- name: Install
run: uv sync --all-groups
- name: Check the key is configured
env:
INTERFAZE_API_KEY: ${{ secrets.INTERFAZE_API_KEY }}
run: test -n "$INTERFAZE_API_KEY" || { echo "::error::INTERFAZE_API_KEY secret is not set"; exit 1; }
- name: Run live QA
env:
INTERFAZE_API_KEY: ${{ secrets.INTERFAZE_API_KEY }}
run: uv run python scripts/qa_live.py

js:
name: live QA (js)
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.repository_owner == 'InterfazeAI'
defaults:
run:
working-directory: js
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache-dependency-path: js/package-lock.json
- run: npm ci
- name: Check the key is configured
env:
INTERFAZE_API_KEY: ${{ secrets.INTERFAZE_API_KEY }}
run: test -n "$INTERFAZE_API_KEY" || { echo "::error::INTERFAZE_API_KEY secret is not set"; exit 1; }
- name: Run live QA
env:
INTERFAZE_API_KEY: ${{ secrets.INTERFAZE_API_KEY }}
run: npm run qa:live
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.env
.env.*
!.env.example

__pycache__/
*.py[cod]
.venv/
Expand Down
59 changes: 59 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Contributing

Two packages, one repo: [`python/`](./python) (`interfaze-langchain`) and [`js/`](./js) (`@interfaze/langchain`). A change to one usually needs the same change to the other — the two are kept behaviourally identical.

## Setup

```bash
cd python && uv sync --all-groups
cd js && npm ci
```

## Unit tests

Offline — every request is mocked, and CI runs these on every push.

```bash
cd python && uv run pytest tests/unit_tests/
cd js && npm test
```

Python also runs `ruff check .`, `ruff format --check .` and `mypy`; JS runs `npm run typecheck`, `npm run format:check` and `npm run check:pkg`. CI runs the Python suite against 3.10–3.13 and the JS suite against Node 20/22/24, so check the ends of both matrices before pushing anything version-sensitive:

```bash
cd python && uv run --python 3.10 --all-groups pytest tests/unit_tests/
```

## The langchain-tests standard harness

`tests/integration_tests/` is LangChain's own `ChatModelIntegrationTests` conformance suite. It makes real calls, so it is not in CI and needs a key:

```bash
cd python
INTERFAZE_API_KEY=sk_... uv run pytest tests/integration_tests
```

## Live QA

A go/no-go gate against the real API — every modality, the streaming side channels, and the negative contract cases. Not part of PR CI; the `Live QA` workflow runs it weekly and on demand.

```bash
export INTERFAZE_API_KEY=sk_...
export INTERFAZE_BASE_URL=https://api.interfaze.ai/v1 # optional

cd python && uv run python scripts/qa_live.py
cd js && npm run qa:live
```

Run both before cutting a release. They exercise paths the mocked suites cannot: real `<think>` streaming, precontext from live tool runs, and the server-side validation limits the READMEs document.

## Releasing

Five files carry the version and must agree — `python/pyproject.toml`, `python/interfaze_langchain/_version.py`, `js/package.json`, `js/jsr.json`, `js/src/version.ts`. The last two reach users as a `User-Agent`.

```bash
node scripts/check-versions.mjs # do the five agree?
node scripts/check-versions.mjs v1.2.3 # ...and do they match the tag?
```

CI runs the first form on every PR. Publishing runs the second against the release tag and re-runs both test suites before anything is uploaded; a GitHub prerelease goes to TestPyPI only, a full release to PyPI, npm and JSR.
69 changes: 57 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Interfaze LangChain SDK

The official [LangChain](https://www.langchain.com) integration for [Interfaze](https://interfaze.ai), for both **Python** (`langchain-interfaze`) and **TypeScript / JavaScript** (`@interfaze/langchain`).
The official [LangChain](https://www.langchain.com) integration for [Interfaze](https://interfaze.ai), for both **Python** (`interfaze-langchain`) and **TypeScript / JavaScript** (`@interfaze/langchain`).

[Docs](https://interfaze.ai/docs) · [limits](https://interfaze.ai/docs/limits) · [pricing](https://interfaze.ai/pricing) · [dashboard](https://interfaze.ai) · [Python SDK](https://github.com/InterfazeAI/interfaze-python) · [TypeScript / JavaScript SDK](https://github.com/InterfazeAI/interfaze-js)

Expand All @@ -11,7 +11,7 @@ The official [LangChain](https://www.langchain.com) integration for [Interfaze](
Python:

```bash
pip install langchain-interfaze
pip install interfaze-langchain
```

TypeScript / JavaScript:
Expand All @@ -27,7 +27,7 @@ The TS structured-output and tool examples use `zod` for schemas (`npm install z
Python:

```python
from langchain_interfaze import ChatInterfaze
from interfaze_langchain import ChatInterfaze

llm = ChatInterfaze(api_key="sk_...") # or set INTERFAZE_API_KEY and call ChatInterfaze()
```
Expand Down Expand Up @@ -285,7 +285,7 @@ res.response_metadata.reasoning;

## Multimodal Inputs

Images, audio, PDFs, and CSV use standard LangChain content parts, by URL or base64:
Images, audio, PDFs, Word documents (`.docx`), and CSV use standard LangChain content parts, by URL or base64:

Python:

Expand Down Expand Up @@ -347,7 +347,8 @@ await llm.invoke([
]);
```

> A video block accepts `url`, `base64` (with an optional `mime_type`), or `file_id`, plus an optional `extras` `{"filename": …}`.
> A video block accepts `url` or `base64` (with an optional `mime_type`), plus an optional `extras` `{"filename": …}`.
> The container mime type is inferred from the URL extension when you don't pass one. Interfaze has no file store, so `file_id` is not supported.

## Async and batch

Expand Down Expand Up @@ -396,25 +397,69 @@ const chain = ChatPromptTemplate.fromTemplate("Translate to {lang}: {text}").pip
await chain.invoke({ lang: "French", text: "Hello" });
```

## Feeding precontext
## Client options

Pass precomputed tool output to skip Interfaze's internal tool run:
Set router, cache, and streaming behavior once on the client:

Python:

```python
llm = ChatInterfaze(precontext=[{"name": "ocr", "result": {"extracted_text": "..."}}])
llm = ChatInterfaze(
show_additional_info=True, # emit inline <precontext> while streaming
bypass_cache=True, # skip the semantic cache
bypass_moa=True, # skip the mixture-of-architecture router
)
```

TypeScript:

```ts
const llm = new ChatInterfaze({ precontext: [{ name: "ocr", result: { extracted_text: "..." } }] });
const llm = new ChatInterfaze({
showAdditionalInfo: true, // emit inline <precontext> while streaming
bypassCache: true, // skip the semantic cache
bypassMoA: true, // skip the mixture-of-architecture router
});
```

`showAdditionalInfo` / `show_additional_info` is the only way to get `precontext` **while streaming** — non-streaming responses always carry it. `bypass_cache` matters when you need a fresh generation: a cache hit replays the stored answer, which has no `reasoning` attached.

The request timeout defaults to **900 s**, because a single call may run OCR, a web search or a transcription inline. Pass `timeout` to change it.

## Tasks and guardrails

`ChatInterfaze` is a chat model. For the one-shot `tasks.*` helpers ([run_task](https://interfaze.ai/docs/run-tasks)) and `guard` safety codes, use the core `interfaze` client directly ([Python](https://github.com/InterfazeAI/interfaze-python) · [TypeScript / JavaScript](https://github.com/InterfazeAI/interfaze-js)).
Interfaze reads `<task>` and `<guard>` tags from the **first system message**, so both work through a plain LangChain `SystemMessage`:

Python:

```python
from langchain_core.messages import HumanMessage, SystemMessage

llm.invoke([SystemMessage("<task>web_search</task>"), HumanMessage("GLP-1 research paper")])
llm.invoke([SystemMessage("<guard>S1, S2, S3</guard>"), HumanMessage("How to kill a human?")]) # -> "unsafe S1"
```

TypeScript:

```ts
await llm.invoke([new SystemMessage("<task>web_search</task>"), new HumanMessage("GLP-1 research paper")]);
await llm.invoke([new SystemMessage("<guard>S1, S2, S3</guard>"), new HumanMessage("How to kill a human?")]); // -> "unsafe S1"
```

One task at a time, from `ocr`, `object_detection`, `gui_detection`, `web_search`, `scraper`, `translate`, `speech_to_text`, `forecast`, `classification`. A task cannot be combined with a non-empty structured-output schema.

For the one-shot `tasks.*` helpers ([run_task](https://interfaze.ai/docs/run-tasks)), use the core `interfaze` client directly ([Python](https://github.com/InterfazeAI/interfaze-python) · [TypeScript / JavaScript](https://github.com/InterfazeAI/interfaze-js)).

## Server limits

`ChatInterfaze` forwards standard LangChain options, but validates only the subset supported by Interfaze:

| Option | Accepted |
| ------------------------------------- | ------------------------------------------------------------------- |
| `temperature` | `0`–`1` (values above `1` are a `400`) |
| `max_tokens` / `maxTokens` | `1`–`32000` |
| `reasoning_effort` / `reasoningEffort`| `minimal`, `low`, `medium`, `high`, plus `on` / `off` / `auto` |
| `tool_choice` | ignored — the router always picks |
| `stop`, `n`, `seed`, `logprobs` | ignored |

## Errors

Expand Down Expand Up @@ -444,8 +489,8 @@ import { BadRequestError, InterfazeError, RateLimitError } from "interfaze";
| [Precontext](#precontext) | `response_metadata["precontext"]` | `response_metadata.precontext` |
| [Async and batch](#async-and-batch) | `ainvoke` / `astream` / `batch` | `invoke` / `stream` / `batch` |
| [Chains](#chains-lcel) | LCEL (`\|`) | LCEL (`.pipe()`) |
| [Feed precontext](#feeding-precontext) | `ChatInterfaze(precontext=[...])` | `new ChatInterfaze({ precontext })` |
| [Tasks / guardrails](#tasks-and-guardrails) | core `interfaze` client | core `interfaze` client |
| [Client options](#client-options) | `bypass_cache=True`, … | `bypassCache: true`, … |
| [Tasks / guardrails](#tasks-and-guardrails) | `SystemMessage("<task>…</task>")` | `new SystemMessage("<task>…")` |

## License

Expand Down
Loading