Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 7 additions & 2 deletions .claude/skills/adapter-ops/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ LiteLLM requires provider prefixes on model names:

@staticmethod
def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:
adapter_metadata["model"] = NewProviderLLMParameters.validate_model(adapter_metadata)
adapter_metadata["model"] = NewProviderLLMParameters.validate_model(
adapter_metadata
)
return NewProviderLLMParameters(**adapter_metadata).model_dump()

@staticmethod
Expand All @@ -114,6 +116,7 @@ LiteLLM requires provider prefixes on model names:
5. **Test the adapter**:
```python
from unstract.sdk1.adapters.adapterkit import Adapterkit

kit = Adapterkit()
adapters = kit.get_adapters_list()
# Verify new adapter appears
Expand Down Expand Up @@ -142,7 +145,9 @@ LiteLLM requires provider prefixes on model names:

@staticmethod
def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:
adapter_metadata["model"] = NewProviderEmbeddingParameters.validate_model(adapter_metadata)
adapter_metadata["model"] = NewProviderEmbeddingParameters.validate_model(
adapter_metadata
)
return NewProviderEmbeddingParameters(**adapter_metadata).model_dump()

@staticmethod
Expand Down
25 changes: 15 additions & 10 deletions .claude/skills/adapter-ops/references/adapter_patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:

# Exclude control fields before validation
validation_metadata = {
k: v for k, v in adapter_metadata.items()
k: v
for k, v in adapter_metadata.items()
if k not in ("enable_reasoning", "reasoning_effort")
}

Expand Down Expand Up @@ -327,8 +328,7 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:

# Check if thinking was previously enabled
has_thinking_config = (
"thinking" in adapter_metadata
and adapter_metadata.get("thinking") is not None
"thinking" in adapter_metadata and adapter_metadata.get("thinking") is not None
)
if not enable_thinking and has_thinking_config:
enable_thinking = True
Expand All @@ -348,7 +348,8 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:

# Exclude control fields from validation
validation_metadata = {
k: v for k, v in result_metadata.items()
k: v
for k, v in result_metadata.items()
if k not in ("enable_thinking", "budget_tokens", "thinking")
}

Expand Down Expand Up @@ -570,10 +571,12 @@ adapter_class = kit.get_adapter_class_by_adapter_id(
)

# Validate metadata
validated = adapter_class.validate({
"model": "my-model",
"api_key": "sk-xxx",
})
validated = adapter_class.validate(
{
"model": "my-model",
"api_key": "sk-xxx",
}
)
print(validated)

# Get JSON schema
Expand Down Expand Up @@ -645,9 +648,11 @@ unconditionally prepends `custom_openai/`, and **no** LiteLLM cost-map key uses
class MiniMaxLLMParameters(OpenAICompatibleLLMParameters):
api_base: str = "https://api.minimax.io/v1"


# CORRECT - emits "minimax/MiniMax-M3", priced at $0.30 / $1.20 per 1M tokens
class MiniMaxLLMParameters(BaseChatCompletionParameters):
... # follow OpenRouterLLMParameters
class MiniMaxLLMParameters(
BaseChatCompletionParameters
): ... # follow OpenRouterLLMParameters
```

Note that `get_provider()` returns `"minimax"` in *both* cases and matches `litellm_provider`
Expand Down
45 changes: 26 additions & 19 deletions .claude/skills/connector-ops/references/connector_patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class GoogleDriveFS(UnstractFileSystem):
if self._client is None: # Double-check
# Import heavy libraries here, not at module level
from google.oauth2.credentials import Credentials

self._client = self._create_client()
return self._client
```
Expand Down Expand Up @@ -77,6 +78,7 @@ def __init__(self, settings: dict[str, Any]):
self.ssl_key = settings.get("sslKey", "")
self.ssl_ca = settings.get("sslCA", "")


def get_engine(self):
conn_params = {...}

Expand Down Expand Up @@ -128,14 +130,10 @@ def test_credentials(self) -> bool:
conn.close()
return True
except AuthenticationError as e:
raise ConnectorError(
f"Authentication failed: {e}",
treat_as_user_message=True
)
raise ConnectorError(f"Authentication failed: {e}", treat_as_user_message=True)
except ConnectionRefusedError as e:
raise ConnectorError(
f"Connection refused - check host and port: {e}",
treat_as_user_message=True
f"Connection refused - check host and port: {e}", treat_as_user_message=True
)
except Exception as e:
raise ConnectorError(f"Connection error: {e}")
Expand Down Expand Up @@ -186,9 +184,11 @@ def sql_to_db_mapping(self, value: Any, column_name: str | None = None) -> str:
```python
from fsspec import AbstractFileSystem


