Merge branch 'main' into user/guojian/feat_otel_logging
commit
e1bdebcf69
@ -0,0 +1,25 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class HuaweiCloudConfig(BaseSettings):
|
||||||
|
"""
|
||||||
|
Configuration settings for Huawei cloud search service
|
||||||
|
"""
|
||||||
|
|
||||||
|
HUAWEI_CLOUD_HOSTS: Optional[str] = Field(
|
||||||
|
description="Hostname or IP address of the Huawei cloud search service instance",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
HUAWEI_CLOUD_USER: Optional[str] = Field(
|
||||||
|
description="Username for authenticating with Huawei cloud search service",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
HUAWEI_CLOUD_PASSWORD: Optional[str] = Field(
|
||||||
|
description="Password for authenticating with Huawei cloud search service",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic.fields import FieldInfo
|
||||||
|
|
||||||
|
from .http_request import NacosHttpClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from configs.remote_settings_sources.base import RemoteSettingsSource
|
||||||
|
|
||||||
|
from .utils import _parse_config
|
||||||
|
|
||||||
|
|
||||||
|
class NacosSettingsSource(RemoteSettingsSource):
|
||||||
|
def __init__(self, configs: Mapping[str, Any]):
|
||||||
|
self.configs = configs
|
||||||
|
self.remote_configs: dict[str, Any] = {}
|
||||||
|
self.async_init()
|
||||||
|
|
||||||
|
def async_init(self):
|
||||||
|
data_id = os.getenv("DIFY_ENV_NACOS_DATA_ID", "dify-api-env.properties")
|
||||||
|
group = os.getenv("DIFY_ENV_NACOS_GROUP", "nacos-dify")
|
||||||
|
tenant = os.getenv("DIFY_ENV_NACOS_NAMESPACE", "")
|
||||||
|
|
||||||
|
params = {"dataId": data_id, "group": group, "tenant": tenant}
|
||||||
|
try:
|
||||||
|
content = NacosHttpClient().http_request("/nacos/v1/cs/configs", method="GET", headers={}, params=params)
|
||||||
|
self.remote_configs = self._parse_config(content)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("[get-access-token] exception occurred")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _parse_config(self, content: str) -> dict:
|
||||||
|
if not content:
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
return _parse_config(self, content)
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to parse config: {e}")
|
||||||
|
|
||||||
|
def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
|
||||||
|
if not isinstance(self.remote_configs, dict):
|
||||||
|
raise ValueError(f"remote configs is not dict, but {type(self.remote_configs)}")
|
||||||
|
|
||||||
|
field_value = self.remote_configs.get(field_name)
|
||||||
|
if field_value is None:
|
||||||
|
return None, field_name, False
|
||||||
|
|
||||||
|
return field_value, field_name, False
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NacosHttpClient:
|
||||||
|
def __init__(self):
|
||||||
|
self.username = os.getenv("DIFY_ENV_NACOS_USERNAME")
|
||||||
|
self.password = os.getenv("DIFY_ENV_NACOS_PASSWORD")
|
||||||
|
self.ak = os.getenv("DIFY_ENV_NACOS_ACCESS_KEY")
|
||||||
|
self.sk = os.getenv("DIFY_ENV_NACOS_SECRET_KEY")
|
||||||
|
self.server = os.getenv("DIFY_ENV_NACOS_SERVER_ADDR", "localhost:8848")
|
||||||
|
self.token = None
|
||||||
|
self.token_ttl = 18000
|
||||||
|
self.token_expire_time: float = 0
|
||||||
|
|
||||||
|
def http_request(self, url, method="GET", headers=None, params=None):
|
||||||
|
try:
|
||||||
|
self._inject_auth_info(headers, params)
|
||||||
|
response = requests.request(method, url="http://" + self.server + url, headers=headers, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.text
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return f"Request to Nacos failed: {e}"
|
||||||
|
|
||||||
|
def _inject_auth_info(self, headers, params, module="config"):
|
||||||
|
headers.update({"User-Agent": "Nacos-Http-Client-In-Dify:v0.0.1"})
|
||||||
|
|
||||||
|
if module == "login":
|
||||||
|
return
|
||||||
|
|
||||||
|
ts = str(int(time.time() * 1000))
|
||||||
|
|
||||||
|
if self.ak and self.sk:
|
||||||
|
sign_str = self.get_sign_str(params["group"], params["tenant"], ts)
|
||||||
|
headers["Spas-AccessKey"] = self.ak
|
||||||
|
headers["Spas-Signature"] = self.__do_sign(sign_str, self.sk)
|
||||||
|
headers["timeStamp"] = ts
|
||||||
|
if self.username and self.password:
|
||||||
|
self.get_access_token(force_refresh=False)
|
||||||
|
params["accessToken"] = self.token
|
||||||
|
|
||||||
|
def __do_sign(self, sign_str, sk):
|
||||||
|
return (
|
||||||
|
base64.encodebytes(hmac.new(sk.encode(), sign_str.encode(), digestmod=hashlib.sha1).digest())
|
||||||
|
.decode()
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_sign_str(self, group, tenant, ts):
|
||||||
|
sign_str = ""
|
||||||
|
if tenant:
|
||||||
|
sign_str = tenant + "+"
|
||||||
|
if group:
|
||||||
|
sign_str = sign_str + group + "+"
|
||||||
|
if sign_str:
|
||||||
|
sign_str += ts
|
||||||
|
return sign_str
|
||||||
|
|
||||||
|
def get_access_token(self, force_refresh=False):
|
||||||
|
current_time = time.time()
|
||||||
|
if self.token and not force_refresh and self.token_expire_time > current_time:
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
params = {"username": self.username, "password": self.password}
|
||||||
|
url = "http://" + self.server + "/nacos/v1/auth/login"
|
||||||
|
try:
|
||||||
|
resp = requests.request("POST", url, headers=None, params=params)
|
||||||
|
resp.raise_for_status()
|
||||||
|
response_data = resp.json()
|
||||||
|
self.token = response_data.get("accessToken")
|
||||||
|
self.token_ttl = response_data.get("tokenTtl", 18000)
|
||||||
|
self.token_expire_time = current_time + self.token_ttl - 10
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("[get-access-token] exception occur")
|
||||||
|
raise
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
def _parse_config(self, content: str) -> dict[str, str]:
|
||||||
|
config: dict[str, str] = {}
|
||||||
|
if not content:
|
||||||
|
return config
|
||||||
|
|
||||||
|
for line in content.splitlines():
|
||||||
|
cleaned_line = line.strip()
|
||||||
|
if not cleaned_line or cleaned_line.startswith(("#", "!")):
|
||||||
|
continue
|
||||||
|
|
||||||
|
separator_index = -1
|
||||||
|
for i, c in enumerate(cleaned_line):
|
||||||
|
if c in ("=", ":") and (i == 0 or cleaned_line[i - 1] != "\\"):
|
||||||
|
separator_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if separator_index == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = cleaned_line[:separator_index].strip()
|
||||||
|
raw_value = cleaned_line[separator_index + 1 :].strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
decoded_value = bytes(raw_value, "utf-8").decode("unicode_escape")
|
||||||
|
decoded_value = decoded_value.replace(r"\=", "=").replace(r"\:", ":")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
decoded_value = raw_value
|
||||||
|
|
||||||
|
config[key] = decoded_value
|
||||||
|
|
||||||
|
return config
|
||||||
@ -0,0 +1,215 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import ssl
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
from pydantic import BaseModel, model_validator
|
||||||
|
|
||||||
|
from configs import dify_config
|
||||||
|
from core.rag.datasource.vdb.field import Field
|
||||||
|
from core.rag.datasource.vdb.vector_base import BaseVector
|
||||||
|
from core.rag.datasource.vdb.vector_factory import AbstractVectorFactory
|
||||||
|
from core.rag.datasource.vdb.vector_type import VectorType
|
||||||
|
from core.rag.embedding.embedding_base import Embeddings
|
||||||
|
from core.rag.models.document import Document
|
||||||
|
from extensions.ext_redis import redis_client
|
||||||
|
from models.dataset import Dataset
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_ssl_context() -> ssl.SSLContext:
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
ssl_context.check_hostname = False
|
||||||
|
ssl_context.verify_mode = ssl.CERT_NONE
|
||||||
|
return ssl_context
|
||||||
|
|
||||||
|
|
||||||
|
class HuaweiCloudVectorConfig(BaseModel):
|
||||||
|
hosts: str
|
||||||
|
username: str | None
|
||||||
|
password: str | None
|
||||||
|
|
||||||
|
@model_validator(mode="before")
|
||||||
|
@classmethod
|
||||||
|
def validate_config(cls, values: dict) -> dict:
|
||||||
|
if not values["hosts"]:
|
||||||
|
raise ValueError("config HOSTS is required")
|
||||||
|
return values
|
||||||
|
|
||||||
|
def to_elasticsearch_params(self) -> dict[str, Any]:
|
||||||
|
params = {
|
||||||
|
"hosts": self.hosts.split(","),
|
||||||
|
"verify_certs": False,
|
||||||
|
"ssl_show_warn": False,
|
||||||
|
"request_timeout": 30000,
|
||||||
|
"retry_on_timeout": True,
|
||||||
|
"max_retries": 10,
|
||||||
|
}
|
||||||
|
if self.username and self.password:
|
||||||
|
params["basic_auth"] = (self.username, self.password)
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
class HuaweiCloudVector(BaseVector):
|
||||||
|
def __init__(self, index_name: str, config: HuaweiCloudVectorConfig):
|
||||||
|
super().__init__(index_name.lower())
|
||||||
|
self._client = Elasticsearch(**config.to_elasticsearch_params())
|
||||||
|
|
||||||
|
def get_type(self) -> str:
|
||||||
|
return VectorType.HUAWEI_CLOUD
|
||||||
|
|
||||||
|
def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs):
|
||||||
|
uuids = self._get_uuids(documents)
|
||||||
|
for i in range(len(documents)):
|
||||||
|
self._client.index(
|
||||||
|
index=self._collection_name,
|
||||||
|
id=uuids[i],
|
||||||
|
document={
|
||||||
|
Field.CONTENT_KEY.value: documents[i].page_content,
|
||||||
|
Field.VECTOR.value: embeddings[i] or None,
|
||||||
|
Field.METADATA_KEY.value: documents[i].metadata or {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._client.indices.refresh(index=self._collection_name)
|
||||||
|
return uuids
|
||||||
|
|
||||||
|
def text_exists(self, id: str) -> bool:
|
||||||
|
return bool(self._client.exists(index=self._collection_name, id=id))
|
||||||
|
|
||||||
|
def delete_by_ids(self, ids: list[str]) -> None:
|
||||||
|
if not ids:
|
||||||
|
return
|
||||||
|
for id in ids:
|
||||||
|
self._client.delete(index=self._collection_name, id=id)
|
||||||
|
|
||||||
|
def delete_by_metadata_field(self, key: str, value: str) -> None:
|
||||||
|
query_str = {"query": {"match": {f"metadata.{key}": f"{value}"}}}
|
||||||
|
results = self._client.search(index=self._collection_name, body=query_str)
|
||||||
|
ids = [hit["_id"] for hit in results["hits"]["hits"]]
|
||||||
|
if ids:
|
||||||
|
self.delete_by_ids(ids)
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
self._client.indices.delete(index=self._collection_name)
|
||||||
|
|
||||||
|
def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]:
|
||||||
|
top_k = kwargs.get("top_k", 4)
|
||||||
|
|
||||||
|
query = {
|
||||||
|
"size": top_k,
|
||||||
|
"query": {
|
||||||
|
"vector": {
|
||||||
|
Field.VECTOR.value: {
|
||||||
|
"vector": query_vector,
|
||||||
|
"topk": top_k,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
results = self._client.search(index=self._collection_name, body=query)
|
||||||
|
|
||||||
|
docs_and_scores = []
|
||||||
|
for hit in results["hits"]["hits"]:
|
||||||
|
docs_and_scores.append(
|
||||||
|
(
|
||||||
|
Document(
|
||||||
|
page_content=hit["_source"][Field.CONTENT_KEY.value],
|
||||||
|
vector=hit["_source"][Field.VECTOR.value],
|
||||||
|
metadata=hit["_source"][Field.METADATA_KEY.value],
|
||||||
|
),
|
||||||
|
hit["_score"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
docs = []
|
||||||
|
for doc, score in docs_and_scores:
|
||||||
|
score_threshold = float(kwargs.get("score_threshold") or 0.0)
|
||||||
|
if score > score_threshold:
|
||||||
|
if doc.metadata is not None:
|
||||||
|
doc.metadata["score"] = score
|
||||||
|
docs.append(doc)
|
||||||
|
|
||||||
|
return docs
|
||||||
|
|
||||||
|
def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]:
|
||||||
|
query_str = {"match": {Field.CONTENT_KEY.value: query}}
|
||||||
|
results = self._client.search(index=self._collection_name, query=query_str, size=kwargs.get("top_k", 4))
|
||||||
|
docs = []
|
||||||
|
for hit in results["hits"]["hits"]:
|
||||||
|
docs.append(
|
||||||
|
Document(
|
||||||
|
page_content=hit["_source"][Field.CONTENT_KEY.value],
|
||||||
|
vector=hit["_source"][Field.VECTOR.value],
|
||||||
|
metadata=hit["_source"][Field.METADATA_KEY.value],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return docs
|
||||||
|
|
||||||
|
def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs):
|
||||||
|
metadatas = [d.metadata if d.metadata is not None else {} for d in texts]
|
||||||
|
self.create_collection(embeddings, metadatas)
|
||||||
|
self.add_texts(texts, embeddings, **kwargs)
|
||||||
|
|
||||||
|
def create_collection(
|
||||||
|
self,
|
||||||
|
embeddings: list[list[float]],
|
||||||
|
metadatas: Optional[list[dict[Any, Any]]] = None,
|
||||||
|
index_params: Optional[dict] = None,
|
||||||
|
):
|
||||||
|
lock_name = f"vector_indexing_lock_{self._collection_name}"
|
||||||
|
with redis_client.lock(lock_name, timeout=20):
|
||||||
|
collection_exist_cache_key = f"vector_indexing_{self._collection_name}"
|
||||||
|
if redis_client.get(collection_exist_cache_key):
|
||||||
|
logger.info(f"Collection {self._collection_name} already exists.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._client.indices.exists(index=self._collection_name):
|
||||||
|
dim = len(embeddings[0])
|
||||||
|
mappings = {
|
||||||
|
"properties": {
|
||||||
|
Field.CONTENT_KEY.value: {"type": "text"},
|
||||||
|
Field.VECTOR.value: { # Make sure the dimension is correct here
|
||||||
|
"type": "vector",
|
||||||
|
"dimension": dim,
|
||||||
|
"indexing": True,
|
||||||
|
"algorithm": "GRAPH",
|
||||||
|
"metric": "cosine",
|
||||||
|
"neighbors": 32,
|
||||||
|
"efc": 128,
|
||||||
|
},
|
||||||
|
Field.METADATA_KEY.value: {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"doc_id": {"type": "keyword"} # Map doc_id to keyword type
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings = {"index.vector": True}
|
||||||
|
self._client.indices.create(index=self._collection_name, mappings=mappings, settings=settings)
|
||||||
|
|
||||||
|
redis_client.set(collection_exist_cache_key, 1, ex=3600)
|
||||||
|
|
||||||
|
|
||||||
|
class HuaweiCloudVectorFactory(AbstractVectorFactory):
|
||||||
|
def init_vector(self, dataset: Dataset, attributes: list, embeddings: Embeddings) -> HuaweiCloudVector:
|
||||||
|
if dataset.index_struct_dict:
|
||||||
|
class_prefix: str = dataset.index_struct_dict["vector_store"]["class_prefix"]
|
||||||
|
collection_name = class_prefix.lower()
|
||||||
|
else:
|
||||||
|
dataset_id = dataset.id
|
||||||
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id).lower()
|
||||||
|
dataset.index_struct = json.dumps(self.gen_index_struct_dict(VectorType.HUAWEI_CLOUD, collection_name))
|
||||||
|
|
||||||
|
return HuaweiCloudVector(
|
||||||
|
index_name=collection_name,
|
||||||
|
config=HuaweiCloudVectorConfig(
|
||||||
|
hosts=dify_config.HUAWEI_CLOUD_HOSTS or "http://localhost:9200",
|
||||||
|
username=dify_config.HUAWEI_CLOUD_USER,
|
||||||
|
password=dify_config.HUAWEI_CLOUD_PASSWORD,
|
||||||
|
),
|
||||||
|
)
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
Patch for OpenTelemetry context detach method to handle None tokens gracefully.
|
||||||
|
|
||||||
|
This patch addresses the issue where OpenTelemetry's context.detach() method raises a TypeError
|
||||||
|
when called with a None token. The error occurs in the contextvars_context.py file where it tries
|
||||||
|
to call reset() on a None token.
|
||||||
|
|
||||||
|
Related GitHub issue: https://github.com/langgenius/dify/issues/18496
|
||||||
|
|
||||||
|
Error being fixed:
|
||||||
|
```
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "opentelemetry/context/__init__.py", line 154, in detach
|
||||||
|
_RUNTIME_CONTEXT.detach(token)
|
||||||
|
File "opentelemetry/context/contextvars_context.py", line 50, in detach
|
||||||
|
self._current_context.reset(token) # type: ignore
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
TypeError: expected an instance of Token, got None
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of modifying the third-party package directly, this patch monkey-patches the
|
||||||
|
context.detach method to gracefully handle None tokens.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from opentelemetry import context
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Store the original detach method
|
||||||
|
original_detach = context.detach
|
||||||
|
|
||||||
|
|
||||||
|
# Create a patched version that handles None tokens
|
||||||
|
@wraps(original_detach)
|
||||||
|
def patched_detach(token):
|
||||||
|
"""
|
||||||
|
A patched version of context.detach that handles None tokens gracefully.
|
||||||
|
"""
|
||||||
|
if token is None:
|
||||||
|
logger.debug("Attempted to detach a None token, skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
return original_detach(token)
|
||||||
|
|
||||||
|
|
||||||
|
def is_enabled():
|
||||||
|
"""
|
||||||
|
Check if the extension is enabled.
|
||||||
|
Always enable this patch to prevent errors even when OpenTelemetry is disabled.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def init_app(app):
|
||||||
|
"""
|
||||||
|
Initialize the OpenTelemetry context patch.
|
||||||
|
"""
|
||||||
|
# Replace the original detach method with our patched version
|
||||||
|
context.detach = patched_detach
|
||||||
|
logger.info("OpenTelemetry context.detach patched to handle None tokens")
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
|
from api.core.rag.datasource.vdb.field import Field
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
|
||||||
|
|
||||||
|
class MockIndicesClient:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create(self, index, mappings, settings):
|
||||||
|
return {"acknowledge": True}
|
||||||
|
|
||||||
|
def refresh(self, index):
|
||||||
|
return {"acknowledge": True}
|
||||||
|
|
||||||
|
def delete(self, index):
|
||||||
|
return {"acknowledge": True}
|
||||||
|
|
||||||
|
def exists(self, index):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MockClient:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.indices = MockIndicesClient()
|
||||||
|
|
||||||
|
def index(self, **kwargs):
|
||||||
|
return {"acknowledge": True}
|
||||||
|
|
||||||
|
def exists(self, **kwargs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def delete(self, **kwargs):
|
||||||
|
return {"acknowledge": True}
|
||||||
|
|
||||||
|
def search(self, **kwargs):
|
||||||
|
return {
|
||||||
|
"took": 1,
|
||||||
|
"hits": {
|
||||||
|
"hits": [
|
||||||
|
{
|
||||||
|
"_source": {
|
||||||
|
Field.CONTENT_KEY.value: "abcdef",
|
||||||
|
Field.VECTOR.value: [1, 2],
|
||||||
|
Field.METADATA_KEY.value: {},
|
||||||
|
},
|
||||||
|
"_score": 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_source": {
|
||||||
|
Field.CONTENT_KEY.value: "123456",
|
||||||
|
Field.VECTOR.value: [2, 2],
|
||||||
|
Field.METADATA_KEY.value: {},
|
||||||
|
},
|
||||||
|
"_score": 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_source": {
|
||||||
|
Field.CONTENT_KEY.value: "a1b2c3",
|
||||||
|
Field.VECTOR.value: [3, 2],
|
||||||
|
Field.METADATA_KEY.value: {},
|
||||||
|
},
|
||||||
|
"_score": 0.8,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MOCK = os.getenv("MOCK_SWITCH", "false").lower() == "true"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def setup_client_mock(request, monkeypatch: MonkeyPatch):
|
||||||
|
if MOCK:
|
||||||
|
monkeypatch.setattr(Elasticsearch, "__init__", MockClient.__init__)
|
||||||
|
monkeypatch.setattr(Elasticsearch, "index", MockClient.index)
|
||||||
|
monkeypatch.setattr(Elasticsearch, "exists", MockClient.exists)
|
||||||
|
monkeypatch.setattr(Elasticsearch, "delete", MockClient.delete)
|
||||||
|
monkeypatch.setattr(Elasticsearch, "search", MockClient.search)
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
if MOCK:
|
||||||
|
monkeypatch.undo()
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
from core.rag.datasource.vdb.huawei.huawei_cloud_vector import HuaweiCloudVector, HuaweiCloudVectorConfig
|
||||||
|
from tests.integration_tests.vdb.__mock.huaweicloudvectordb import setup_client_mock
|
||||||
|
from tests.integration_tests.vdb.test_vector_store import AbstractVectorTest, get_example_text, setup_mock_redis
|
||||||
|
|
||||||
|
|
||||||
|
class HuaweiCloudVectorTest(AbstractVectorTest):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.vector = HuaweiCloudVector(
|
||||||
|
"dify",
|
||||||
|
HuaweiCloudVectorConfig(
|
||||||
|
hosts="https://127.0.0.1:9200",
|
||||||
|
username="dify",
|
||||||
|
password="dify",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def search_by_vector(self):
|
||||||
|
hits_by_vector = self.vector.search_by_vector(query_vector=self.example_embedding)
|
||||||
|
assert len(hits_by_vector) == 3
|
||||||
|
|
||||||
|
def search_by_full_text(self):
|
||||||
|
hits_by_full_text = self.vector.search_by_full_text(query=get_example_text())
|
||||||
|
assert len(hits_by_full_text) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_huawei_cloud_vector(setup_mock_redis, setup_client_mock):
|
||||||
|
HuaweiCloudVectorTest().run_all_tests()
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
from core.model_runtime.entities.message_entities import (
|
||||||
|
ImagePromptMessageContent,
|
||||||
|
TextPromptMessageContent,
|
||||||
|
UserPromptMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_prompt_message_with_prompt_message_contents():
|
||||||
|
prompt = UserPromptMessage(content=[TextPromptMessageContent(data="Hello, World!")])
|
||||||
|
assert isinstance(prompt.content, list)
|
||||||
|
assert isinstance(prompt.content[0], TextPromptMessageContent)
|
||||||
|
assert prompt.content[0].data == "Hello, World!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dump_prompt_message():
|
||||||
|
example_url = "https://example.com/image.jpg"
|
||||||
|
prompt = UserPromptMessage(
|
||||||
|
content=[
|
||||||
|
ImagePromptMessageContent(
|
||||||
|
url=example_url,
|
||||||
|
format="jpeg",
|
||||||
|
mime_type="image/jpeg",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data = prompt.model_dump()
|
||||||
|
assert data["content"][0].get("url") == example_url
|
||||||
@ -0,0 +1 @@
|
|||||||
|
/vendor
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"guzzlehttp/guzzle": "^7.9"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": ["dify-client.php"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,663 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "7827c548fdcc7e87cb0ae341dd2c6b1b",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "guzzlehttp/guzzle",
|
||||||
|
"version": "7.9.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/guzzle/guzzle.git",
|
||||||
|
"reference": "d281ed313b989f213357e3be1a179f02196ac99b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
|
||||||
|
"reference": "d281ed313b989f213357e3be1a179f02196ac99b",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
|
||||||
|
"guzzlehttp/psr7": "^2.7.0",
|
||||||
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/http-client-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"guzzle/client-integration-tests": "3.0.2",
|
||||||
|
"php-http/message-factory": "^1.1",
|
||||||
|
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
|
||||||
|
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-curl": "Required for CURL handler support",
|
||||||
|
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||||
|
"psr/log": "Required for using the Log middleware"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"bamarni-bin": {
|
||||||
|
"bin-links": true,
|
||||||
|
"forward-command": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions_include.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"GuzzleHttp\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Michael Dowling",
|
||||||
|
"email": "mtdowling@gmail.com",
|
||||||
|
"homepage": "https://github.com/mtdowling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jeremy Lindblom",
|
||||||
|
"email": "jeremeamia@gmail.com",
|
||||||
|
"homepage": "https://github.com/jeremeamia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "George Mponos",
|
||||||
|
"email": "gmponos@gmail.com",
|
||||||
|
"homepage": "https://github.com/gmponos"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com",
|
||||||
|
"homepage": "https://github.com/Nyholm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Márk Sági-Kazár",
|
||||||
|
"email": "mark.sagikazar@gmail.com",
|
||||||
|
"homepage": "https://github.com/sagikazarmark"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Schultze",
|
||||||
|
"email": "webmaster@tubo-world.de",
|
||||||
|
"homepage": "https://github.com/Tobion"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Guzzle is a PHP HTTP client library",
|
||||||
|
"keywords": [
|
||||||
|
"client",
|
||||||
|
"curl",
|
||||||
|
"framework",
|
||||||
|
"http",
|
||||||
|
"http client",
|
||||||
|
"psr-18",
|
||||||
|
"psr-7",
|
||||||
|
"rest",
|
||||||
|
"web service"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||||
|
"source": "https://github.com/guzzle/guzzle/tree/7.9.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Nyholm",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-07-24T11:22:20+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "guzzlehttp/promises",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/guzzle/promises.git",
|
||||||
|
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
|
||||||
|
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
|
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"bamarni-bin": {
|
||||||
|
"bin-links": true,
|
||||||
|
"forward-command": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"GuzzleHttp\\Promise\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Michael Dowling",
|
||||||
|
"email": "mtdowling@gmail.com",
|
||||||
|
"homepage": "https://github.com/mtdowling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com",
|
||||||
|
"homepage": "https://github.com/Nyholm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Schultze",
|
||||||
|
"email": "webmaster@tubo-world.de",
|
||||||
|
"homepage": "https://github.com/Tobion"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Guzzle promises library",
|
||||||
|
"keywords": [
|
||||||
|
"promise"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/guzzle/promises/issues",
|
||||||
|
"source": "https://github.com/guzzle/promises/tree/2.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Nyholm",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-03-27T13:27:01+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "guzzlehttp/psr7",
|
||||||
|
"version": "2.7.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/guzzle/psr7.git",
|
||||||
|
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
|
||||||
|
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"psr/http-factory": "^1.0",
|
||||||
|
"psr/http-message": "^1.1 || ^2.0",
|
||||||
|
"ralouphie/getallheaders": "^3.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/http-factory-implementation": "1.0",
|
||||||
|
"psr/http-message-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
|
"http-interop/http-factory-tests": "0.9.0",
|
||||||
|
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"bamarni-bin": {
|
||||||
|
"bin-links": true,
|
||||||
|
"forward-command": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"GuzzleHttp\\Psr7\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Michael Dowling",
|
||||||
|
"email": "mtdowling@gmail.com",
|
||||||
|
"homepage": "https://github.com/mtdowling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "George Mponos",
|
||||||
|
"email": "gmponos@gmail.com",
|
||||||
|
"homepage": "https://github.com/gmponos"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com",
|
||||||
|
"homepage": "https://github.com/Nyholm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Márk Sági-Kazár",
|
||||||
|
"email": "mark.sagikazar@gmail.com",
|
||||||
|
"homepage": "https://github.com/sagikazarmark"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Schultze",
|
||||||
|
"email": "webmaster@tubo-world.de",
|
||||||
|
"homepage": "https://github.com/Tobion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Márk Sági-Kazár",
|
||||||
|
"email": "mark.sagikazar@gmail.com",
|
||||||
|
"homepage": "https://sagikazarmark.hu"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PSR-7 message implementation that also provides common utility methods",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"message",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response",
|
||||||
|
"stream",
|
||||||
|
"uri",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/guzzle/psr7/issues",
|
||||||
|
"source": "https://github.com/guzzle/psr7/tree/2.7.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Nyholm",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-03-27T12:30:47+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-client",
|
||||||
|
"version": "1.0.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-client.git",
|
||||||
|
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||||
|
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.0 || ^8.0",
|
||||||
|
"psr/http-message": "^1.0 || ^2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Client\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP clients",
|
||||||
|
"homepage": "https://github.com/php-fig/http-client",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-client",
|
||||||
|
"psr",
|
||||||
|
"psr-18"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-client"
|
||||||
|
},
|
||||||
|
"time": "2023-09-23T14:17:50+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-factory",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-factory.git",
|
||||||
|
"reference": "e616d01114759c4c489f93b099585439f795fe35"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
|
||||||
|
"reference": "e616d01114759c4c489f93b099585439f795fe35",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0.0",
|
||||||
|
"psr/http-message": "^1.0 || ^2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Message\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interfaces for PSR-7 HTTP message factories",
|
||||||
|
"keywords": [
|
||||||
|
"factory",
|
||||||
|
"http",
|
||||||
|
"message",
|
||||||
|
"psr",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-factory/tree/1.0.2"
|
||||||
|
},
|
||||||
|
"time": "2023-04-10T20:10:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-message",
|
||||||
|
"version": "2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-message.git",
|
||||||
|
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||||
|
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Message\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP messages",
|
||||||
|
"homepage": "https://github.com/php-fig/http-message",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-message",
|
||||||
|
"psr",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-message/tree/2.0"
|
||||||
|
},
|
||||||
|
"time": "2023-04-04T09:54:51+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ralouphie/getallheaders",
|
||||||
|
"version": "3.0.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||||
|
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||||
|
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.6"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"php-coveralls/php-coveralls": "^2.1",
|
||||||
|
"phpunit/phpunit": "^5 || ^6.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/getallheaders.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ralph Khattar",
|
||||||
|
"email": "ralph.khattar@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A polyfill for getallheaders.",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||||
|
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||||
|
},
|
||||||
|
"time": "2019-03-08T08:55:37+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/deprecation-contracts",
|
||||||
|
"version": "v3.5.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||||
|
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
|
||||||
|
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.5-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/contracts",
|
||||||
|
"url": "https://github.com/symfony/contracts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"function.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A generic function and convention to trigger deprecation notices",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-25T14:20:29+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.6.0"
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue