diff --git a/src/llmling/config/models.py b/src/llmling/config/models.py index a1a20dd..a5e4eeb 100644 --- a/src/llmling/config/models.py +++ b/src/llmling/config/models.py @@ -62,6 +62,7 @@ class BaseResource(BaseModel): resource_type: str = Field(init=False) description: str = "" + uri: str | None = None # Add this field processors: list[ProcessingStep] = Field( default_factory=list ) # Optional with empty default diff --git a/src/llmling/config/runtime.py b/src/llmling/config/runtime.py index 0ca1aa0..8731ebf 100644 --- a/src/llmling/config/runtime.py +++ b/src/llmling/config/runtime.py @@ -288,6 +288,7 @@ async def resolve_resource_uri(self, uri_or_name: str) -> tuple[str, Resource]: logger.debug("Trying as resource name") resource = self._resource_registry[uri_or_name] loader = self._loader_registry.get_loader(resource) + loader = loader.create(resource, uri_or_name) # Create instance uri = loader.create_uri(name=uri_or_name) except KeyError: pass @@ -298,13 +299,13 @@ async def resolve_resource_uri(self, uri_or_name: str) -> tuple[str, Resource]: if "/" in uri_or_name or "\\" in uri_or_name or "." in uri_or_name: try: logger.debug("Trying as file path") - uri = PathResourceLoader.create_uri(name=uri_or_name) resource = PathResource(path=uri_or_name) + loader = PathResourceLoader.create(resource, uri_or_name) + uri = loader.create_uri(name=uri_or_name) except Exception as exc: # noqa: BLE001 logger.debug("Failed to create file URI: %s", exc) else: return uri, resource - msg = ( f"Could not resolve resource {uri_or_name!r}. Expected resource name or path." ) diff --git a/src/llmling/resources/base.py b/src/llmling/resources/base.py index 5bca440..5f40642 100644 --- a/src/llmling/resources/base.py +++ b/src/llmling/resources/base.py @@ -183,8 +183,7 @@ def get_name_from_uri(cls, uri: str) -> str: else: return normalized - @classmethod - def create_uri(cls, *, name: str) -> str: + def create_uri(self, *, name: str) -> str: """Create a valid URI for this resource type. Args: @@ -196,8 +195,7 @@ def create_uri(cls, *, name: str) -> str: # Remove any existing scheme if "://" in name: name = name.split("://", 1)[1] - - return cls.get_uri_template().format(name=name) + return self.get_uri_template().format(name=name) def __repr__(self) -> str: """Show loader type and context.""" diff --git a/src/llmling/resources/loaders/image.py b/src/llmling/resources/loaders/image.py index fba04bf..5db5541 100644 --- a/src/llmling/resources/loaders/image.py +++ b/src/llmling/resources/loaders/image.py @@ -35,11 +35,10 @@ def get_uri_template(cls) -> str: """Image URIs follow the same pattern as file URIs.""" return "image:///{name}" - @classmethod - def create_uri(cls, *, name: str) -> str: + def create_uri(self, *, name: str) -> str: """Handle image paths properly.""" normalized = name.replace("\\", "/").lstrip("/") - return cls.get_uri_template().format(name=normalized) + return self.get_uri_template().format(name=normalized) @classmethod def get_name_from_uri(cls, uri: str) -> str: diff --git a/src/llmling/resources/loaders/path.py b/src/llmling/resources/loaders/path.py index 52bf738..ca42df8 100644 --- a/src/llmling/resources/loaders/path.py +++ b/src/llmling/resources/loaders/path.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, ClassVar +import upath from upath import UPath from llmling.config.models import PathResource @@ -39,10 +40,16 @@ def get_name_from_uri(cls, uri: str) -> str: msg = f"Invalid URI: {uri}" raise exceptions.LoaderError(msg) from exc - @classmethod - def create_uri(cls, *, name: str) -> str: - """Create a URI from a path.""" + def create_uri(self, *, name: str) -> str: + """Create a URI based on resource path basename or explicit URI.""" try: + if self.context and self.context.resource: + if self.context.resource.uri: + return paths.path_to_uri(self.context.resource.uri) + # Use basename of the configured path + path = upath.UPath(self.context.resource.path) + return paths.path_to_uri(path.name) + # Fallback to name if no context return paths.path_to_uri(name) except ValueError as exc: msg = f"Failed to create URI from {name}" @@ -100,5 +107,5 @@ async def _load_impl( if __name__ == "__main__": - uri = PathResourceLoader.create_uri(name="/path/to/file.txt") + uri = PathResourceLoader().create_uri(name="/path/to/file.txt") print(uri) # file:///path/to/file.txt diff --git a/src/llmling/resources/registry.py b/src/llmling/resources/registry.py index 54eba8f..7e7ed7e 100644 --- a/src/llmling/resources/registry.py +++ b/src/llmling/resources/registry.py @@ -144,6 +144,7 @@ def get_uri(self, name: str) -> str: """Get URI for a resource by name.""" resource = self[name] loader = self.loader_registry.get_loader(resource) + loader = loader.create(resource, name) # Create instance return loader.create_uri(name=name) @logfire.instrument("Loading resource {name}") diff --git a/tests/resources/test_loaders.py b/tests/resources/test_loaders.py index 5350d12..84f6627 100644 --- a/tests/resources/test_loaders.py +++ b/tests/resources/test_loaders.py @@ -222,7 +222,9 @@ def test_uri_creation(uri_template: str, name: str, expected: str) -> None: (TextResourceLoader,), {"get_uri_template": staticmethod(lambda: uri_template)}, ) - assert test_loader.create_uri(name=name) == expected # type: ignore + # Create instance with no context + loader = test_loader(None) + assert loader.create_uri(name=name) == expected # type: ignore def test_create_loaded_resource() -> None: