Add ability to load (deserialize) objects from other namespaces (#7726)

I have some Prompt subclasses in my project that I'd like to be able to
deserialize in callbacks. Right now `loads()`/`load()` will bail when it
encounters my object, but I know I can trust the objects because they're
in my own projects.

<!-- Thank you for contributing to LangChain!

Replace this comment with:
  - Description: a description of the change, 
  - Issue: the issue # it fixes (if applicable),
  - Dependencies: any dependencies required for this change,
- Tag maintainer: for a quicker response, tag the relevant maintainer
(see below),
- Twitter handle: we announce bigger features on Twitter. If your PR
gets announced and you'd like a mention, we'll gladly shout you out!

If you're adding a new integration, please include:
1. a test for the integration, preferably unit tests that do not rely on
network access,
  2. an example notebook showing its use.

Maintainer responsibilities:
  - General / Misc / if you don't know who to tag: @baskaryan
  - DataLoaders / VectorStores / Retrievers: @rlancemartin, @eyurtsev
  - Models / Prompts: @hwchase17, @baskaryan
  - Memory: @hwchase17
  - Agents / Tools / Toolkits: @hinthornw
  - Tracing / Callbacks: @agola11
  - Async: @agola11

If no one reviews your PR within a few days, feel free to @-mention the
same people again.

See contribution guidelines for more information on how to write/run
tests, lint, etc:
https://github.com/hwchase17/langchain/blob/master/.github/CONTRIBUTING.md
 -->
This commit is contained in:
Alec Flett 2023-07-26 08:59:28 -07:00 committed by GitHub
parent 5c6dcb1960
commit 4da43f77e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,7 +1,7 @@
import importlib import importlib
import json import json
import os import os
from typing import Any, Dict, Optional from typing import Any, Dict, List, Optional
from langchain.load.serializable import Serializable from langchain.load.serializable import Serializable
@ -9,8 +9,16 @@ from langchain.load.serializable import Serializable
class Reviver: class Reviver:
"""Reviver for JSON objects.""" """Reviver for JSON objects."""
def __init__(self, secrets_map: Optional[Dict[str, str]] = None) -> None: def __init__(
self,
secrets_map: Optional[Dict[str, str]] = None,
valid_namespaces: Optional[List[str]] = None,
) -> None:
self.secrets_map = secrets_map or dict() self.secrets_map = secrets_map or dict()
# By default only support langchain, but user can pass in additional namespaces
self.valid_namespaces = (
["langchain", *valid_namespaces] if valid_namespaces else ["langchain"]
)
def __call__(self, value: Dict[str, Any]) -> Any: def __call__(self, value: Dict[str, Any]) -> Any:
if ( if (
@ -43,8 +51,7 @@ class Reviver:
): ):
[*namespace, name] = value["id"] [*namespace, name] = value["id"]
# Currently, we only support langchain imports. if namespace[0] not in self.valid_namespaces:
if namespace[0] != "langchain":
raise ValueError(f"Invalid namespace: {value}") raise ValueError(f"Invalid namespace: {value}")
# The root namespace "langchain" is not a valid identifier. # The root namespace "langchain" is not a valid identifier.
@ -66,14 +73,21 @@ class Reviver:
return value return value
def loads(text: str, *, secrets_map: Optional[Dict[str, str]] = None) -> Any: def loads(
text: str,
*,
secrets_map: Optional[Dict[str, str]] = None,
valid_namespaces: Optional[List[str]] = None,
) -> Any:
"""Load a JSON object from a string. """Load a JSON object from a string.
Args: Args:
text: The string to load. text: The string to load.
secrets_map: A map of secrets to load. secrets_map: A map of secrets to load.
valid_namespaces: A list of additional namespaces (modules)
to allow to be deserialized.
Returns: Returns:
""" """
return json.loads(text, object_hook=Reviver(secrets_map)) return json.loads(text, object_hook=Reviver(secrets_map, valid_namespaces))