Merge remote-tracking branch 'origin/feat/COG-24-add-qdrant' into feat/COG-24-add-qdrant

This commit is contained in:
Boris Arzentar 2024-03-13 15:28:45 +01:00
commit 2a8633a93a
9 changed files with 651 additions and 50 deletions

571
cognee - Get Started.ipynb Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,4 @@
from .api.v1.add.add import add from .api.v1.add.add import add
from .api.v1.cognify.cognify import cognify from .api.v1.cognify.cognify import cognify
from .api.v1.list_datasets.list_datasets import list_datasets from .api.v1.list_datasets.list_datasets import list_datasets
from .api.v1.search import search

View file

@ -1,10 +1,13 @@
""" This module contains the search function that is used to search for nodes in the graph.""" """ This module contains the search function that is used to search for nodes in the graph."""
from enum import Enum, auto from enum import Enum, auto
from typing import Dict, Any, Callable, List from typing import Dict, Any, Callable, List
from cognitive_architecture.infrastructure.databases.graph.get_graph_client import get_graph_client
from cognitive_architecture.modules.search.graph.search_adjacent import search_adjacent from cognitive_architecture.modules.search.graph.search_adjacent import search_adjacent
from cognitive_architecture.modules.search.vector.search_similarity import search_similarity from cognitive_architecture.modules.search.vector.search_similarity import search_similarity
from cognitive_architecture.modules.search.graph.search_categories import search_categories from cognitive_architecture.modules.search.graph.search_categories import search_categories
from cognitive_architecture.modules.search.graph.search_neighbour import search_neighbour from cognitive_architecture.modules.search.graph.search_neighbour import search_neighbour
from cognitive_architecture.shared.data_models import GraphDBType
class SearchType(Enum): class SearchType(Enum):
@ -14,7 +17,7 @@ class SearchType(Enum):
NEIGHBOR = auto() NEIGHBOR = auto()
def complex_search(graph, query_params: Dict[SearchType, Dict[str, Any]]) -> List: async def complex_search(graph, query_params: Dict[SearchType, Dict[str, Any]]) -> List:
search_functions: Dict[SearchType, Callable] = { search_functions: Dict[SearchType, Callable] = {
SearchType.ADJACENT: search_adjacent, SearchType.ADJACENT: search_adjacent,
SearchType.SIMILARITY: search_similarity, SearchType.SIMILARITY: search_similarity,
@ -24,10 +27,40 @@ def complex_search(graph, query_params: Dict[SearchType, Dict[str, Any]]) -> Lis
results = set() results = set()
# Create a list to hold all the coroutine objects
search_tasks = []
for search_type, params in query_params.items(): for search_type, params in query_params.items():
search_func = search_functions.get(search_type) search_func = search_functions.get(search_type)
if search_func: if search_func:
search_result = search_func(graph, **params) # Schedule the coroutine for execution and store the task
results.update(search_result) task = search_func(graph, **params)
search_tasks.append(task)
# Use asyncio.gather to run all scheduled tasks concurrently
search_results = await asyncio.gather(*search_tasks)
# Update the results set with the results from all tasks
for search_result in search_results:
results.update(search_result)
return list(results) return list(results)
if __name__ == "__main__":
import asyncio
query_params = {
SearchType.SIMILARITY: {'query': 'your search query here'}
}
async def main():
graph_client = get_graph_client(GraphDBType.NETWORKX)
await graph_client.load_graph_from_file()
graph = graph_client.graph
results = await complex_search(graph, query_params)
print(results)
asyncio.run(main())

View file

@ -1,7 +1,14 @@
from cognitive_architecture.infrastructure.llm.get_llm_client import get_llm_client from cognitive_architecture.infrastructure.llm.get_llm_client import get_llm_client
from cognitive_architecture.modules.cognify.graph.add_node_connections import extract_node_descriptions
async def search_similarity(query ,unique_layer_uuids):
async def search_similarity(query:str ,graph):
node_descriptions = await extract_node_descriptions(graph.nodes(data = True))
# print(node_descriptions)
unique_layer_uuids = set(node["layer_decomposition_uuid"] for node in node_descriptions)
client = get_llm_client() client = get_llm_client()
out = [] out = []

78
poetry.lock generated
View file

@ -524,17 +524,17 @@ css = ["tinycss2 (>=1.1.0,<1.3)"]
[[package]] [[package]]
name = "boto3" name = "boto3"
version = "1.34.60" version = "1.34.61"
description = "The AWS SDK for Python" description = "The AWS SDK for Python"
optional = false optional = false
python-versions = ">= 3.8" python-versions = ">= 3.8"
files = [ files = [
{file = "boto3-1.34.60-py3-none-any.whl", hash = "sha256:199f11518b370e2ec1f6d5ec0c647eca967b6eb2cf6a332d9fc01a2144ea3690"}, {file = "boto3-1.34.61-py3-none-any.whl", hash = "sha256:992e994c7e481a5d3259c699574882b79d631a46f7c369bea350b7ccb0651317"},
{file = "boto3-1.34.60.tar.gz", hash = "sha256:f78f30f4d6b1d5839ec20da9ffe0f3b36c1b404a415d208459a0b88c202a05e9"}, {file = "boto3-1.34.61.tar.gz", hash = "sha256:4b40bf2c8494647c9e88c180537dc9fc0c1047a9fffbb1e5b0da6596f1e59b7b"},
] ]
[package.dependencies] [package.dependencies]
botocore = ">=1.34.60,<1.35.0" botocore = ">=1.34.61,<1.35.0"
jmespath = ">=0.7.1,<2.0.0" jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0" s3transfer = ">=0.10.0,<0.11.0"
@ -543,13 +543,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]] [[package]]
name = "botocore" name = "botocore"
version = "1.34.60" version = "1.34.61"
description = "Low-level, data-driven core of boto 3." description = "Low-level, data-driven core of boto 3."
optional = false optional = false
python-versions = ">= 3.8" python-versions = ">= 3.8"
files = [ files = [
{file = "botocore-1.34.60-py3-none-any.whl", hash = "sha256:6e5317aab5dea19579e7c33528d53761bfb02f3fac5da2de01d1686a25f116a5"}, {file = "botocore-1.34.61-py3-none-any.whl", hash = "sha256:079f3288d38f97fd5656c25c44a94bea0e7090b938abfdeea463eaadb210c4a0"},
{file = "botocore-1.34.60.tar.gz", hash = "sha256:4101494f0b692c95c592cba2719a61854e1c2923d89c60eaddf0e0d986442562"}, {file = "botocore-1.34.61.tar.gz", hash = "sha256:72df4af7e4e6392552c882d48c74e4be9bf7be4cd8d829711b312fbae13d7034"},
] ]
[package.dependencies] [package.dependencies]
@ -830,13 +830,13 @@ cron = ["capturer (>=2.4)"]
[[package]] [[package]]
name = "comm" name = "comm"
version = "0.2.1" version = "0.2.2"
description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "comm-0.2.1-py3-none-any.whl", hash = "sha256:87928485c0dfc0e7976fd89fc1e187023cf587e7c353e4a9b417555b44adf021"}, {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"},
{file = "comm-0.2.1.tar.gz", hash = "sha256:0bc91edae1344d39d3661dcbc36937181fdaddb304790458f8b044dbc064b89a"}, {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"},
] ]
[package.dependencies] [package.dependencies]
@ -2350,16 +2350,6 @@ qtconsole = ["qtconsole"]
test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"] test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"]
test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"]
[[package]]
name = "iso639"
version = "0.1.4"
description = "ISO639-2 support for Python."
optional = false
python-versions = "*"
files = [
{file = "iso639-0.1.4.tar.gz", hash = "sha256:88b70cf6c64ee9c2c2972292818c8beb32db9ea6f4de1f8471a9b081a3d92e98"},
]
[[package]] [[package]]
name = "isoduration" name = "isoduration"
version = "20.11.0" version = "20.11.0"
@ -2607,13 +2597,13 @@ test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"
[[package]] [[package]]
name = "jupyter-events" name = "jupyter-events"
version = "0.9.0" version = "0.9.1"
description = "Jupyter Event System library" description = "Jupyter Event System library"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "jupyter_events-0.9.0-py3-none-any.whl", hash = "sha256:d853b3c10273ff9bc8bb8b30076d65e2c9685579db736873de6c2232dde148bf"}, {file = "jupyter_events-0.9.1-py3-none-any.whl", hash = "sha256:e51f43d2c25c2ddf02d7f7a5045f71fc1d5cb5ad04ef6db20da961c077654b9b"},
{file = "jupyter_events-0.9.0.tar.gz", hash = "sha256:81ad2e4bc710881ec274d31c6c50669d71bbaa5dd9d01e600b56faa85700d399"}, {file = "jupyter_events-0.9.1.tar.gz", hash = "sha256:a52e86f59eb317ee71ff2d7500c94b963b8a24f0b7a1517e2e653e24258e15c7"},
] ]
[package.dependencies] [package.dependencies]
@ -2682,13 +2672,13 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-sc
[[package]] [[package]]
name = "jupyter-server-terminals" name = "jupyter-server-terminals"
version = "0.5.2" version = "0.5.3"
description = "A Jupyter Server Extension Providing Terminals." description = "A Jupyter Server Extension Providing Terminals."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "jupyter_server_terminals-0.5.2-py3-none-any.whl", hash = "sha256:1b80c12765da979513c42c90215481bbc39bd8ae7c0350b4f85bc3eb58d0fa80"}, {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"},
{file = "jupyter_server_terminals-0.5.2.tar.gz", hash = "sha256:396b5ccc0881e550bf0ee7012c6ef1b53edbde69e67cab1d56e89711b46052e8"}, {file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"},
] ]
[package.dependencies] [package.dependencies]
@ -3763,13 +3753,13 @@ files = [
[[package]] [[package]]
name = "nbclient" name = "nbclient"
version = "0.9.0" version = "0.10.0"
description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor."
optional = false optional = false
python-versions = ">=3.8.0" python-versions = ">=3.8.0"
files = [ files = [
{file = "nbclient-0.9.0-py3-none-any.whl", hash = "sha256:a3a1ddfb34d4a9d17fc744d655962714a866639acd30130e9be84191cd97cd15"}, {file = "nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f"},
{file = "nbclient-0.9.0.tar.gz", hash = "sha256:4b28c207877cf33ef3a9838cdc7a54c5ceff981194a82eac59d558f05487295e"}, {file = "nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09"},
] ]
[package.dependencies] [package.dependencies]
@ -3781,7 +3771,7 @@ traitlets = ">=5.4"
[package.extras] [package.extras]
dev = ["pre-commit"] dev = ["pre-commit"]
docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"]
test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"]
[[package]] [[package]]
name = "nbconvert" name = "nbconvert"
@ -3822,13 +3812,13 @@ webpdf = ["playwright"]
[[package]] [[package]]
name = "nbformat" name = "nbformat"
version = "5.9.2" version = "5.10.2"
description = "The Jupyter Notebook format" description = "The Jupyter Notebook format"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "nbformat-5.9.2-py3-none-any.whl", hash = "sha256:1c5172d786a41b82bcfd0c23f9e6b6f072e8fb49c39250219e4acfff1efe89e9"}, {file = "nbformat-5.10.2-py3-none-any.whl", hash = "sha256:7381189a0d537586b3f18bae5dbad347d7dd0a7cf0276b09cdcd5c24d38edd99"},
{file = "nbformat-5.9.2.tar.gz", hash = "sha256:5f98b5ba1997dff175e77e0c17d5c10a96eaed2cbd1de3533d1fc35d5e111192"}, {file = "nbformat-5.10.2.tar.gz", hash = "sha256:c535b20a0d4310167bf4d12ad31eccfb0dc61e6392d6f8c570ab5b45a06a49a3"},
] ]
[package.dependencies] [package.dependencies]
@ -4106,6 +4096,7 @@ description = "Nvidia JIT LTO Library"
optional = false optional = false
python-versions = ">=3" python-versions = ">=3"
files = [ files = [
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-manylinux2014_aarch64.whl", hash = "sha256:75d6498c96d9adb9435f2bbdbddb479805ddfb97b5c1b32395c694185c20ca57"},
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c6428836d20fe7e327191c175791d38570e10762edc588fb46749217cd444c74"}, {file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c6428836d20fe7e327191c175791d38570e10762edc588fb46749217cd444c74"},
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-win_amd64.whl", hash = "sha256:991905ffa2144cb603d8ca7962d75c35334ae82bf92820b6ba78157277da1ad2"}, {file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-win_amd64.whl", hash = "sha256:991905ffa2144cb603d8ca7962d75c35334ae82bf92820b6ba78157277da1ad2"},
] ]
@ -5156,13 +5147,13 @@ files = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.6.3" version = "2.6.4"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic-2.6.3-py3-none-any.whl", hash = "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a"}, {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"},
{file = "pydantic-2.6.3.tar.gz", hash = "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"}, {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"},
] ]
[package.dependencies] [package.dependencies]
@ -5710,7 +5701,6 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@ -6923,13 +6913,13 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"]
[[package]] [[package]]
name = "terminado" name = "terminado"
version = "0.18.0" version = "0.18.1"
description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "terminado-0.18.0-py3-none-any.whl", hash = "sha256:87b0d96642d0fe5f5abd7783857b9cab167f221a39ff98e3b9619a788a3c0f2e"}, {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"},
{file = "terminado-0.18.0.tar.gz", hash = "sha256:1ea08a89b835dd1b8c0c900d92848147cef2537243361b2e3f4dc15df9b6fded"}, {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"},
] ]
[package.dependencies] [package.dependencies]
@ -7585,13 +7575,13 @@ xlsx = ["networkx", "openpyxl", "pandas", "xlrd"]
[[package]] [[package]]
name = "unstructured-client" name = "unstructured-client"
version = "0.21.1" version = "0.22.0"
description = "Python Client SDK for Unstructured API" description = "Python Client SDK for Unstructured API"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "unstructured-client-0.21.1.tar.gz", hash = "sha256:82109e485423169006addb18a780b0ffd0b94bde4ede2953ceb5880e164fedb6"}, {file = "unstructured-client-0.22.0.tar.gz", hash = "sha256:d76351c456fcf4067f59135c09a35008a120103621a1ac94cf9448db28ef2e1e"},
{file = "unstructured_client-0.21.1-py3-none-any.whl", hash = "sha256:52a82a550bb5f722e77f3b3633664734e5ada0a1e354bf9503825e1f204d1434"}, {file = "unstructured_client-0.22.0-py3-none-any.whl", hash = "sha256:140f7dd8515cc9be3276f66d6fb08d235ea35a50d4eb38a6ef82b60fc3d2fea0"},
] ]
[package.dependencies] [package.dependencies]
@ -8068,4 +8058,4 @@ weaviate = []
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "3.10.13" python-versions = "3.10.13"
content-hash = "5d72d599cec8707c632ef2307f41811103586b464164150521faa0ec30ffe195" content-hash = "d9010f8123850150bf7a9cf1c2955a69c69ce28467bbe8b6ff9d7bf0c7f072e1"

View file

@ -31,17 +31,16 @@ instructor = "^0.3.4"
networkx = "^3.2.1" networkx = "^3.2.1"
graphviz = "^0.20.1" graphviz = "^0.20.1"
langdetect = "^1.0.9" langdetect = "^1.0.9"
iso639 = "^0.1.4"
debugpy = "^1.8.0" debugpy = "^1.8.0"
pyarrow = "^15.0.0" pyarrow = "^15.0.0"
pylint = "^3.0.3" pylint = "^3.0.3"
aiosqlite = "^0.20.0" aiosqlite = "^0.20.0"
unstructured = {extras = ["all-docs"], version = "^0.12.5"}
pymupdf = "^1.23.25" pymupdf = "^1.23.25"
pandas = "^2.2.1" pandas = "^2.2.1"
greenlet = "^3.0.3" greenlet = "^3.0.3"
ruff = "^0.2.2" ruff = "^0.2.2"
filetype = "^1.2.0" filetype = "^1.2.0"
unstructured = {extras = ["all-docs"], version = "^0.12.5"}
nltk = "^3.8.1" nltk = "^3.8.1"
scikit-learn = "^1.4.1.post1" scikit-learn = "^1.4.1.post1"
dlt = "^0.4.6" dlt = "^0.4.6"