Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion edenai_apis/apis/winstonai/config.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
WINSTON_AI_API_URL = "https://api.gowinston.ai/functions/v1"
WINSTON_AI_API_URL = "https://api.gowinston.ai"
177 changes: 14 additions & 163 deletions edenai_apis/apis/winstonai/winstonai_api.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import json
from http import HTTPStatus
from typing import Any, Dict, Optional, Sequence
from uuid import uuid4

import httpx
from typing import Dict, Sequence, Any, Optional
import requests

from edenai_apis.apis.winstonai.config import WINSTON_AI_API_URL
Expand All @@ -24,7 +21,7 @@
from edenai_apis.loaders.loaders import load_provider
from edenai_apis.utils.exception import ProviderException
from edenai_apis.utils.types import ResponseType
from edenai_apis.utils.upload_s3 import aupload_file_bytes_to_s3, upload_file_to_s3
from edenai_apis.utils.upload_s3 import upload_file_to_s3


class WinstonaiApi(ProviderInterface, TextInterface, ImageInterface):
Expand All @@ -48,11 +45,11 @@ def image__ai_detection(
if not file_url and not file:
raise ProviderException("file or file_url required")

payload = json.dumps({"url": file_url or upload_file_to_s3(file, file)})
payload = json.dumps({"url": file_url or upload_file_to_s3(file, file), "version": "latest"})

response = requests.request(
"POST",
f"{self.api_url}/image-detection",
f"{self.api_url}/v2/image-detection",
headers=self.headers,
data=payload,
)
Comment on lines 50 to 55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add timeout to prevent indefinite hangs.

The HTTP request lacks a timeout parameter, which can cause the application to hang indefinitely if the Winston AI server doesn't respond. This is especially problematic for image processing which may take longer.

Apply this diff to add a timeout:

 response = requests.request(
     "POST",
     f"{self.api_url}/v2/image-detection",
     headers=self.headers,
     data=payload,
+    timeout=30,
 )

Based on static analysis hints (Ruff S113).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response = requests.request(
"POST",
f"{self.api_url}/image-detection",
f"{self.api_url}/v2/image-detection",
headers=self.headers,
data=payload,
)
response = requests.request(
"POST",
f"{self.api_url}/v2/image-detection",
headers=self.headers,
data=payload,
timeout=30,
)
🧰 Tools
🪛 Ruff (0.14.6)

50-50: Probable use of requests call without timeout

(S113)

🤖 Prompt for AI Agents
In edenai_apis/apis/winstonai/winstonai_api.py around lines 50 to 55, the
requests.request call has no timeout which can cause indefinite hangs; add a
timeout parameter (e.g., timeout=30) to the request call or, better, use a
configurable attribute like self.timeout with a sensible default and pass
timeout=self.timeout to requests.request so the HTTP call fails fast when the
Winston AI server is unresponsive.

Expand All @@ -77,67 +74,27 @@ def image__ai_detection(
standardized_response=standardized_response,
)

async def image__aai_detection(
self, file: Optional[str] = None, file_url: Optional[str] = None, **kwargs
) -> ResponseType[ImageAiDetectionDataclass]:
if not file_url and not file:
raise ProviderException("file or file_url required")

if file and not file_url:
from io import BytesIO

with open(file, "rb") as f:
file_bytes = BytesIO(f.read())
url = await aupload_file_bytes_to_s3(file_bytes, file)
else:
url = file_url

payload = json.dumps({"url": url})

async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, read=120.0)) as client:
response = await client.request(
"POST",
f"{self.api_url}/image-detection",
headers=self.headers,
data=payload,
)

if response.status_code != 200:
raise ProviderException(response.json(), code=response.status_code)

original_response = response.json()

score = 1 - original_response.get("score") / 100
prediction = ImageAiDetectionDataclass.set_label_based_on_score(score)
if score is None:
raise ProviderException(response.json())

standardized_response = ImageAiDetectionDataclass(
ai_score=score,
prediction=prediction,
)

return ResponseType[ImageAiDetectionDataclass](
original_response=original_response,
standardized_response=standardized_response,
)

