26382a7ac6
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
JetBrains Plugin / Actionlint (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (rust) (push) Waiting to run
JetBrains Plugin / Validation (push) Waiting to run
JetBrains Plugin / Build (push) Waiting to run
JetBrains Plugin / Test (push) Blocked by required conditions
Security Check / Security Scan (push) Waiting to run
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
105 lines
3.4 KiB
Python
105 lines
3.4 KiB
Python
"""Tests for the LangChain compress integration.
|
|
|
|
The content-reattachment logic is unit-tested with a fake message (no LangChain
|
|
needed); the full ``compress_messages`` path runs against a loopback server when
|
|
``langchain-core`` is installed.
|
|
"""
|
|
|
|
import json
|
|
import threading
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
import pytest
|
|
|
|
from lean_ctx.langchain import _reattach_content, compress_messages
|
|
|
|
|
|
class _FakeMessage:
|
|
"""Minimal pydantic-v2-like message: ``content`` plus ``model_copy``."""
|
|
|
|
def __init__(self, content, role="user"):
|
|
self.content = content
|
|
self.role = role
|
|
|
|
def model_copy(self, update):
|
|
clone = _FakeMessage(self.content, self.role)
|
|
for key, value in update.items():
|
|
setattr(clone, key, value)
|
|
return clone
|
|
|
|
def __eq__(self, other):
|
|
return (
|
|
isinstance(other, _FakeMessage)
|
|
and other.content == self.content
|
|
and other.role == self.role
|
|
)
|
|
|
|
|
|
def test_reattach_content_swaps_only_content():
|
|
originals = [_FakeMessage("long original body", "user")]
|
|
out = _reattach_content(originals, [{"role": "user", "content": "short"}])
|
|
assert out[0].content == "short"
|
|
assert out[0].role == "user"
|
|
assert out[0] is not originals[0]
|
|
|
|
|
|
def test_reattach_content_preserves_originals_on_count_mismatch():
|
|
originals = [_FakeMessage("a"), _FakeMessage("b")]
|
|
out = _reattach_content(originals, [{"content": "x"}])
|
|
assert out == originals
|
|
|
|
|
|
def test_reattach_content_keeps_message_without_model_copy():
|
|
class _Plain:
|
|
content = "untouched"
|
|
|
|
originals = [_Plain()]
|
|
out = _reattach_content(originals, [{"content": "new"}])
|
|
assert out[0].content == "untouched"
|
|
|
|
|
|
@pytest.fixture
|
|
def base_url():
|
|
class _Handler(BaseHTTPRequestHandler):
|
|
def do_POST(self): # noqa: N802
|
|
length = int(self.headers.get("Content-Length", 0))
|
|
body = json.loads(self.rfile.read(length).decode("utf-8"))
|
|
out = []
|
|
for message in body["messages"]:
|
|
rewritten = dict(message)
|
|
if isinstance(rewritten.get("content"), str):
|
|
rewritten["content"] = rewritten["content"][:8]
|
|
out.append(rewritten)
|
|
payload = json.dumps({"messages": out, "stats": {}}).encode("utf-8")
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(payload)))
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
|
|
def log_message(self, *args):
|
|
pass
|
|
|
|
httpd = HTTPServer(("127.0.0.1", 0), _Handler)
|
|
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
|
|
thread.start()
|
|
try:
|
|
host, port = httpd.server_address
|
|
yield f"http://{host}:{port}"
|
|
finally:
|
|
httpd.shutdown()
|
|
thread.join(timeout=2)
|
|
|
|
|
|
def test_compress_messages_rewrites_content(base_url):
|
|
pytest.importorskip("langchain_core")
|
|
from langchain_core.messages import HumanMessage, SystemMessage
|
|
|
|
messages = [
|
|
SystemMessage(content="You are a helpful assistant."),
|
|
HumanMessage(content="this is a long message body"),
|
|
]
|
|
out = compress_messages(messages, model="gpt-4o", base_url=base_url)
|
|
assert type(out[1]).__name__ == "HumanMessage"
|
|
assert out[1].content == "this is "
|