ovos-chromadb-embeddings-plugin 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ovos-chromadb-embeddings-plugin-0.0.0/PKG-INFO +67 -0
- ovos-chromadb-embeddings-plugin-0.0.0/README.md +55 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings/__init__.py +112 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings/version.py +7 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/PKG-INFO +67 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/SOURCES.txt +11 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/dependency_links.txt +1 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/entry_points.txt +3 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/requires.txt +3 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/top_level.txt +1 -0
- ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/zip-safe +1 -0
- ovos-chromadb-embeddings-plugin-0.0.0/setup.cfg +4 -0
- ovos-chromadb-embeddings-plugin-0.0.0/setup.py +65 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ovos-chromadb-embeddings-plugin
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: A chromadb embeddings plugin for OVOS
|
|
5
|
+
Home-page: https://github.com/TigreGotico/ovos-chromadb-embeddings-plugin
|
|
6
|
+
Author: jarbasai
|
|
7
|
+
Author-email: jarbasai@mailfence.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Description: # ChromaEmbeddingsDB Plugin
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
The `ChromaEmbeddingsDB` plugin integrates with the [ChromaDB](https://www.trychroma.com/) database to provide a robust solution for managing and querying embeddings. This plugin extends the abstract `EmbeddingsDB` class, allowing you to store, retrieve, and query embeddings efficiently using ChromaDB’s capabilities.
|
|
14
|
+
|
|
15
|
+
This plugin is meant to be used by other specialized plugins such as:
|
|
16
|
+
- https://github.com/TigreGotico/ovos-face-embeddings-plugin
|
|
17
|
+
- https://github.com/TigreGotico/ovos-voice-embeddings-plugin
|
|
18
|
+
- https://github.com/TigreGotico/ovos-gguf-embeddings-plugin
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Add Embeddings**: Store embeddings with associated keys.
|
|
23
|
+
- **Retrieve Embeddings**: Fetch embeddings by their keys.
|
|
24
|
+
- **Delete Embeddings**: Remove embeddings by their keys.
|
|
25
|
+
- **Query Embeddings**: Find the closest embeddings to a given query, with support for cosine distance.
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
Here is a full example demonstrating the basic usage of `ChromaEmbeddingsDB`.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import numpy as np
|
|
33
|
+
from chroma_embeddings_db import ChromaEmbeddingsDB
|
|
34
|
+
|
|
35
|
+
# Initialize the database
|
|
36
|
+
db = ChromaEmbeddingsDB(path="path_to_chromadb_storage")
|
|
37
|
+
|
|
38
|
+
# Add embeddings
|
|
39
|
+
embedding1 = np.array([0.1, 0.2, 0.3])
|
|
40
|
+
embedding2 = np.array([0.4, 0.5, 0.6])
|
|
41
|
+
db.add_embeddings("user1", embedding1)
|
|
42
|
+
db.add_embeddings("user2", embedding2)
|
|
43
|
+
|
|
44
|
+
# Retrieve and print embeddings
|
|
45
|
+
print(db.get_embedding("user1"))
|
|
46
|
+
print(db.get_embedding("user2"))
|
|
47
|
+
|
|
48
|
+
# Query embeddings
|
|
49
|
+
query_embedding = np.array([0.2, 0.3, 0.4])
|
|
50
|
+
results = db.query(query_embedding, top_k=2)
|
|
51
|
+
print(results)
|
|
52
|
+
|
|
53
|
+
# Delete an embedding
|
|
54
|
+
db.delete_embedding("user1")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> Ensure that the path provided to the `ChromaEmbeddingsDB` constructor is accessible and writable.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Acknowledgements
|
|
61
|
+
|
|
62
|
+
- [ChromaDB](https://www.trychroma.com/) for providing the database backend.
|
|
63
|
+
- [NumPy](https://numpy.org) for numerical operations.
|
|
64
|
+
|
|
65
|
+
Keywords: OVOS openvoiceos plugin chromadb recognition
|
|
66
|
+
Platform: UNKNOWN
|
|
67
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# ChromaEmbeddingsDB Plugin
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `ChromaEmbeddingsDB` plugin integrates with the [ChromaDB](https://www.trychroma.com/) database to provide a robust solution for managing and querying embeddings. This plugin extends the abstract `EmbeddingsDB` class, allowing you to store, retrieve, and query embeddings efficiently using ChromaDB’s capabilities.
|
|
6
|
+
|
|
7
|
+
This plugin is meant to be used by other specialized plugins such as:
|
|
8
|
+
- https://github.com/TigreGotico/ovos-face-embeddings-plugin
|
|
9
|
+
- https://github.com/TigreGotico/ovos-voice-embeddings-plugin
|
|
10
|
+
- https://github.com/TigreGotico/ovos-gguf-embeddings-plugin
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Add Embeddings**: Store embeddings with associated keys.
|
|
15
|
+
- **Retrieve Embeddings**: Fetch embeddings by their keys.
|
|
16
|
+
- **Delete Embeddings**: Remove embeddings by their keys.
|
|
17
|
+
- **Query Embeddings**: Find the closest embeddings to a given query, with support for cosine distance.
|
|
18
|
+
|
|
19
|
+
## Example
|
|
20
|
+
|
|
21
|
+
Here is a full example demonstrating the basic usage of `ChromaEmbeddingsDB`.
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import numpy as np
|
|
25
|
+
from chroma_embeddings_db import ChromaEmbeddingsDB
|
|
26
|
+
|
|
27
|
+
# Initialize the database
|
|
28
|
+
db = ChromaEmbeddingsDB(path="path_to_chromadb_storage")
|
|
29
|
+
|
|
30
|
+
# Add embeddings
|
|
31
|
+
embedding1 = np.array([0.1, 0.2, 0.3])
|
|
32
|
+
embedding2 = np.array([0.4, 0.5, 0.6])
|
|
33
|
+
db.add_embeddings("user1", embedding1)
|
|
34
|
+
db.add_embeddings("user2", embedding2)
|
|
35
|
+
|
|
36
|
+
# Retrieve and print embeddings
|
|
37
|
+
print(db.get_embedding("user1"))
|
|
38
|
+
print(db.get_embedding("user2"))
|
|
39
|
+
|
|
40
|
+
# Query embeddings
|
|
41
|
+
query_embedding = np.array([0.2, 0.3, 0.4])
|
|
42
|
+
results = db.query(query_embedding, top_k=2)
|
|
43
|
+
print(results)
|
|
44
|
+
|
|
45
|
+
# Delete an embedding
|
|
46
|
+
db.delete_embedding("user1")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> Ensure that the path provided to the `ChromaEmbeddingsDB` constructor is accessible and writable.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Acknowledgements
|
|
53
|
+
|
|
54
|
+
- [ChromaDB](https://www.trychroma.com/) for providing the database backend.
|
|
55
|
+
- [NumPy](https://numpy.org) for numerical operations.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import List, Optional, Dict
|
|
2
|
+
|
|
3
|
+
import chromadb
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from ovos_plugin_manager.templates.embeddings import EmbeddingsDB, EmbeddingsTuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ChromaEmbeddingsDB(EmbeddingsDB):
|
|
10
|
+
"""An implementation of EmbeddingsDB using ChromaDB for managing embeddings."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, path: str):
|
|
13
|
+
"""Initialize the ChromaEmbeddingsDB.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
path (str): The path to the ChromaDB storage.
|
|
17
|
+
"""
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.client = chromadb.PersistentClient(path=path)
|
|
20
|
+
self.collection = self.client.get_or_create_collection(
|
|
21
|
+
"embeddings", metadata={"hnsw:space": "cosine"}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def add_embeddings(self, key: str, embedding: np.ndarray, metadata: Optional[Dict[str, any]] = None) -> None:
|
|
25
|
+
"""Add or update embeddings in the database.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
key (str): The unique key for the embedding.
|
|
29
|
+
embedding (np.ndarray): The embedding vector to store.
|
|
30
|
+
metadata (Optional[Dict[str, any]]): Optional metadata associated with the embedding.
|
|
31
|
+
"""
|
|
32
|
+
self.collection.upsert(
|
|
33
|
+
embeddings=[embedding.tolist()],
|
|
34
|
+
ids=[key],
|
|
35
|
+
metadatas=[metadata or {}]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def delete_embedding(self, key: str) -> None:
|
|
39
|
+
"""Delete embeddings from the database.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
key (str): The unique key for the embedding to delete.
|
|
43
|
+
"""
|
|
44
|
+
self.collection.delete(ids=[key])
|
|
45
|
+
|
|
46
|
+
def get_embedding(self, key: str) -> np.ndarray:
|
|
47
|
+
"""Retrieve embeddings from the database.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
key (str): The unique key for the embedding to retrieve.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
np.ndarray: The retrieved embedding vector.
|
|
54
|
+
"""
|
|
55
|
+
result = self.collection.get(ids=[key], include=["embeddings"])
|
|
56
|
+
embedding_list = result['embeddings'][0]
|
|
57
|
+
return np.array(embedding_list)
|
|
58
|
+
|
|
59
|
+
def query(self, embedding: np.ndarray, top_k: int = 5, return_metadata: bool = False) -> List[EmbeddingsTuple]:
|
|
60
|
+
"""Query the database for the closest embeddings to the given query embedding.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
embedding (np.ndarray): The embedding vector to query.
|
|
64
|
+
top_k (int, optional): The number of top results to return. Defaults to 5.
|
|
65
|
+
return_metadata (bool, optional): Whether to include metadata in the results. Defaults to False.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List[EmbeddingsTuple]: A list of tuples containing the keys, distances, and optionally metadata.
|
|
69
|
+
"""
|
|
70
|
+
embedding_list = embedding.tolist() if isinstance(embedding, np.ndarray) else embedding
|
|
71
|
+
results = self.collection.query(
|
|
72
|
+
query_embeddings=[embedding_list],
|
|
73
|
+
n_results=top_k
|
|
74
|
+
)
|
|
75
|
+
ids = results["ids"][0]
|
|
76
|
+
distances = results["distances"][0]
|
|
77
|
+
metadatas = results["metadatas"][0]
|
|
78
|
+
|
|
79
|
+
if return_metadata:
|
|
80
|
+
return list(zip(ids, distances, metadatas))
|
|
81
|
+
return list(zip(ids, distances))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
import numpy as np
|
|
86
|
+
|
|
87
|
+
# Initialize the database
|
|
88
|
+
db = ChromaEmbeddingsDB(path="chromadb_storage")
|
|
89
|
+
|
|
90
|
+
# Add embeddings
|
|
91
|
+
embedding1 = np.array([0.1, 0.2, 0.3])
|
|
92
|
+
embedding2 = np.array([0.4, 0.5, 0.6])
|
|
93
|
+
db.add_embeddings("user1", embedding1, metadata={"name": "Bob"})
|
|
94
|
+
db.add_embeddings("user2", embedding2, metadata={"name": "Joe"})
|
|
95
|
+
|
|
96
|
+
# Retrieve and print embeddings
|
|
97
|
+
print(db.get_embedding("user1"))
|
|
98
|
+
print(db.get_embedding("user2"))
|
|
99
|
+
|
|
100
|
+
# Query embeddings
|
|
101
|
+
query_embedding = np.array([0.2, 0.3, 0.4])
|
|
102
|
+
results = db.query(query_embedding, top_k=2)
|
|
103
|
+
print(results)
|
|
104
|
+
# [('user2', 0.0053884541273605535),
|
|
105
|
+
# ('user1', 0.007416666029069874)]
|
|
106
|
+
results = db.query(query_embedding, top_k=2, return_metadata=True)
|
|
107
|
+
print(results)
|
|
108
|
+
# [('user2', 0.0053884541273605535, {'name': 'Joe'}),
|
|
109
|
+
# ('user1', 0.007416666029069874, {'name': 'Bob'})]
|
|
110
|
+
|
|
111
|
+
# Delete an embedding
|
|
112
|
+
db.delete_embedding("user1")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ovos-chromadb-embeddings-plugin
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: A chromadb embeddings plugin for OVOS
|
|
5
|
+
Home-page: https://github.com/TigreGotico/ovos-chromadb-embeddings-plugin
|
|
6
|
+
Author: jarbasai
|
|
7
|
+
Author-email: jarbasai@mailfence.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Description: # ChromaEmbeddingsDB Plugin
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
The `ChromaEmbeddingsDB` plugin integrates with the [ChromaDB](https://www.trychroma.com/) database to provide a robust solution for managing and querying embeddings. This plugin extends the abstract `EmbeddingsDB` class, allowing you to store, retrieve, and query embeddings efficiently using ChromaDB’s capabilities.
|
|
14
|
+
|
|
15
|
+
This plugin is meant to be used by other specialized plugins such as:
|
|
16
|
+
- https://github.com/TigreGotico/ovos-face-embeddings-plugin
|
|
17
|
+
- https://github.com/TigreGotico/ovos-voice-embeddings-plugin
|
|
18
|
+
- https://github.com/TigreGotico/ovos-gguf-embeddings-plugin
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Add Embeddings**: Store embeddings with associated keys.
|
|
23
|
+
- **Retrieve Embeddings**: Fetch embeddings by their keys.
|
|
24
|
+
- **Delete Embeddings**: Remove embeddings by their keys.
|
|
25
|
+
- **Query Embeddings**: Find the closest embeddings to a given query, with support for cosine distance.
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
Here is a full example demonstrating the basic usage of `ChromaEmbeddingsDB`.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import numpy as np
|
|
33
|
+
from chroma_embeddings_db import ChromaEmbeddingsDB
|
|
34
|
+
|
|
35
|
+
# Initialize the database
|
|
36
|
+
db = ChromaEmbeddingsDB(path="path_to_chromadb_storage")
|
|
37
|
+
|
|
38
|
+
# Add embeddings
|
|
39
|
+
embedding1 = np.array([0.1, 0.2, 0.3])
|
|
40
|
+
embedding2 = np.array([0.4, 0.5, 0.6])
|
|
41
|
+
db.add_embeddings("user1", embedding1)
|
|
42
|
+
db.add_embeddings("user2", embedding2)
|
|
43
|
+
|
|
44
|
+
# Retrieve and print embeddings
|
|
45
|
+
print(db.get_embedding("user1"))
|
|
46
|
+
print(db.get_embedding("user2"))
|
|
47
|
+
|
|
48
|
+
# Query embeddings
|
|
49
|
+
query_embedding = np.array([0.2, 0.3, 0.4])
|
|
50
|
+
results = db.query(query_embedding, top_k=2)
|
|
51
|
+
print(results)
|
|
52
|
+
|
|
53
|
+
# Delete an embedding
|
|
54
|
+
db.delete_embedding("user1")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> Ensure that the path provided to the `ChromaEmbeddingsDB` constructor is accessible and writable.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Acknowledgements
|
|
61
|
+
|
|
62
|
+
- [ChromaDB](https://www.trychroma.com/) for providing the database backend.
|
|
63
|
+
- [NumPy](https://numpy.org) for numerical operations.
|
|
64
|
+
|
|
65
|
+
Keywords: OVOS openvoiceos plugin chromadb recognition
|
|
66
|
+
Platform: UNKNOWN
|
|
67
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
ovos_chromadb_embeddings/__init__.py
|
|
4
|
+
ovos_chromadb_embeddings/version.py
|
|
5
|
+
ovos_chromadb_embeddings_plugin.egg-info/PKG-INFO
|
|
6
|
+
ovos_chromadb_embeddings_plugin.egg-info/SOURCES.txt
|
|
7
|
+
ovos_chromadb_embeddings_plugin.egg-info/dependency_links.txt
|
|
8
|
+
ovos_chromadb_embeddings_plugin.egg-info/entry_points.txt
|
|
9
|
+
ovos_chromadb_embeddings_plugin.egg-info/requires.txt
|
|
10
|
+
ovos_chromadb_embeddings_plugin.egg-info/top_level.txt
|
|
11
|
+
ovos_chromadb_embeddings_plugin.egg-info/zip-safe
|
ovos-chromadb-embeddings-plugin-0.0.0/ovos_chromadb_embeddings_plugin.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ovos_chromadb_embeddings
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from setuptools import setup
|
|
5
|
+
|
|
6
|
+
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
|
7
|
+
|
|
8
|
+
with open(f"{BASEDIR}/README.md", "r") as fh:
|
|
9
|
+
long_desc = fh.read()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_version():
|
|
13
|
+
""" Find the version of the package"""
|
|
14
|
+
version_file = os.path.join(BASEDIR, 'ovos_chromadb_embeddings', 'version.py')
|
|
15
|
+
major, minor, build, alpha = (None, None, None, None)
|
|
16
|
+
with open(version_file) as f:
|
|
17
|
+
for line in f:
|
|
18
|
+
if 'VERSION_MAJOR' in line:
|
|
19
|
+
major = line.split('=')[1].strip()
|
|
20
|
+
elif 'VERSION_MINOR' in line:
|
|
21
|
+
minor = line.split('=')[1].strip()
|
|
22
|
+
elif 'VERSION_BUILD' in line:
|
|
23
|
+
build = line.split('=')[1].strip()
|
|
24
|
+
elif 'VERSION_ALPHA' in line:
|
|
25
|
+
alpha = line.split('=')[1].strip()
|
|
26
|
+
|
|
27
|
+
if ((major and minor and build and alpha) or
|
|
28
|
+
'# END_VERSION_BLOCK' in line):
|
|
29
|
+
break
|
|
30
|
+
version = f"{major}.{minor}.{build}"
|
|
31
|
+
if int(alpha):
|
|
32
|
+
version += f"a{alpha}"
|
|
33
|
+
return version
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def required(requirements_file):
|
|
37
|
+
""" Read requirements file and remove comments and empty lines. """
|
|
38
|
+
with open(os.path.join(BASEDIR, requirements_file), 'r') as f:
|
|
39
|
+
requirements = f.read().splitlines()
|
|
40
|
+
if 'MYCROFT_LOOSE_REQUIREMENTS' in os.environ:
|
|
41
|
+
print('USING LOOSE REQUIREMENTS!')
|
|
42
|
+
requirements = [r.replace('==', '>=').replace('~=', '>=') for r in requirements]
|
|
43
|
+
return [pkg for pkg in requirements
|
|
44
|
+
if pkg.strip() and not pkg.startswith("#")]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
PLUGIN_ENTRY_POINT = 'ovos-chromadb-embeddings-plugin=ovos_chromadb_embeddings:ChromaEmbeddingsDB'
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
setup(
|
|
51
|
+
name='ovos-chromadb-embeddings-plugin',
|
|
52
|
+
version=get_version(),
|
|
53
|
+
description='A chromadb embeddings plugin for OVOS',
|
|
54
|
+
url='https://github.com/TigreGotico/ovos-chromadb-embeddings-plugin',
|
|
55
|
+
author='jarbasai',
|
|
56
|
+
author_email='jarbasai@mailfence.com',
|
|
57
|
+
license='MIT',
|
|
58
|
+
packages=['ovos_chromadb_embeddings'],
|
|
59
|
+
zip_safe=True,
|
|
60
|
+
keywords='OVOS openvoiceos plugin chromadb recognition',
|
|
61
|
+
entry_points={'opm.embeddings': PLUGIN_ENTRY_POINT},
|
|
62
|
+
install_requires=required("requirements.txt"),
|
|
63
|
+
long_description=long_desc,
|
|
64
|
+
long_description_content_type='text/markdown'
|
|
65
|
+
)
|