mirror of
https://github.com/langgenius/dify-plugin-sdks.git
synced 2026-07-22 18:35:29 -04:00
c6f83a63e1
* chore: fix ruff issue * feat(oauth): implement OAuth * feat(invoke-message): refactor message handling and introduce InvokeMessage class * feat(plugin-oauth): add credential_id and credential_type to tool parameters * feat(plugin-oauth): add credential_id and credential_type to tool parameters * chore: update dify_plugin version to 0.5.0b4 and clean up github.yaml * chore: update plugin version to 0.1.2 in manifest.yaml * feat(session): session context and tool backwards invocation credential support * feat(oauth): session context and tool backwards invocation credential support * feat: update README and requirements for OAuth support in version 0.4.2 * feat: add .gitignore to exclude IDE files and secret keys * chore: apply ruff * feat: bump version to 0.4.2b1 * feat: update GitHub plugin configuration for OAuth support and improve credential handling * feat: update .gitignore to exclude dify plugin files and public keys * feat: fix credential validation for GitHub API and bump version to 0.2.1 * feat: update GitHub plugin to support multiple access tokens and bump version to 0.2.5 * chore: apply ruff * feat: add ToolProviderOAuthError for improved OAuth error handling in GitHub plugin * chore: apply ruff * chore: bump version to 0.4.2 * chore: update examples sdk version to 0.4.2 * fix: thread deadlock in PluginRunner when running tests without gevent monkey patching * feat: add support for refreshing OAuth credentials in Plugin and GitHub provider * feat: refactor OAuth credential handling to return structured OAuthCredentials object * apply ruff * feat: refactor OAuth credential handling to use ToolOAuthCredentials for improved structure * feat: reorganize imports in __init__.py for improved clarity and structure * feat: add Microsoft To Do plugin for refresh token example * chore: apply ruff * fix: update author in GitHub configuration and clean up Microsoft To Do schema * chore: bump version to 0.4.2b2 in pyproject.toml * feat: update Microsoft To Do plugin to handle OAuth token encoding and version bump * feat:remove inelegant example * chore: update dify_plugin version to 0.4.2 * chore: bump version to 0.4.2 in pyproject.toml --------- Co-authored-by: Yeuoly <admin@srmxy.cn>
206 lines
6.4 KiB
Python
206 lines
6.4 KiB
Python
import time
|
|
from json import JSONDecodeError, dumps
|
|
|
|
from models.text_embedding.jina_tokenizer import JinaTokenizer
|
|
from requests import post
|
|
|
|
from dify_plugin import TextEmbeddingModel
|
|
from dify_plugin.entities import I18nObject
|
|
from dify_plugin.entities.model import (
|
|
AIModelEntity,
|
|
EmbeddingInputType,
|
|
FetchFrom,
|
|
ModelPropertyKey,
|
|
ModelType,
|
|
PriceType,
|
|
)
|
|
from dify_plugin.entities.model.text_embedding import (
|
|
EmbeddingUsage,
|
|
TextEmbeddingResult,
|
|
)
|
|
from dify_plugin.errors.model import (
|
|
CredentialsValidateFailedError,
|
|
InvokeAuthorizationError,
|
|
InvokeBadRequestError,
|
|
InvokeConnectionError,
|
|
InvokeError,
|
|
InvokeRateLimitError,
|
|
InvokeServerUnavailableError,
|
|
)
|
|
|
|
|
|
class JinaTextEmbeddingModel(TextEmbeddingModel):
|
|
"""
|
|
Model class for Jina text embedding model.
|
|
"""
|
|
|
|
api_base: str = "https://api.jina.ai/v1"
|
|
|
|
def _invoke(
|
|
self,
|
|
model: str,
|
|
credentials: dict,
|
|
texts: list[str],
|
|
user: str | None = None,
|
|
input_type: EmbeddingInputType = EmbeddingInputType.DOCUMENT,
|
|
) -> TextEmbeddingResult:
|
|
"""
|
|
Invoke text embedding model
|
|
|
|
:param model: model name
|
|
:param credentials: model credentials
|
|
:param texts: texts to embed
|
|
:param user: unique user id
|
|
:return: embeddings result
|
|
"""
|
|
api_key = credentials["api_key"]
|
|
if not api_key:
|
|
raise CredentialsValidateFailedError("api_key is required")
|
|
|
|
base_url = credentials.get("base_url", self.api_base)
|
|
base_url = base_url.removesuffix("/")
|
|
|
|
url = base_url + "/embeddings"
|
|
headers = {
|
|
"Authorization": "Bearer " + api_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
def transform_jina_input_text(model, text):
|
|
if model == "jina-clip-v1":
|
|
return {"text": text}
|
|
return text
|
|
|
|
data = {
|
|
"model": model,
|
|
"input": [transform_jina_input_text(model, text) for text in texts],
|
|
}
|
|
|
|
try:
|
|
response = post(url, headers=headers, data=dumps(data)) # noqa: S113
|
|
except Exception as e:
|
|
raise InvokeConnectionError(str(e)) from e
|
|
|
|
if response.status_code != 200:
|
|
try:
|
|
resp = response.json()
|
|
msg = resp["detail"]
|
|
if response.status_code == 401:
|
|
raise InvokeAuthorizationError(msg)
|
|
elif response.status_code == 429:
|
|
raise InvokeRateLimitError(msg)
|
|
elif response.status_code == 500:
|
|
raise InvokeServerUnavailableError(msg)
|
|
else:
|
|
raise InvokeBadRequestError(msg)
|
|
except JSONDecodeError as e:
|
|
raise InvokeServerUnavailableError(
|
|
f"Failed to convert response to json: {e} with text: {response.text}"
|
|
) from e
|
|
|
|
try:
|
|
resp = response.json()
|
|
embeddings = resp["data"]
|
|
usage = resp["usage"]
|
|
except Exception as e:
|
|
raise InvokeServerUnavailableError(
|
|
f"Failed to convert response to json: {e} with text: {response.text}"
|
|
) from e
|
|
|
|
usage = self._calc_response_usage(model=model, credentials=credentials, tokens=usage["total_tokens"])
|
|
|
|
result = TextEmbeddingResult(
|
|
model=model,
|
|
embeddings=[[float(data) for data in x["embedding"]] for x in embeddings],
|
|
usage=usage,
|
|
)
|
|
|
|
return result
|
|
|
|
def get_num_tokens(self, model: str, credentials: dict, texts: list[str]) -> list[int]:
|
|
"""
|
|
Get number of tokens for given prompt messages
|
|
|
|
:param model: model name
|
|
:param credentials: model credentials
|
|
:param texts: texts to embed
|
|
:return:
|
|
"""
|
|
num_tokens = []
|
|
for text in texts:
|
|
# use JinaTokenizer to get num tokens
|
|
num_tokens.append(JinaTokenizer.get_num_tokens(text))
|
|
|
|
return num_tokens
|
|
|
|
def validate_credentials(self, model: str, credentials: dict) -> None:
|
|
"""
|
|
Validate model credentials
|
|
|
|
:param model: model name
|
|
:param credentials: model credentials
|
|
:return:
|
|
"""
|
|
try:
|
|
self._invoke(model=model, credentials=credentials, texts=["ping"])
|
|
except Exception as e:
|
|
raise CredentialsValidateFailedError(f"Credentials validation failed: {e}") from e
|
|
|
|
@property
|
|
def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
|
|
return {
|
|
InvokeConnectionError: [InvokeConnectionError],
|
|
InvokeServerUnavailableError: [InvokeServerUnavailableError],
|
|
InvokeRateLimitError: [InvokeRateLimitError],
|
|
InvokeAuthorizationError: [InvokeAuthorizationError],
|
|
InvokeBadRequestError: [KeyError, InvokeBadRequestError],
|
|
}
|
|
|
|
def _calc_response_usage(self, model: str, credentials: dict, tokens: int) -> EmbeddingUsage:
|
|
"""
|
|
Calculate response usage
|
|
|
|
:param model: model name
|
|
:param credentials: model credentials
|
|
:param tokens: input tokens
|
|
:return: usage
|
|
"""
|
|
# get input price info
|
|
input_price_info = self.get_price(
|
|
model=model,
|
|
credentials=credentials,
|
|
price_type=PriceType.INPUT,
|
|
tokens=tokens,
|
|
)
|
|
|
|
# transform usage
|
|
usage = EmbeddingUsage(
|
|
tokens=tokens,
|
|
total_tokens=tokens,
|
|
unit_price=input_price_info.unit_price,
|
|
price_unit=input_price_info.unit,
|
|
total_price=input_price_info.total_amount,
|
|
currency=input_price_info.currency,
|
|
latency=time.perf_counter() - self.started_at,
|
|
)
|
|
|
|
return usage
|
|
|
|
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity:
|
|
"""
|
|
generate custom model entities from credentials
|
|
"""
|
|
entity = AIModelEntity(
|
|
model=model,
|
|
label=I18nObject(en_US=model),
|
|
model_type=ModelType.TEXT_EMBEDDING,
|
|
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
|
model_properties={
|
|
ModelPropertyKey.CONTEXT_SIZE: int(
|
|
credentials.get("context_size") or 128,
|
|
)
|
|
},
|
|
)
|
|
|
|
return entity
|