uipath-langchain 0.0.104__py3-none-any.whl → 0.0.107__py3-none-any.whl
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.
Potentially problematic release.
This version of uipath-langchain might be problematic. Click here for more details.
- uipath_langchain/_cli/_runtime/_output.py +0 -1
- uipath_langchain/_cli/_utils/_graph.py +5 -3
- uipath_langchain/_cli/cli_new.py +5 -5
- uipath_langchain/retrievers/context_grounding_retriever.py +6 -0
- uipath_langchain/vectorstores/__init__.py +3 -0
- {uipath_langchain-0.0.104.dist-info → uipath_langchain-0.0.107.dist-info}/METADATA +1 -1
- {uipath_langchain-0.0.104.dist-info → uipath_langchain-0.0.107.dist-info}/RECORD +10 -15
- uipath_langchain/chat/utils/__init__.py +0 -0
- uipath_langchain/chat/utils/_chat_types.py +0 -54
- uipath_langchain/utils/__init__.py +0 -3
- uipath_langchain/utils/_request_mixin.py +0 -488
- uipath_langchain/utils/_settings.py +0 -91
- uipath_langchain/utils/_sleep_policy.py +0 -41
- {uipath_langchain-0.0.104.dist-info → uipath_langchain-0.0.107.dist-info}/WHEEL +0 -0
- {uipath_langchain-0.0.104.dist-info → uipath_langchain-0.0.107.dist-info}/entry_points.txt +0 -0
- {uipath_langchain-0.0.104.dist-info → uipath_langchain-0.0.107.dist-info}/licenses/LICENSE +0 -0
|
@@ -257,7 +257,6 @@ class LangGraphOutputProcessor:
|
|
|
257
257
|
trigger_type=UiPathResumeTriggerType.ACTION,
|
|
258
258
|
item_key=action.key,
|
|
259
259
|
)
|
|
260
|
-
return
|
|
261
260
|
if isinstance(self.interrupt_info, InterruptInfo):
|
|
262
261
|
uipath_sdk = UiPath()
|
|
263
262
|
if self.interrupt_info.type is UiPathResumeTriggerType.JOB:
|
|
@@ -155,9 +155,11 @@ class LangGraphConfig:
|
|
|
155
155
|
env_path = os.path.abspath(os.path.normpath(env_file))
|
|
156
156
|
if os.path.exists(env_path):
|
|
157
157
|
if not load_dotenv(env_path):
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
# log warning only if dotenv is not empty
|
|
159
|
+
if os.path.getsize(env_path) > 0:
|
|
160
|
+
logger.warning(
|
|
161
|
+
f"Could not load environment variables from {env_path}"
|
|
162
|
+
)
|
|
161
163
|
else:
|
|
162
164
|
logger.debug(f"Loaded environment variables from {env_path}")
|
|
163
165
|
|
uipath_langchain/_cli/cli_new.py
CHANGED
|
@@ -31,7 +31,7 @@ version = "0.0.1"
|
|
|
31
31
|
description = "{project_name}"
|
|
32
32
|
authors = [{{ name = "John Doe", email = "john.doe@myemail.com" }}]
|
|
33
33
|
dependencies = [
|
|
34
|
-
"uipath-langchain>=0.0.
|
|
34
|
+
"uipath-langchain>=0.0.106",
|
|
35
35
|
"langchain-anthropic>=0.3.8",
|
|
36
36
|
]
|
|
37
37
|
requires-python = ">=3.10"
|
|
@@ -54,14 +54,14 @@ def langgraph_new_middleware(name: str) -> MiddlewareResult:
|
|
|
54
54
|
console.success("Created 'langgraph.json' file.")
|
|
55
55
|
generate_pyproject(directory, name)
|
|
56
56
|
console.success("Created 'pyproject.toml' file.")
|
|
57
|
-
os.system("uv sync")
|
|
58
|
-
ctx = click.get_current_context()
|
|
59
|
-
init_cmd = ctx.parent.command.get_command(ctx, "init") # type: ignore
|
|
60
|
-
ctx.invoke(init_cmd)
|
|
61
57
|
console.config(
|
|
62
58
|
f""" Please ensure to define either {click.style("ANTHROPIC_API_KEY", fg="bright_yellow")} or {click.style("OPENAI_API_KEY", fg="bright_yellow")} in your .env file. """
|
|
63
59
|
)
|
|
60
|
+
init_command = """uipath init"""
|
|
64
61
|
run_command = """uipath run agent '{"topic": "UiPath"}'"""
|
|
62
|
+
console.hint(
|
|
63
|
+
f""" Initialize project: {click.style(init_command, fg="cyan")}"""
|
|
64
|
+
)
|
|
65
65
|
console.hint(f""" Run agent: {click.style(run_command, fg="cyan")}""")
|
|
66
66
|
return MiddlewareResult(should_continue=False)
|
|
67
67
|
except Exception as e:
|
|
@@ -11,6 +11,8 @@ from uipath import UiPath
|
|
|
11
11
|
|
|
12
12
|
class ContextGroundingRetriever(BaseRetriever):
|
|
13
13
|
index_name: str
|
|
14
|
+
folder_path: Optional[str] = None
|
|
15
|
+
folder_key: Optional[str] = None
|
|
14
16
|
uipath_sdk: Optional[UiPath] = None
|
|
15
17
|
number_of_results: Optional[int] = 10
|
|
16
18
|
|
|
@@ -24,6 +26,8 @@ class ContextGroundingRetriever(BaseRetriever):
|
|
|
24
26
|
self.index_name,
|
|
25
27
|
query,
|
|
26
28
|
self.number_of_results if self.number_of_results is not None else 10,
|
|
29
|
+
folder_path=self.folder_path,
|
|
30
|
+
folder_key=self.folder_key,
|
|
27
31
|
)
|
|
28
32
|
|
|
29
33
|
return [
|
|
@@ -47,6 +51,8 @@ class ContextGroundingRetriever(BaseRetriever):
|
|
|
47
51
|
self.index_name,
|
|
48
52
|
query,
|
|
49
53
|
self.number_of_results if self.number_of_results is not None else 10,
|
|
54
|
+
folder_path=self.folder_path,
|
|
55
|
+
folder_key=self.folder_key,
|
|
50
56
|
)
|
|
51
57
|
|
|
52
58
|
return [
|
|
@@ -2,42 +2,37 @@ uipath_langchain/__init__.py,sha256=VBrvQn7d3nuOdN7zEnV2_S-uhmkjgEIlXiFVeZxZakQ,
|
|
|
2
2
|
uipath_langchain/middlewares.py,sha256=tre7o9DFMgWk1DJiEEUmT6_wiP-PPkWtKmG0iOyvr9c,509
|
|
3
3
|
uipath_langchain/_cli/__init__.py,sha256=juqd9PbXs4yg45zMJ7BHAOPQjb7sgEbWE9InBtGZhfo,24
|
|
4
4
|
uipath_langchain/_cli/cli_init.py,sha256=B5BVUA7pDvKyRRGEx5mgmeE5SJvLPM3cnLGt6a9iixY,7417
|
|
5
|
-
uipath_langchain/_cli/cli_new.py,sha256=
|
|
5
|
+
uipath_langchain/_cli/cli_new.py,sha256=dL8-Rri6u67ZZdbb4nT38A5xD_Q3fVnG0UK9VSeKaqg,2563
|
|
6
6
|
uipath_langchain/_cli/cli_run.py,sha256=8-7NBguH3ACwN3bHEtyj2d3N-FFLJQLaiHDNI_3hnQE,2863
|
|
7
7
|
uipath_langchain/_cli/_runtime/_context.py,sha256=wr4aNn06ReIXmetEZ6b6AnpAt64p13anQ2trZ5Bzgio,807
|
|
8
8
|
uipath_langchain/_cli/_runtime/_escalation.py,sha256=oA5NvZvCo8ngELFJRyhZNM69DxVHrshhMY6CUk_cukQ,8055
|
|
9
9
|
uipath_langchain/_cli/_runtime/_exception.py,sha256=USKkLYkG-dzjX3fEiMMOHnVUpiXJs_xF0OQXCCOvbYM,546
|
|
10
10
|
uipath_langchain/_cli/_runtime/_input.py,sha256=gKzPaGW-EzgeAskWJjbCWnfZRLu_BM7lCXkq0XkVGLU,5614
|
|
11
|
-
uipath_langchain/_cli/_runtime/_output.py,sha256=
|
|
11
|
+
uipath_langchain/_cli/_runtime/_output.py,sha256=zGSGEUiWHg-q8-Ccm_NU8Vi-7oWCzq5GN1oFqO9FV9Q,16099
|
|
12
12
|
uipath_langchain/_cli/_runtime/_runtime.py,sha256=ai3qxNmdPACxowq7WcNBn9m8-KXe4vFwvpBn0lh6kmQ,11689
|
|
13
13
|
uipath_langchain/_cli/_templates/langgraph.json.template,sha256=eeh391Gta_hoRgaNaZ58nW1LNvCVXA7hlAH6l7Veous,107
|
|
14
14
|
uipath_langchain/_cli/_templates/main.py.template,sha256=9JEyPxwc4Ce8Dfd2UAgHgpwkkjuXwWXOQZ-65P53QuM,1195
|
|
15
|
-
uipath_langchain/_cli/_utils/_graph.py,sha256=
|
|
15
|
+
uipath_langchain/_cli/_utils/_graph.py,sha256=P7m03i6kcLda8XVpVtppcM8GOrSW62zWcv3rCR1H5zs,7086
|
|
16
16
|
uipath_langchain/_utils/__init__.py,sha256=WoY66enCygRXTh6v5B1UrRcFCnQYuPJ8oqDkwomXzLc,194
|
|
17
17
|
uipath_langchain/_utils/_request_mixin.py,sha256=t_1HWBxqEl-wsSk9ubmIM-8vs9BlNy4ZVBxtDxktn6U,18489
|
|
18
18
|
uipath_langchain/_utils/_settings.py,sha256=MhwEVj4gVRSar0RBf2w2hTjO-5Qm-HpCuufqN3gSWjA,3390
|
|
19
19
|
uipath_langchain/_utils/_sleep_policy.py,sha256=e9pHdjmcCj4CVoFM1jMyZFelH11YatsgWfpyrfXzKBQ,1251
|
|
20
20
|
uipath_langchain/chat/__init__.py,sha256=WDcvy91ixvZ3Mq7Ae94g5CjyQwXovDBnEv1NlD5SXBE,116
|
|
21
21
|
uipath_langchain/chat/models.py,sha256=sLz8yzEMUMSNsCFyywRxFwe2JisR3TP-n1vbeRKl9H8,10225
|
|
22
|
-
uipath_langchain/chat/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
uipath_langchain/chat/utils/_chat_types.py,sha256=iTUh4gY8ML2yLzTQ7H4ZFXChA8RbPz7NgkJK4x7fSbs,1226
|
|
24
22
|
uipath_langchain/embeddings/__init__.py,sha256=QICtYB58ZyqFfDQrEaO8lTEgAU5NuEKlR7iIrS0OBtc,156
|
|
25
23
|
uipath_langchain/embeddings/embeddings.py,sha256=gntzTfwO1pHbgnXiPdfETJaaurvQWqxVUCH75VMah54,4274
|
|
26
24
|
uipath_langchain/retrievers/__init__.py,sha256=rOn7PyyHgZ4pMnXWPkGqmuBmx8eGuo-Oyndo7Wm9IUU,108
|
|
27
|
-
uipath_langchain/retrievers/context_grounding_retriever.py,sha256=
|
|
25
|
+
uipath_langchain/retrievers/context_grounding_retriever.py,sha256=YLCIwy89LhLnNqcM0YJ5mZoeNyCs5UiKD3Wly8gnW1E,2239
|
|
28
26
|
uipath_langchain/tracers/AsyncUiPathTracer.py,sha256=VLqoegxa8J3VwMCkGicO626nSzyCQv-Zj14lZrWXA2Y,8900
|
|
29
27
|
uipath_langchain/tracers/UiPathTracer.py,sha256=V5g1nlB0TI1wYvUIkfCEcAdhy4thqeMBmjflWtxc-_M,5601
|
|
30
28
|
uipath_langchain/tracers/__init__.py,sha256=MwrQh6iuPXMS72S5EX0JdCAX0TKe-l7fIPGV3EG0Ssk,256
|
|
31
29
|
uipath_langchain/tracers/_events.py,sha256=CJri76SSdu7rGJIkXurJ2C5sQahfSK4E5UWwWYdEAtE,922
|
|
32
30
|
uipath_langchain/tracers/_instrument_traceable.py,sha256=0e841zVzcPWjOGtmBx0GeHbq3JoqsmWv6gVPzDOKNTM,13496
|
|
33
31
|
uipath_langchain/tracers/_utils.py,sha256=JOT1tKMdvqjMDtj2WbmbOWMeMlTXBWavxWpogX7KlRA,1543
|
|
34
|
-
uipath_langchain/
|
|
35
|
-
uipath_langchain/utils/_request_mixin.py,sha256=WFyTDyAthSci1DRwUwS21I3hLntD7HdVzYc0ZPoi3ys,18296
|
|
36
|
-
uipath_langchain/utils/_settings.py,sha256=MhwEVj4gVRSar0RBf2w2hTjO-5Qm-HpCuufqN3gSWjA,3390
|
|
37
|
-
uipath_langchain/utils/_sleep_policy.py,sha256=e9pHdjmcCj4CVoFM1jMyZFelH11YatsgWfpyrfXzKBQ,1251
|
|
32
|
+
uipath_langchain/vectorstores/__init__.py,sha256=w8qs1P548ud1aIcVA_QhBgf_jZDrRMK5Lono78yA8cs,114
|
|
38
33
|
uipath_langchain/vectorstores/context_grounding_vectorstore.py,sha256=eTa5sX43-ydB1pj9VNHUPbB-hC36fZK_CGrNe5U2Nrw,9393
|
|
39
|
-
uipath_langchain-0.0.
|
|
40
|
-
uipath_langchain-0.0.
|
|
41
|
-
uipath_langchain-0.0.
|
|
42
|
-
uipath_langchain-0.0.
|
|
43
|
-
uipath_langchain-0.0.
|
|
34
|
+
uipath_langchain-0.0.107.dist-info/METADATA,sha256=qlngAR2W0uO6XsnBKP7cwCC9nQetiEihBoeKOXwGhcE,4166
|
|
35
|
+
uipath_langchain-0.0.107.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
36
|
+
uipath_langchain-0.0.107.dist-info/entry_points.txt,sha256=FUtzqGOEntlJKMJIXhQUfT7ZTbQmGhke1iCmDWZaQZI,81
|
|
37
|
+
uipath_langchain-0.0.107.dist-info/licenses/LICENSE,sha256=JDpt-uotAkHFmxpwxi6gwx6HQ25e-lG4U_Gzcvgp7JY,1063
|
|
38
|
+
uipath_langchain-0.0.107.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional, Union
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class ToolCall(BaseModel):
|
|
7
|
-
id: str
|
|
8
|
-
name: str
|
|
9
|
-
arguments: Dict[Any, Any]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Message(BaseModel):
|
|
13
|
-
role: str
|
|
14
|
-
content: Optional[Union[str, Dict[Any, Any]]] = None
|
|
15
|
-
tool_calls: Optional[List[ToolCall]] = None
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Tool(BaseModel):
|
|
19
|
-
name: str
|
|
20
|
-
description: str
|
|
21
|
-
parameters: Optional[Dict[Any, Any]] = None
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ToolChoice(BaseModel):
|
|
25
|
-
type: str
|
|
26
|
-
name: Optional[str] = None
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ChatCompletionRequest(BaseModel):
|
|
30
|
-
max_tokens: int
|
|
31
|
-
# n: int
|
|
32
|
-
# top_p: float
|
|
33
|
-
temperature: float
|
|
34
|
-
frequency_penalty: float
|
|
35
|
-
presence_penalty: float
|
|
36
|
-
messages: List[Message]
|
|
37
|
-
tools: Optional[List[Tool]] = None
|
|
38
|
-
tool_choice: Optional[ToolChoice] = None
|
|
39
|
-
response_format: Optional[Dict[str, Any]] = None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class ModelSettings(BaseModel):
|
|
43
|
-
top_p: float = 1.0
|
|
44
|
-
n: int = 1
|
|
45
|
-
temperature: float = 0.0
|
|
46
|
-
frequency_penalty: float = 0.0
|
|
47
|
-
presence_penalty: float = 0.0
|
|
48
|
-
max_tokens: int = 4096
|
|
49
|
-
tool_choice: Optional[str] = None
|
|
50
|
-
enforced_tool_name: Optional[str] = None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class UiPathOutput(BaseModel):
|
|
54
|
-
output_field: str = Field(..., description="The output field")
|
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
# mypy: disable-error-code="no-redef,arg-type"
|
|
2
|
-
import json
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
import time
|
|
6
|
-
from typing import Any, Dict, List, Mapping, Optional
|
|
7
|
-
|
|
8
|
-
import httpx
|
|
9
|
-
import openai
|
|
10
|
-
from langchain_core.embeddings import Embeddings
|
|
11
|
-
from langchain_core.language_models.chat_models import _cleanup_llm_representation
|
|
12
|
-
from pydantic import BaseModel, Field, SecretStr
|
|
13
|
-
from tenacity import (
|
|
14
|
-
AsyncRetrying,
|
|
15
|
-
Retrying,
|
|
16
|
-
retry_if_exception_type,
|
|
17
|
-
stop_after_attempt,
|
|
18
|
-
wait_exponential_jitter,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
from uipath_langchain.utils._settings import (
|
|
22
|
-
UiPathClientFactorySettings,
|
|
23
|
-
UiPathClientSettings,
|
|
24
|
-
get_uipath_token_header,
|
|
25
|
-
)
|
|
26
|
-
from uipath_langchain.utils._sleep_policy import before_sleep_log
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_from_uipath_url():
|
|
30
|
-
url = os.getenv("UIPATH_URL")
|
|
31
|
-
if url:
|
|
32
|
-
return "/".join(url.split("/", 3)[:3])
|
|
33
|
-
return None
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class UiPathRequestMixin(BaseModel):
|
|
37
|
-
class Config:
|
|
38
|
-
arbitrary_types_allowed = True
|
|
39
|
-
|
|
40
|
-
default_headers: Optional[Mapping[str, str]] = {
|
|
41
|
-
"X-UiPath-Streaming-Enabled": "false",
|
|
42
|
-
}
|
|
43
|
-
model_name: Optional[str] = Field(
|
|
44
|
-
default_factory=lambda: os.getenv("UIPATH_MODEL_NAME", "gpt-4o-2024-08-06"),
|
|
45
|
-
alias="model",
|
|
46
|
-
)
|
|
47
|
-
settings: Optional[UiPathClientSettings] = None
|
|
48
|
-
client_id: Optional[str] = Field(
|
|
49
|
-
default_factory=lambda: os.getenv("UIPATH_CLIENT_ID")
|
|
50
|
-
)
|
|
51
|
-
client_secret: Optional[str] = Field(
|
|
52
|
-
default_factory=lambda: os.getenv("UIPATH_CLIENT_SECRET")
|
|
53
|
-
)
|
|
54
|
-
base_url: Optional[str] = Field(
|
|
55
|
-
default_factory=lambda data: getattr(data["settings"], "base_url", None)
|
|
56
|
-
or os.getenv("UIPATH_BASE_URL")
|
|
57
|
-
or get_from_uipath_url(),
|
|
58
|
-
alias="azure_endpoint",
|
|
59
|
-
)
|
|
60
|
-
access_token: Optional[str] = Field(
|
|
61
|
-
default_factory=lambda data: (
|
|
62
|
-
getattr(data["settings"], "access_token", None)
|
|
63
|
-
or os.getenv("UIPATH_ACCESS_TOKEN") # Environment variable
|
|
64
|
-
or os.getenv("UIPATH_SERVICE_TOKEN") # Environment variable
|
|
65
|
-
or get_uipath_token_header(
|
|
66
|
-
UiPathClientFactorySettings(
|
|
67
|
-
UIPATH_BASE_URL=data["base_url"],
|
|
68
|
-
UIPATH_CLIENT_ID=data["client_id"],
|
|
69
|
-
UIPATH_CLIENT_SECRET=data["client_secret"],
|
|
70
|
-
)
|
|
71
|
-
) # Get service token from UiPath
|
|
72
|
-
)
|
|
73
|
-
)
|
|
74
|
-
org_id: Any = Field(
|
|
75
|
-
default_factory=lambda data: getattr(data["settings"], "org_id", None)
|
|
76
|
-
or os.getenv("UIPATH_ORGANIZATION_ID", "")
|
|
77
|
-
)
|
|
78
|
-
tenant_id: Any = Field(
|
|
79
|
-
default_factory=lambda data: getattr(data["settings"], "tenant_id", None)
|
|
80
|
-
or os.getenv("UIPATH_TENANT_ID", "")
|
|
81
|
-
)
|
|
82
|
-
requesting_product: Any = Field(
|
|
83
|
-
default_factory=lambda data: getattr(
|
|
84
|
-
data["settings"], "requesting_product", None
|
|
85
|
-
)
|
|
86
|
-
or os.getenv("UIPATH_REQUESTING_PRODUCT", "")
|
|
87
|
-
)
|
|
88
|
-
requesting_feature: Any = Field(
|
|
89
|
-
default_factory=lambda data: getattr(
|
|
90
|
-
data["settings"], "requesting_feature", None
|
|
91
|
-
)
|
|
92
|
-
or os.getenv("UIPATH_REQUESTING_FEATURE", "")
|
|
93
|
-
)
|
|
94
|
-
default_request_timeout: Any = Field(
|
|
95
|
-
default_factory=lambda data: float(
|
|
96
|
-
getattr(data["settings"], "timeout_seconds", None)
|
|
97
|
-
or os.getenv("UIPATH_TIMEOUT_SECONDS", "120")
|
|
98
|
-
),
|
|
99
|
-
alias="timeout",
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
openai_api_version: Optional[str] = Field(
|
|
103
|
-
default_factory=lambda: os.getenv("OPENAI_API_VERSION", "2024-08-01-preview"),
|
|
104
|
-
alias="api_version",
|
|
105
|
-
)
|
|
106
|
-
include_account_id: bool = False
|
|
107
|
-
temperature: Optional[float] = 0.0
|
|
108
|
-
max_tokens: Optional[int] = 1000
|
|
109
|
-
frequency_penalty: Optional[float] = None
|
|
110
|
-
presence_penalty: Optional[float] = None
|
|
111
|
-
|
|
112
|
-
logger: Optional[logging.Logger] = None
|
|
113
|
-
max_retries: Optional[int] = 5
|
|
114
|
-
base_delay: float = 5.0
|
|
115
|
-
max_delay: float = 60.0
|
|
116
|
-
|
|
117
|
-
_url: Optional[str] = None
|
|
118
|
-
_auth_headers: Optional[Dict[str, str]] = None
|
|
119
|
-
|
|
120
|
-
# required to instantiate AzureChatOpenAI subclasses
|
|
121
|
-
azure_endpoint: Optional[str] = Field(
|
|
122
|
-
default="placeholder", description="Bypassed Azure endpoint"
|
|
123
|
-
)
|
|
124
|
-
openai_api_key: Optional[SecretStr] = Field(
|
|
125
|
-
default=SecretStr("placeholder"), description="Bypassed API key"
|
|
126
|
-
)
|
|
127
|
-
# required to instatiate ChatAnthropic subclasses (will be needed when passthrough is implemented for Anthropic models)
|
|
128
|
-
stop_sequences: Optional[List[str]] = Field(
|
|
129
|
-
default=None, description="Bypassed stop sequence"
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
def _request(
|
|
133
|
-
self, url: str, request_body: Dict[str, Any], headers: Dict[str, str]
|
|
134
|
-
) -> Dict[str, Any]:
|
|
135
|
-
"""Run an asynchronous call to the LLM."""
|
|
136
|
-
# if self.logger:
|
|
137
|
-
# self.logger.info(f"Completion request: {request_body['messages'][:2]}")
|
|
138
|
-
with httpx.Client(
|
|
139
|
-
event_hooks={
|
|
140
|
-
"request": [self._log_request_duration],
|
|
141
|
-
"response": [self._log_response_duration],
|
|
142
|
-
}
|
|
143
|
-
) as client:
|
|
144
|
-
response = client.post(
|
|
145
|
-
url,
|
|
146
|
-
headers=headers,
|
|
147
|
-
json=request_body,
|
|
148
|
-
timeout=self.default_request_timeout,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
# Handle HTTP errors and map them to OpenAI exceptions
|
|
152
|
-
try:
|
|
153
|
-
response.raise_for_status()
|
|
154
|
-
except httpx.HTTPStatusError as err:
|
|
155
|
-
if self.logger:
|
|
156
|
-
self.logger.error(
|
|
157
|
-
"Error querying UiPath: %s (%s)",
|
|
158
|
-
err.response.reason_phrase,
|
|
159
|
-
err.response.status_code,
|
|
160
|
-
extra={
|
|
161
|
-
"ActionName": self.settings.action_name,
|
|
162
|
-
"ActionId": self.settings.action_id,
|
|
163
|
-
}
|
|
164
|
-
if self.settings
|
|
165
|
-
else None,
|
|
166
|
-
)
|
|
167
|
-
raise self._make_status_error_from_response(err.response) from err
|
|
168
|
-
|
|
169
|
-
return response.json()
|
|
170
|
-
|
|
171
|
-
def _call(
|
|
172
|
-
self, url: str, request_body: Dict[str, Any], headers: Dict[str, str]
|
|
173
|
-
) -> Dict[str, Any]:
|
|
174
|
-
"""Run a synchronous call with retries to LLM"""
|
|
175
|
-
if self.max_retries is None:
|
|
176
|
-
return self._request(url, request_body, headers)
|
|
177
|
-
|
|
178
|
-
retryer = Retrying(
|
|
179
|
-
stop=stop_after_attempt(self.max_retries),
|
|
180
|
-
wait=wait_exponential_jitter(
|
|
181
|
-
initial=self.base_delay,
|
|
182
|
-
max=self.max_delay,
|
|
183
|
-
jitter=1.0,
|
|
184
|
-
),
|
|
185
|
-
retry=retry_if_exception_type(
|
|
186
|
-
(openai.RateLimitError, httpx.TimeoutException)
|
|
187
|
-
),
|
|
188
|
-
reraise=True,
|
|
189
|
-
before_sleep=before_sleep_log(self.logger, logging.WARNING)
|
|
190
|
-
if self.logger is not None
|
|
191
|
-
else None,
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
try:
|
|
195
|
-
return retryer(self._request, url, request_body, headers)
|
|
196
|
-
except openai.APIStatusError as err:
|
|
197
|
-
if self.logger:
|
|
198
|
-
self.logger.error(
|
|
199
|
-
"Failed querying LLM after retries: %s",
|
|
200
|
-
err,
|
|
201
|
-
extra={
|
|
202
|
-
"ActionName": self.settings.action_name,
|
|
203
|
-
"ActionId": self.settings.action_id,
|
|
204
|
-
}
|
|
205
|
-
if self.settings
|
|
206
|
-
else None,
|
|
207
|
-
)
|
|
208
|
-
raise err
|
|
209
|
-
|
|
210
|
-
async def _arequest(
|
|
211
|
-
self, url: str, request_body: Dict[str, Any], headers: Dict[str, str]
|
|
212
|
-
) -> Dict[str, Any]:
|
|
213
|
-
# if self.logger:
|
|
214
|
-
# self.logger.info(f"Completion request: {request_body['messages'][:2]}")
|
|
215
|
-
async with httpx.AsyncClient(
|
|
216
|
-
event_hooks={
|
|
217
|
-
"request": [self._alog_request_duration],
|
|
218
|
-
"response": [self._alog_response_duration],
|
|
219
|
-
}
|
|
220
|
-
) as client:
|
|
221
|
-
response = await client.post(
|
|
222
|
-
url,
|
|
223
|
-
headers=headers,
|
|
224
|
-
json=request_body,
|
|
225
|
-
timeout=self.default_request_timeout,
|
|
226
|
-
)
|
|
227
|
-
# Handle HTTP errors and map them to OpenAI exceptions
|
|
228
|
-
try:
|
|
229
|
-
response.raise_for_status()
|
|
230
|
-
except httpx.HTTPStatusError as err:
|
|
231
|
-
if self.logger:
|
|
232
|
-
self.logger.error(
|
|
233
|
-
"Error querying LLM: %s (%s)",
|
|
234
|
-
err.response.reason_phrase,
|
|
235
|
-
err.response.status_code,
|
|
236
|
-
extra={
|
|
237
|
-
"ActionName": self.settings.action_name,
|
|
238
|
-
"ActionId": self.settings.action_id,
|
|
239
|
-
}
|
|
240
|
-
if self.settings
|
|
241
|
-
else None,
|
|
242
|
-
)
|
|
243
|
-
raise self._make_status_error_from_response(err.response) from err
|
|
244
|
-
|
|
245
|
-
return response.json()
|
|
246
|
-
|
|
247
|
-
async def _acall(
|
|
248
|
-
self, url: str, request_body: Dict[str, Any], headers: Dict[str, str]
|
|
249
|
-
) -> Dict[str, Any]:
|
|
250
|
-
"""Run an asynchronous call with retries to the LLM."""
|
|
251
|
-
if self.max_retries is None:
|
|
252
|
-
return await self._arequest(url, request_body, headers)
|
|
253
|
-
|
|
254
|
-
retryer = AsyncRetrying(
|
|
255
|
-
stop=stop_after_attempt(self.max_retries),
|
|
256
|
-
wait=wait_exponential_jitter(
|
|
257
|
-
initial=self.base_delay,
|
|
258
|
-
max=self.max_delay,
|
|
259
|
-
jitter=1.0,
|
|
260
|
-
),
|
|
261
|
-
retry=retry_if_exception_type(
|
|
262
|
-
(openai.RateLimitError, httpx.TimeoutException)
|
|
263
|
-
),
|
|
264
|
-
reraise=True,
|
|
265
|
-
before_sleep=before_sleep_log(self.logger, logging.WARNING)
|
|
266
|
-
if self.logger is not None
|
|
267
|
-
else None,
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
try:
|
|
271
|
-
response: Any = await retryer(self._arequest, url, request_body, headers)
|
|
272
|
-
if self.logger:
|
|
273
|
-
self.logger.info(
|
|
274
|
-
f"[uipath_langchain_client] Finished retryer after {retryer.statistics['attempt_number'] - 1} retries",
|
|
275
|
-
extra={
|
|
276
|
-
"retry": f"{retryer.statistics['attempt_number'] - 1}",
|
|
277
|
-
"ActionName": self.settings.action_name,
|
|
278
|
-
"ActionId": self.settings.action_id,
|
|
279
|
-
}
|
|
280
|
-
if self.settings
|
|
281
|
-
else {
|
|
282
|
-
"retry": f"{retryer.statistics['attempt_number'] - 1}",
|
|
283
|
-
},
|
|
284
|
-
)
|
|
285
|
-
return response
|
|
286
|
-
except openai.APIStatusError as err:
|
|
287
|
-
if self.logger:
|
|
288
|
-
self.logger.error(
|
|
289
|
-
"[uipath_langchain_client] Failed querying LLM after retries: %s",
|
|
290
|
-
err,
|
|
291
|
-
extra={
|
|
292
|
-
"reason": err.message,
|
|
293
|
-
"statusCode": err.status_code,
|
|
294
|
-
"ActionName": self.settings.action_name,
|
|
295
|
-
"ActionId": self.settings.action_id,
|
|
296
|
-
}
|
|
297
|
-
if self.settings
|
|
298
|
-
else {
|
|
299
|
-
"reason": err.message,
|
|
300
|
-
"statusCode": err.status_code,
|
|
301
|
-
},
|
|
302
|
-
)
|
|
303
|
-
raise err
|
|
304
|
-
|
|
305
|
-
def _make_status_error_from_response(
|
|
306
|
-
self,
|
|
307
|
-
response: httpx.Response,
|
|
308
|
-
) -> openai.APIStatusError:
|
|
309
|
-
"""Function reproduced from openai._client to handle UiPath errors."""
|
|
310
|
-
if response.is_closed and not response.is_stream_consumed:
|
|
311
|
-
# We can't read the response body as it has been closed
|
|
312
|
-
# before it was read. This can happen if an event hook
|
|
313
|
-
# raises a status error.
|
|
314
|
-
body = None
|
|
315
|
-
err_msg = f"Error code: {response.status_code}"
|
|
316
|
-
else:
|
|
317
|
-
err_text = response.text.strip()
|
|
318
|
-
body = err_text
|
|
319
|
-
|
|
320
|
-
try:
|
|
321
|
-
body = json.loads(err_text)
|
|
322
|
-
err_msg = f"Error code: {response.status_code} - {body}"
|
|
323
|
-
except Exception:
|
|
324
|
-
err_msg = err_text or f"Error code: {response.status_code}"
|
|
325
|
-
|
|
326
|
-
return self._make_status_error(err_msg, body=body, response=response)
|
|
327
|
-
|
|
328
|
-
def _make_status_error(
|
|
329
|
-
self,
|
|
330
|
-
err_msg: str,
|
|
331
|
-
*,
|
|
332
|
-
body: object,
|
|
333
|
-
response: httpx.Response,
|
|
334
|
-
) -> openai.APIStatusError:
|
|
335
|
-
"""Function reproduced from openai._client to handle UiPath errors."""
|
|
336
|
-
data = body.get("error", body) if isinstance(body, Mapping) else body
|
|
337
|
-
if response.status_code == 400:
|
|
338
|
-
return openai.BadRequestError(err_msg, response=response, body=data)
|
|
339
|
-
|
|
340
|
-
if response.status_code == 401:
|
|
341
|
-
return openai.AuthenticationError(err_msg, response=response, body=data)
|
|
342
|
-
|
|
343
|
-
if response.status_code == 403:
|
|
344
|
-
return openai.PermissionDeniedError(err_msg, response=response, body=data)
|
|
345
|
-
|
|
346
|
-
if response.status_code == 404:
|
|
347
|
-
return openai.NotFoundError(err_msg, response=response, body=data)
|
|
348
|
-
|
|
349
|
-
if response.status_code == 409:
|
|
350
|
-
return openai.ConflictError(err_msg, response=response, body=data)
|
|
351
|
-
|
|
352
|
-
if response.status_code == 422:
|
|
353
|
-
return openai.UnprocessableEntityError(
|
|
354
|
-
err_msg, response=response, body=data
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
if response.status_code == 429:
|
|
358
|
-
return openai.RateLimitError(err_msg, response=response, body=data)
|
|
359
|
-
|
|
360
|
-
if response.status_code >= 500:
|
|
361
|
-
return openai.InternalServerError(err_msg, response=response, body=data)
|
|
362
|
-
return openai.APIStatusError(err_msg, response=response, body=data)
|
|
363
|
-
|
|
364
|
-
def _log_request_duration(self, request: httpx.Request):
|
|
365
|
-
"""Log the start time of the request."""
|
|
366
|
-
if self.logger:
|
|
367
|
-
request.extensions["start_time"] = time.monotonic()
|
|
368
|
-
|
|
369
|
-
def _log_response_duration(self, response: httpx.Response):
|
|
370
|
-
"""Log the duration of the request."""
|
|
371
|
-
if self.logger:
|
|
372
|
-
start_time = response.request.extensions.get("start_time")
|
|
373
|
-
if start_time:
|
|
374
|
-
duration = time.monotonic() - start_time
|
|
375
|
-
type = "embedding"
|
|
376
|
-
if not isinstance(self, Embeddings):
|
|
377
|
-
type = "normalized" if self.is_normalized else "completion"
|
|
378
|
-
self.logger.info(
|
|
379
|
-
f"[uipath_langchain_client] Request to {response.request.url} took {duration:.2f} seconds.",
|
|
380
|
-
extra={
|
|
381
|
-
"requestUrl": f"{response.request.url}",
|
|
382
|
-
"duration": f"{duration:.2f}",
|
|
383
|
-
"type": type,
|
|
384
|
-
"ActionName": self.settings.action_name,
|
|
385
|
-
"ActionId": self.settings.action_id,
|
|
386
|
-
}
|
|
387
|
-
if self.settings
|
|
388
|
-
else {
|
|
389
|
-
"requestUrl": f"{response.request.url}",
|
|
390
|
-
"duration": f"{duration:.2f}",
|
|
391
|
-
"type": type,
|
|
392
|
-
},
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
async def _alog_request_duration(self, request: httpx.Request):
|
|
396
|
-
"""Log the start time of the request."""
|
|
397
|
-
self._log_request_duration(request)
|
|
398
|
-
|
|
399
|
-
async def _alog_response_duration(self, response: httpx.Response):
|
|
400
|
-
"""Log the duration of the request."""
|
|
401
|
-
self._log_response_duration(response)
|
|
402
|
-
|
|
403
|
-
@property
|
|
404
|
-
def _llm_type(self) -> str:
|
|
405
|
-
"""Get the type of language model used by this chat model. Used for logging purposes only."""
|
|
406
|
-
return "uipath"
|
|
407
|
-
|
|
408
|
-
@property
|
|
409
|
-
def _identifying_params(self) -> Dict[str, Any]:
|
|
410
|
-
return {
|
|
411
|
-
"url": self.url,
|
|
412
|
-
"model": self.model_name,
|
|
413
|
-
"temperature": self.temperature,
|
|
414
|
-
"max_tokens": self.max_tokens,
|
|
415
|
-
"frequency_penalty": self.frequency_penalty,
|
|
416
|
-
"presence_penalty": self.presence_penalty,
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
def _prepare_url(self, url: str) -> httpx.URL:
|
|
420
|
-
return httpx.URL(self.url)
|
|
421
|
-
|
|
422
|
-
def _build_headers(self, options, retries_taken: int = 0) -> httpx.Headers:
|
|
423
|
-
return httpx.Headers(self.auth_headers)
|
|
424
|
-
|
|
425
|
-
@property
|
|
426
|
-
def url(self) -> str:
|
|
427
|
-
if not self._url:
|
|
428
|
-
self._url = (
|
|
429
|
-
f"{self.base_url}/{self.org_id}/{self.tenant_id}/{self.endpoint}"
|
|
430
|
-
)
|
|
431
|
-
return self._url
|
|
432
|
-
|
|
433
|
-
@property
|
|
434
|
-
def endpoint(self) -> str:
|
|
435
|
-
raise NotImplementedError(
|
|
436
|
-
"The endpoint property is not implemented for this class."
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
@property
|
|
440
|
-
def auth_headers(self) -> Dict[str, str]:
|
|
441
|
-
if not self._auth_headers:
|
|
442
|
-
self._auth_headers = {
|
|
443
|
-
**self.default_headers, # type: ignore
|
|
444
|
-
"Authorization": f"Bearer {self.access_token}",
|
|
445
|
-
"X-UiPath-LlmGateway-RequestingProduct": self.requesting_product,
|
|
446
|
-
"X-UiPath-LlmGateway-RequestingFeature": self.requesting_feature,
|
|
447
|
-
"X-UiPath-LlmGateway-TimeoutSeconds": str(self.default_request_timeout),
|
|
448
|
-
}
|
|
449
|
-
if self.is_normalized and self.model_name:
|
|
450
|
-
self._auth_headers["X-UiPath-LlmGateway-NormalizedApi-ModelName"] = (
|
|
451
|
-
self.model_name
|
|
452
|
-
)
|
|
453
|
-
if self.include_account_id:
|
|
454
|
-
self._auth_headers["x-uipath-internal-accountid"] = self.org_id
|
|
455
|
-
self._auth_headers["x-uipath-internal-tenantid"] = self.tenant_id
|
|
456
|
-
return self._auth_headers
|
|
457
|
-
|
|
458
|
-
def _get_llm_string(self, stop: Optional[list[str]] = None, **kwargs: Any) -> str:
|
|
459
|
-
serialized_repr = getattr(self, "_serialized", self.model_dump())
|
|
460
|
-
_cleanup_llm_representation(serialized_repr, 1)
|
|
461
|
-
kwargs = serialized_repr.get("kwargs", serialized_repr)
|
|
462
|
-
for key in [
|
|
463
|
-
"base_url",
|
|
464
|
-
"access_token",
|
|
465
|
-
"client_id",
|
|
466
|
-
"client_secret",
|
|
467
|
-
"org_id",
|
|
468
|
-
"tenant_id",
|
|
469
|
-
"requesting_product",
|
|
470
|
-
"requesting_feature",
|
|
471
|
-
"azure_endpoint",
|
|
472
|
-
"openai_api_version",
|
|
473
|
-
"openai_api_key",
|
|
474
|
-
"default_request_timeout",
|
|
475
|
-
"max_retries",
|
|
476
|
-
"base_delay",
|
|
477
|
-
"max_delay",
|
|
478
|
-
"logger",
|
|
479
|
-
"settings",
|
|
480
|
-
]:
|
|
481
|
-
if key in kwargs:
|
|
482
|
-
kwargs.pop(key, None)
|
|
483
|
-
llm_string = json.dumps(serialized_repr, sort_keys=True)
|
|
484
|
-
return llm_string
|
|
485
|
-
|
|
486
|
-
@property
|
|
487
|
-
def is_normalized(self) -> bool:
|
|
488
|
-
return False
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# mypy: disable-error-code="syntax"
|
|
2
|
-
import os
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from typing import Any, Optional
|
|
5
|
-
|
|
6
|
-
import httpx
|
|
7
|
-
from pydantic import Field
|
|
8
|
-
from pydantic_settings import BaseSettings
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class UiPathCachedPathsSettings(BaseSettings):
|
|
12
|
-
cached_completion_db: str = Field(
|
|
13
|
-
default=os.path.join(
|
|
14
|
-
os.path.dirname(__file__), "tests", "tests_uipath_cache.db"
|
|
15
|
-
),
|
|
16
|
-
alias="CACHED_COMPLETION_DB",
|
|
17
|
-
)
|
|
18
|
-
cached_embeddings_dir: str = Field(
|
|
19
|
-
default=os.path.join(os.path.dirname(__file__), "tests", "cached_embeddings"),
|
|
20
|
-
alias="CACHED_EMBEDDINGS_DIR",
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
uipath_cached_paths_settings = UiPathCachedPathsSettings()
|
|
25
|
-
uipath_token_header: Optional[str] = None
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class UiPathClientFactorySettings(BaseSettings):
|
|
29
|
-
base_url: str = Field(default="", alias="UIPATH_BASE_URL")
|
|
30
|
-
client_id: str = Field(default="", alias="UIPATH_CLIENT_ID")
|
|
31
|
-
client_secret: str = Field(default="", alias="UIPATH_CLIENT_SECRET")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class UiPathClientSettings(BaseSettings):
|
|
35
|
-
access_token: str = Field(default_factory=lambda: get_uipath_token_header())
|
|
36
|
-
base_url: str = Field(default="", alias="UIPATH_BASE_URL")
|
|
37
|
-
org_id: str = Field(default="", alias="UIPATH_ORGANIZATION_ID")
|
|
38
|
-
tenant_id: str = Field(default="", alias="UIPATH_TENANT_ID")
|
|
39
|
-
requesting_product: str = Field(default="", alias="UIPATH_REQUESTING_PRODUCT")
|
|
40
|
-
requesting_feature: str = Field(default="", alias="UIPATH_REQUESTING_FEATURE")
|
|
41
|
-
timeout_seconds: str = Field(default="120", alias="UIPATH_TIMEOUT_SECONDS")
|
|
42
|
-
action_name: str = Field(default="DefaultActionName", alias="UIPATH_ACTION_NAME")
|
|
43
|
-
action_id: str = Field(default="DefaultActionId", alias="UIPATH_ACTION_ID")
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class UiPathEndpoints(Enum):
|
|
47
|
-
NORMALIZED_COMPLETION_ENDPOINT = "llmgateway_/api/chat/completions"
|
|
48
|
-
PASSTHROUGH_COMPLETION_ENDPOINT = "llmgateway_/openai/deployments/{model}/chat/completions?api-version={api_version}"
|
|
49
|
-
EMBEDDING_ENDPOINT = (
|
|
50
|
-
"llmgateway_/openai/deployments/{model}/embeddings?api-version={api_version}"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def get_uipath_token_header(
|
|
55
|
-
settings: Any = None,
|
|
56
|
-
) -> str:
|
|
57
|
-
global uipath_token_header
|
|
58
|
-
if not uipath_token_header:
|
|
59
|
-
settings = settings or UiPathClientFactorySettings()
|
|
60
|
-
url_get_token = f"{settings.base_url}/identity_/connect/token"
|
|
61
|
-
token_credentials = dict(
|
|
62
|
-
client_id=settings.client_id,
|
|
63
|
-
client_secret=settings.client_secret,
|
|
64
|
-
grant_type="client_credentials",
|
|
65
|
-
)
|
|
66
|
-
with httpx.Client() as client:
|
|
67
|
-
res = client.post(url_get_token, data=token_credentials)
|
|
68
|
-
res_json = res.json()
|
|
69
|
-
uipath_token_header = res_json.get("access_token")
|
|
70
|
-
|
|
71
|
-
return uipath_token_header or ""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
async def get_token_header_async(
|
|
75
|
-
settings: Any = None,
|
|
76
|
-
) -> str:
|
|
77
|
-
global uipath_token_header
|
|
78
|
-
if not uipath_token_header:
|
|
79
|
-
settings = settings or UiPathClientFactorySettings()
|
|
80
|
-
url_get_token = f"{settings.base_url}/identity_/connect/token"
|
|
81
|
-
token_credentials = dict(
|
|
82
|
-
client_id=settings.client_id,
|
|
83
|
-
client_secret=settings.client_secret,
|
|
84
|
-
grant_type="client_credentials",
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
with httpx.Client() as client:
|
|
88
|
-
res_json = client.post(url_get_token, data=token_credentials).json()
|
|
89
|
-
uipath_token_header = res_json.get("access_token")
|
|
90
|
-
|
|
91
|
-
return uipath_token_header or ""
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Callable
|
|
3
|
-
|
|
4
|
-
from tenacity import (
|
|
5
|
-
RetryCallState,
|
|
6
|
-
_utils,
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def before_sleep_log(
|
|
11
|
-
logger: "logging.Logger",
|
|
12
|
-
log_level: int,
|
|
13
|
-
exc_info: bool = False,
|
|
14
|
-
) -> Callable[["RetryCallState"], None]:
|
|
15
|
-
"""Before call strategy that logs to some logger the attempt."""
|
|
16
|
-
|
|
17
|
-
def log_it(retry_state: "RetryCallState") -> None:
|
|
18
|
-
if retry_state.outcome is None:
|
|
19
|
-
raise RuntimeError("log_it() called before outcome was set")
|
|
20
|
-
|
|
21
|
-
if retry_state.next_action is None:
|
|
22
|
-
raise RuntimeError("log_it() called before next_action was set")
|
|
23
|
-
|
|
24
|
-
if retry_state.outcome.failed:
|
|
25
|
-
ex = retry_state.outcome.exception()
|
|
26
|
-
verb, value = "raised", f"{ex.__class__.__name__}: {ex}"
|
|
27
|
-
else:
|
|
28
|
-
verb, value = "returned", retry_state.outcome.result()
|
|
29
|
-
|
|
30
|
-
if retry_state.fn is None:
|
|
31
|
-
fn_name = "<unknown>"
|
|
32
|
-
else:
|
|
33
|
-
fn_name = _utils.get_callback_name(retry_state.fn)
|
|
34
|
-
|
|
35
|
-
logger.log(
|
|
36
|
-
log_level,
|
|
37
|
-
f"Retrying #{retry_state.attempt_number} {fn_name} in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
|
|
38
|
-
{"retries": f"{retry_state.attempt_number}"},
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
return log_it
|
|
File without changes
|
|
File without changes
|
|
File without changes
|