def text__ai_detection(
self, text: str, provider_params: Optional[Dict[str, Any]] = None, **kwargs
) -> ResponseType[AiDetectionDataClass]:
if provider_params is None:
provider_params = {}

# WinstonAI has a minimum characters limit of 300 characters for his API
if len(text) < 300:
raise ProviderException("Make sure the text is at least 300 characters long before sending a detection request")

payload = json.dumps(
{
"text": text,
"sentences": True,
"language": provider_params.get("language", "en"),
"version": provider_params.get("version", "2.0"),
"language": provider_params.get("language", "auto"),
"version": provider_params.get("version", "latest"),
}
)

response = requests.request(
"POST", f"{self.api_url}/predict", headers=self.headers, data=payload
"POST", f"{self.api_url}/v2/ai-content-detection", headers=self.headers, data=payload
)
Comment on lines 96 to 98
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add timeout to prevent indefinite hangs.

The HTTP request lacks a timeout parameter, which can cause the application to hang indefinitely if the Winston AI server doesn't respond.

Apply this diff to add a timeout:

 response = requests.request(
-    "POST", f"{self.api_url}/v2/ai-content-detection", headers=self.headers, data=payload
+    "POST", f"{self.api_url}/v2/ai-content-detection", headers=self.headers, data=payload, timeout=30
 )

Based on static analysis hints (Ruff S113).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response = requests.request(
"POST", f"{self.api_url}/predict", headers=self.headers, data=payload
"POST", f"{self.api_url}/v2/ai-content-detection", headers=self.headers, data=payload
)
response = requests.request(
"POST", f"{self.api_url}/v2/ai-content-detection", headers=self.headers, data=payload, timeout=30
)
🧰 Tools
🪛 Ruff (0.14.6)

96-96: Probable use of requests call without timeout

(S113)

🤖 Prompt for AI Agents
In edenai_apis/apis/winstonai/winstonai_api.py around lines 96 to 98, the
requests.request call is missing a timeout which can cause indefinite hangs;
update the call to include a timeout argument (e.g., timeout=30) or use a
configurable attribute like self.request_timeout, and ensure the timeout value
is used in the request invocation so the HTTP call fails fast instead of
blocking indefinitely.


if response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
Expand Down Expand Up @@ -171,54 +128,6 @@ def text__ai_detection(
standardized_response=standardized_response,
)

async def text__aai_detection(
self, text: str, provider_params: Optional[Dict[str, Any]] = None, **kwargs
) -> ResponseType[AiDetectionDataClass]:
if provider_params is None:
provider_params = {}
payload = {
"text": text,
"sentences": True,
"language": provider_params.get("language", "en"),
"version": provider_params.get("version", "2.0"),
}

async with httpx.AsyncClient(timeout=60) as client:
response = await client.post(
f"{self.api_url}/predict", headers=self.headers, data=payload
)

if response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
raise ProviderException("Internal Server Error")

if response.status_code != 200:
raise ProviderException(response.json(), code=response.status_code)

original_response = response.json()
score = original_response.get("score") / 100
sentences = original_response.get("sentences")

if score is None or sentences is None:
raise ProviderException(response.json())

items: Sequence[AiDetectionItem] = [
AiDetectionItem(
text=sentence["text"],
ai_score=1 - (sentence["score"] / 100),
prediction=AiDetectionItem.set_label_based_on_score(
1 - (sentence["score"] / 100)
),
)
for sentence in sentences
]

standardized_response = AiDetectionDataClass(ai_score=1 - score, items=items)

return ResponseType[AiDetectionDataClass](
original_response=original_response,
standardized_response=standardized_response,
)