class MyStorageFS(UnstractFileSystem):
def get_fsspec_fs(self) -> AbstractFileSystem:
from myfs import MyFileSystem

return MyFileSystem(
key=self.access_key,
secret=self.secret_key,
Expand All @@ -202,7 +202,10 @@ class MyStorageFS(UnstractFileSystem):

def is_dir_by_metadata(self, metadata: dict[str, Any]) -> bool:
"""Check if path is directory from metadata."""
return metadata.get("type") == "directory" or metadata.get("StorageClass") == "DIRECTORY"
return (
metadata.get("type") == "directory"
or metadata.get("StorageClass") == "DIRECTORY"
)
```

---
Expand All @@ -220,8 +223,7 @@ SharePoint returned plain lists instead of dicts, breaking file processing silen
**Bad** — reimplementing methods the base class already provides:
```python
class MyFileSystem(AbstractFileSystem):
def ls(self, path, detail=True, **kwargs):
... # Core method - MUST implement
def ls(self, path, detail=True, **kwargs): ... # Core method - MUST implement

# BAD: These are all redundant reimplementations
def listdir(self, path, detail=True, **kwargs):
Expand All @@ -240,25 +242,27 @@ class MyFileSystem(AbstractFileSystem):
def isdir(self, path):
return self.info(path)["type"] == "directory" # Base already does this

def walk(self, path, maxdepth=None, **kwargs):
... # Base already delegates to ls() with full detail/on_error support
def walk(
self, path, maxdepth=None, **kwargs
): ... # Base already delegates to ls() with full detail/on_error support

def delete(self, path, **kwargs):
self.rm(path, **kwargs) # Base already does this

def read_bytes(self, path):
return self.cat_file(path) # Base already does this

def write_bytes(self, path, data, **kwargs):
... # Base already delegates to pipe_file()
def write_bytes(
self, path, data, **kwargs
): ... # Base already delegates to pipe_file()
```

**Good** — implement only the core methods, let fsspec handle the rest:
```python
class MyFileSystem(AbstractFileSystem):
# REQUIRED: Core methods that talk to the service API
def ls(self, path, detail=True, **kwargs): ...
def info(self, path, **kwargs): ... # Optional optimization
def info(self, path, **kwargs): ... # Optional optimization
def _open(self, path, mode="rb", **kwargs): ...
def cat_file(self, path, **kwargs): ...
def pipe_file(self, path, value, **kwargs): ... # NOT write_bytes
Expand Down Expand Up @@ -291,19 +295,21 @@ lists when False) and the `on_error` callback.
from google.cloud import bigquery
from google.oauth2 import service_account

class BigQueryDB(UnstractDB):
...

class BigQueryDB(UnstractDB): ...
```

**Good**:
```python
# No heavy imports at module level


class BigQueryDB(UnstractDB):
def get_engine(self):
# Import when needed
from google.cloud import bigquery
from google.oauth2 import service_account