def text__plagia_detection(
self,
text: str,
Expand All @@ -232,70 +141,12 @@ def text__plagia_detection(
{
"text": text,
"language": provider_params.get("language", "en"),
"version": provider_params.get("version", "2.0"),
}
)

response = requests.request(
"POST", f"{self.api_url}/plagiarism", headers=self.headers, data=payload
)

if response.status_code != 200:
raise ProviderException(response.json(), code=response.status_code)

original_response = response.json()
results = original_response.get("results")

if results is None:
raise ProviderException(response.json())

standardized_response = PlagiaDetectionDataClass(
plagia_score=original_response["score"],
items=[
PlagiaDetectionItem(
text=result["title"],
candidates=[
PlagiaDetectionCandidate(
url=result["url"],
plagia_score=1,
prediction="plagiarized",
plagiarized_text=excerpt,
)
for excerpt in result["excerpts"]
],
)
for result in results
],
)

return ResponseType[PlagiaDetectionDataClass](
original_response=original_response,
standardized_response=standardized_response,
)

async def text__aplagia_detection(
self,
text: str,
title: str = "",
provider_params: Optional[Dict[str, Any]] = None,
**kwargs,
) -> ResponseType[PlagiaDetectionDataClass]:
if provider_params is None:
provider_params = {}
payload = json.dumps(
{
"text": text,
"language": provider_params.get("language", "en"),
"version": provider_params.get("version", "2.0"),
}
"POST", f"{self.api_url}/v1/plagiarism", headers=self.headers, data=payload
)
Comment on lines 147 to 149
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add timeout to prevent indefinite hangs.

The HTTP request lacks a timeout parameter, which can cause the application to hang indefinitely if the Winston AI server doesn't respond.

Apply this diff to add a timeout:

 response = requests.request(
-    "POST", f"{self.api_url}/v1/plagiarism", headers=self.headers, data=payload
+    "POST", f"{self.api_url}/v1/plagiarism", headers=self.headers, data=payload, timeout=30
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response = requests.request(
"POST", f"{self.api_url}/plagiarism", headers=self.headers, data=payload
)
if response.status_code != 200:
raise ProviderException(response.json(), code=response.status_code)
original_response = response.json()
results = original_response.get("results")
if results is None:
raise ProviderException(response.json())
standardized_response = PlagiaDetectionDataClass(
plagia_score=original_response["score"],
items=[
PlagiaDetectionItem(
text=result["title"],
candidates=[
PlagiaDetectionCandidate(
url=result["url"],
plagia_score=1,
prediction="plagiarized",
plagiarized_text=excerpt,
)
for excerpt in result["excerpts"]
],
)
for result in results
],
)
return ResponseType[PlagiaDetectionDataClass](
original_response=original_response,
standardized_response=standardized_response,
)
async def text__aplagia_detection(
self,
text: str,
title: str = "",
provider_params: Optional[Dict[str, Any]] = None,
**kwargs,
) -> ResponseType[PlagiaDetectionDataClass]:
if provider_params is None:
provider_params = {}
payload = json.dumps(
{
"text": text,
"language": provider_params.get("language", "en"),
"version": provider_params.get("version", "2.0"),
}
"POST", f"{self.api_url}/v1/plagiarism", headers=self.headers, data=payload
)
response = requests.request(
"POST", f"{self.api_url}/v1/plagiarism", headers=self.headers, data=payload, timeout=30
)
🧰 Tools
🪛 Ruff (0.14.6)

147-147: Probable use of requests call without timeout

(S113)

🤖 Prompt for AI Agents
In edenai_apis/apis/winstonai/winstonai_api.py around lines 147 to 149, the POST
request to the Winston AI /v1/plagiarism endpoint is missing a timeout, which
can cause indefinite hangs; update the requests.request call to include a
reasonable timeout parameter (e.g., timeout=30) so the call fails fast on
unresponsive servers, and ensure any surrounding error handling remains
compatible with requests.exceptions.Timeout.

async with httpx.AsyncClient(timeout=60) as client:
response = await client.post(
f"{self.api_url}/plagiarism", headers=self.headers, data=payload
)

if response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
raise ProviderException("Internal Server Error")

if response.status_code != 200:
raise ProviderException(response.json(), code=response.status_code)
Expand Down Expand Up @@ -328,4 +179,4 @@ async def text__aplagia_detection(
return ResponseType[PlagiaDetectionDataClass](
original_response=original_response,
standardized_response=standardized_response,
)
)
Loading