...
```

Expand Down Expand Up @@ -341,6 +347,7 @@ def __init__(self, settings: dict[str, Any]):
def get_id() -> str:
return "postgres|abc123"


# Version 2.0 - DON'T DO THIS
@staticmethod
def get_id() -> str:
Expand All @@ -362,9 +369,9 @@ def get_engine(self):
conn_params = {
# TCP keepalive settings
"keepalives": 1,
"keepalives_idle": 30, # Seconds before sending keepalive
"keepalives_idle": 30, # Seconds before sending keepalive
"keepalives_interval": 10, # Seconds between keepalives
"keepalives_count": 5, # Failed keepalives before disconnect
"keepalives_count": 5, # Failed keepalives before disconnect
"connect_timeout": 30,
# ...
}
Expand Down Expand Up @@ -441,7 +448,7 @@ def execute_batch(self, query: str, data: list[tuple], batch_size: int = 1000) -
try:
with engine.cursor() as cursor:
for i in range(0, len(data), batch_size):
batch = data[i:i + batch_size]
batch = data[i : i + batch_size]
cursor.executemany(query, batch)
total_rows += len(batch)
engine.commit()
Expand Down
3 changes: 3 additions & 0 deletions .claude/skills/connector-ops/references/test_patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,16 +501,19 @@ self.assertRegex(connector.get_id(), r"^[a-z_]+\|[a-f0-9-]+$")

# JSON schema validity
import json

schema = json.loads(connector.get_json_schema())
self.assertIn("title", schema)
self.assertIn("type", schema)

# Connection returns correct type
from psycopg2.extensions import connection

self.assertIsInstance(connector.get_engine(), connection)

# Exception handling
from unstract.connectors.exceptions import ConnectorError

with self.assertRaises(ConnectorError) as ctx:
connector.test_credentials()
self.assertIn("expected message", str(ctx.exception))
Expand Down
12 changes: 6 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ci:
- hadolint-docker # Fails in pre-commit CI
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
exclude_types:
Expand Down Expand Up @@ -38,7 +38,7 @@ repos:
- id: no-commit-to-branch

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
rev: v0.16.8
hooks:
- id: ruff
args: [--fix]
Expand All @@ -57,7 +57,7 @@ repos:
# language: python

- repo: https://github.com/asottile/pyupgrade
rev: v3.17.0
rev: v3.21.2
hooks:
- id: pyupgrade
entry: pyupgrade --py39-plus --keep-runtime-typing
Expand Down Expand Up @@ -111,15 +111,15 @@ repos:
stages: [pre-commit]

- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.42.0
rev: v0.49.1
hooks:
- id: markdownlint
args: [--disable, MD013]
- id: markdownlint-fix
args: [--disable, MD013]

- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.2
rev: v8.30.0
hooks:
- id: gitleaks

Expand All @@ -129,7 +129,7 @@ repos:
- id: htmlhint

- repo: https://github.com/hadolint/hadolint
rev: v2.12.1-beta
rev: v2.15.1
hooks:
- id: hadolint-docker
args:
Expand Down
4 changes: 2 additions & 2 deletions backend/account_v2/custom_auth_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import logging
import uuid

from backend.constants import RequestHeader
from backend.internal_api_constants import INTERNAL_API_PREFIX
from django.conf import settings
from django.http import HttpRequest, HttpResponse, JsonResponse
from utils.constants import Account
Expand All @@ -11,8 +13,6 @@
from account_v2.authentication_plugin_registry import AuthenticationPluginRegistry
from account_v2.authentication_service import AuthenticationService
from account_v2.constants import Common
from backend.constants import RequestHeader
from backend.internal_api_constants import INTERNAL_API_PREFIX

logger = logging.getLogger(__name__)

Expand Down
3 changes: 1 addition & 2 deletions backend/account_v2/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import uuid

from backend.constants import FieldLengthConstants as FieldLength
from django.contrib.auth.models import AbstractUser, Group, Permission
from django.db import models

from backend.constants import FieldLengthConstants as FieldLength

NAME_SIZE = 64
KEY_SIZE = 64

Expand Down
14 changes: 7 additions & 7 deletions backend/adapter_processor_v2/adapter_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
from django.core.exceptions import ObjectDoesNotExist
from platform_settings_v2.platform_auth_service import PlatformAuthenticationService
from tenant_account_v2.organization_member_service import OrganizationMemberService
from unstract.sdk1.adapters.adapterkit import Adapterkit
from unstract.sdk1.adapters.base import Adapter
from unstract.sdk1.adapters.x2text.constants import X2TextConstants
from unstract.sdk1.constants import AdapterTypes
from unstract.sdk1.embedding import EmbeddingCompat
from unstract.sdk1.exceptions import SdkError
from unstract.sdk1.llm import LLM

from adapter_processor_v2.constants import AdapterKeys, AllowedDomains
from adapter_processor_v2.exceptions import (
Expand All @@ -16,13 +23,6 @@
InValidAdapterId,
TestAdapterError,
)
from unstract.sdk1.adapters.adapterkit import Adapterkit
from unstract.sdk1.adapters.base import Adapter
from unstract.sdk1.adapters.x2text.constants import X2TextConstants
from unstract.sdk1.constants import AdapterTypes
from unstract.sdk1.embedding import EmbeddingCompat
from unstract.sdk1.exceptions import SdkError
from unstract.sdk1.llm import LLM

from .models import AdapterInstance, UserDefaultAdapter

Expand Down
2 changes: 1 addition & 1 deletion backend/adapter_processor_v2/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging

from rest_framework.exceptions import APIException
from unstract.sdk1.exceptions import SdkError

from adapter_processor_v2.constants import AdapterKeys
from unstract.sdk1.exceptions import SdkError

logger = logging.getLogger(__name__)

Expand Down
Loading
Loading