grasp_agents 0.5.13__tar.gz → 0.5.15__tar.gz

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.
Files changed (59) hide show
  1. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/PKG-INFO +2 -2
  2. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/pyproject.toml +2 -2
  3. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/cloud_llm.py +2 -1
  4. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/http_client.py +17 -5
  5. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/litellm/lite_llm.py +10 -10
  6. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/openai_llm.py +1 -0
  7. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/processors/base_processor.py +3 -2
  8. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/utils.py +34 -18
  9. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/.gitignore +0 -0
  10. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/LICENSE.md +0 -0
  11. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/README.md +0 -0
  12. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/__init__.py +0 -0
  13. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/costs_dict.yaml +0 -0
  14. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/errors.py +0 -0
  15. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/generics_utils.py +0 -0
  16. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/grasp_logging.py +0 -0
  17. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/litellm/__init__.py +0 -0
  18. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/litellm/completion_chunk_converters.py +0 -0
  19. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/litellm/completion_converters.py +0 -0
  20. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/litellm/converters.py +0 -0
  21. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/litellm/message_converters.py +0 -0
  22. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/llm.py +0 -0
  23. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/llm_agent.py +0 -0
  24. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/llm_agent_memory.py +0 -0
  25. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/llm_policy_executor.py +0 -0
  26. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/memory.py +0 -0
  27. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/__init__.py +0 -0
  28. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/completion_chunk_converters.py +0 -0
  29. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/completion_converters.py +0 -0
  30. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/content_converters.py +0 -0
  31. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/converters.py +0 -0
  32. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/message_converters.py +0 -0
  33. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/openai/tool_converters.py +0 -0
  34. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/packet.py +0 -0
  35. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/packet_pool.py +0 -0
  36. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/printer.py +0 -0
  37. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/processors/parallel_processor.py +0 -0
  38. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/processors/processor.py +0 -0
  39. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/prompt_builder.py +0 -0
  40. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/rate_limiting/__init__.py +0 -0
  41. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/rate_limiting/rate_limiter_chunked.py +0 -0
  42. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/rate_limiting/types.py +0 -0
  43. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/rate_limiting/utils.py +0 -0
  44. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/run_context.py +0 -0
  45. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/runner.py +0 -0
  46. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/__init__.py +0 -0
  47. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/completion.py +0 -0
  48. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/completion_chunk.py +0 -0
  49. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/content.py +0 -0
  50. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/converters.py +0 -0
  51. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/events.py +0 -0
  52. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/io.py +0 -0
  53. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/message.py +0 -0
  54. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/typing/tool.py +0 -0
  55. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/usage_tracker.py +0 -0
  56. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/workflow/__init__.py +0 -0
  57. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/workflow/looped_workflow.py +0 -0
  58. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/workflow/sequential_workflow.py +0 -0
  59. {grasp_agents-0.5.13 → grasp_agents-0.5.15}/src/grasp_agents/workflow/workflow_processor.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grasp_agents
3
- Version: 0.5.13
3
+ Version: 0.5.15
4
4
  Summary: Grasp Agents Library
5
5
  License-File: LICENSE.md
6
6
  Requires-Python: <4,>=3.11.4
7
7
  Requires-Dist: dotenv>=0.9.9
8
8
  Requires-Dist: httpx<1,>=0.27.0
9
- Requires-Dist: litellm>=1.74.4
9
+ Requires-Dist: litellm>=1.76.3
10
10
  Requires-Dist: openai<2,>=1.68.2
11
11
  Requires-Dist: pydantic>=2
12
12
  Requires-Dist: pyyaml>=6.0.2
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "grasp_agents"
3
- version = "0.5.13"
3
+ version = "0.5.15"
4
4
  description = "Grasp Agents Library"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11.4,<4"
@@ -13,7 +13,7 @@ dependencies = [
13
13
  "dotenv>=0.9.9",
14
14
  "pyyaml>=6.0.2",
15
15
  "pydantic>=2",
16
- "litellm>=1.74.4",
16
+ "litellm>=1.76.3",
17
17
  ]
18
18
 
19
19
  [dependency-groups]
@@ -64,6 +64,7 @@ class CloudLLM(LLM[SettingsT_co, ConvertT_co], Generic[SettingsT_co, ConvertT_co
64
64
  api_provider: APIProvider | None = None
65
65
  llm_settings: SettingsT_co | None = None
66
66
  rate_limiter: LLMRateLimiter | None = None
67
+ client_timeout: float = 60.0
67
68
  max_client_retries: int = 2 # HTTP client retries for network errors
68
69
  max_response_retries: int = (
69
70
  0 # LLM response retries: try to regenerate to pass validation
@@ -71,7 +72,7 @@ class CloudLLM(LLM[SettingsT_co, ConvertT_co], Generic[SettingsT_co, ConvertT_co
71
72
  apply_response_schema_via_provider: bool = False
72
73
  apply_tool_call_schema_via_provider: bool = False
73
74
  async_http_client: httpx.AsyncClient | None = None
74
- async_http_client_params: dict[str, Any] | AsyncHTTPClientParams | None = None
75
+ async_http_client_params: AsyncHTTPClientParams | None = None
75
76
 
76
77
  def __post_init__(self) -> None:
77
78
  if self.rate_limiter is not None:
@@ -1,5 +1,3 @@
1
- from typing import Any
2
-
3
1
  import httpx
4
2
  from pydantic import BaseModel, NonNegativeFloat, PositiveInt
5
3
 
@@ -9,13 +7,26 @@ class AsyncHTTPClientParams(BaseModel):
9
7
  max_connections: PositiveInt = 2000
10
8
  max_keepalive_connections: PositiveInt = 500
11
9
  keepalive_expiry: float | None = 5
10
+ # proxy: str | None = None
11
+ # follow_redirects: bool = False
12
+ # trust_env: bool = True
13
+ # auth: AuthTypes | None = (None,)
14
+ # params: QueryParamTypes | None = (None,)
15
+ # headers: HeaderTypes | None = (None,)
16
+ # cookies: CookieTypes | None = (None,)
12
17
 
13
18
 
14
19
  def create_simple_async_httpx_client(
15
- client_params: AsyncHTTPClientParams | dict[str, Any],
20
+ client_params: AsyncHTTPClientParams,
16
21
  ) -> httpx.AsyncClient:
17
- if isinstance(client_params, dict):
18
- client_params = AsyncHTTPClientParams(**client_params)
22
+ extra_params = client_params.model_dump(
23
+ exclude={
24
+ "timeout",
25
+ "max_connections",
26
+ "max_keepalive_connections",
27
+ "keepalive_expiry",
28
+ }
29
+ )
19
30
  return httpx.AsyncClient(
20
31
  timeout=httpx.Timeout(client_params.timeout),
21
32
  limits=httpx.Limits(
@@ -23,4 +34,5 @@ def create_simple_async_httpx_client(
23
34
  max_keepalive_connections=client_params.max_keepalive_connections,
24
35
  keepalive_expiry=client_params.keepalive_expiry,
25
36
  ),
37
+ **extra_params,
26
38
  )
@@ -45,7 +45,6 @@ class LiteLLMSettings(OpenAILLMSettings, total=False):
45
45
  class LiteLLM(CloudLLM[LiteLLMSettings, LiteLLMConverters]):
46
46
  converters: LiteLLMConverters = field(default_factory=LiteLLMConverters)
47
47
 
48
- timeout: float | None = None
49
48
  # Drop unsupported LLM settings
50
49
  drop_params: bool = True
51
50
  additional_drop_params: list[str] | None = None
@@ -63,7 +62,7 @@ class LiteLLM(CloudLLM[LiteLLMSettings, LiteLLMConverters]):
63
62
  self._lite_llm_completion_params.update(
64
63
  {
65
64
  "max_retries": self.max_client_retries,
66
- "timeout": self.timeout,
65
+ "timeout": self.client_timeout,
67
66
  "drop_params": self.drop_params,
68
67
  "additional_drop_params": self.additional_drop_params,
69
68
  "allowed_openai_params": self.allowed_openai_params,
@@ -195,14 +194,15 @@ class LiteLLM(CloudLLM[LiteLLMSettings, LiteLLMConverters]):
195
194
 
196
195
  async for completion_chunk in stream:
197
196
  # Fix tool call indices to be unique within each choice
198
- for n, choice in enumerate(completion_chunk.choices):
199
- for tc in choice.delta.tool_calls or []:
200
- # Tool call ID is not None only when it is a new tool call
201
- if tc.id and tc.index in tc_indices[n]:
202
- tc.index = max(tc_indices[n]) + 1
203
- tc_indices[n].add(tc.index)
204
-
205
- yield completion_chunk
197
+ if completion_chunk is not None:
198
+ for n, choice in enumerate(completion_chunk.choices):
199
+ for tc in choice.delta.tool_calls or []:
200
+ # Tool call ID is not None only when it is a new tool call
201
+ if tc.id and tc.index in tc_indices[n]:
202
+ tc.index = max(tc_indices[n]) + 1
203
+ tc_indices[n].add(tc.index)
204
+
205
+ yield completion_chunk
206
206
 
207
207
  def combine_completion_chunks(
208
208
  self,
@@ -150,6 +150,7 @@ class OpenAILLM(CloudLLM[OpenAILLMSettings, OpenAIConverters]):
150
150
  base_url=_api_provider.get("base_url"),
151
151
  api_key=_api_provider.get("api_key"),
152
152
  max_retries=self.max_client_retries,
153
+ timeout=self.client_timeout,
153
154
  **_async_openai_client_params,
154
155
  )
155
156
 
@@ -47,6 +47,7 @@ def with_retry(func: F) -> F:
47
47
  async def wrapper(
48
48
  self: "BaseProcessor[Any, Any, Any, Any]", *args: Any, **kwargs: Any
49
49
  ) -> Packet[Any]:
50
+ none_packet = Packet(payloads=[None], sender=self.name)
50
51
  call_id = kwargs.get("call_id", "unknown")
51
52
  for n_attempt in range(self.max_retries + 1):
52
53
  try:
@@ -61,12 +62,12 @@ def with_retry(func: F) -> F:
61
62
  else:
62
63
  logger.warning(f"{err_message} after retrying:\n{err}")
63
64
  # raise ProcRunError(proc_name=self.name, call_id=call_id) from err
64
- return None # type: ignore[return]
65
+ return none_packet
65
66
 
66
67
  logger.warning(f"{err_message} (retry attempt {n_attempt + 1}):\n{err}")
67
68
  # This part should not be reachable due to the raise in the loop
68
69
  # raise ProcRunError(proc_name=self.name, call_id=call_id)
69
- return None # type: ignore[return]
70
+ return none_packet
70
71
 
71
72
  return cast("F", wrapper)
72
73
 
@@ -6,7 +6,7 @@ from collections.abc import AsyncIterator, Coroutine, Mapping
6
6
  from datetime import UTC, datetime
7
7
  from logging import getLogger
8
8
  from pathlib import Path
9
- from typing import Annotated, Any, TypeVar, get_args, get_origin
9
+ from typing import Annotated, Any, NoReturn, TypeVar, get_args, get_origin
10
10
 
11
11
  from pydantic import TypeAdapter
12
12
  from pydantic import ValidationError as PydanticValidationError
@@ -71,12 +71,15 @@ def is_str_type(t: Any) -> bool:
71
71
  )
72
72
 
73
73
 
74
+ T = TypeVar("T")
75
+
76
+
74
77
  def validate_obj_from_json_or_py_string(
75
78
  s: str,
76
- schema: Any,
79
+ schema: type[T],
77
80
  from_substring: bool = False,
78
81
  strip_language_markdown: bool = True,
79
- ) -> Any:
82
+ ) -> T:
80
83
  try:
81
84
  if is_str_type(schema):
82
85
  parsed = s
@@ -94,19 +97,36 @@ def validate_obj_from_json_or_py_string(
94
97
 
95
98
  def validate_tagged_objs_from_json_or_py_string(
96
99
  s: str,
97
- schema_by_xml_tag: Mapping[str, Any],
100
+ schema_by_xml_tag: Mapping[str, type[T]],
98
101
  from_substring: bool = False,
99
102
  strip_language_markdown: bool = True,
100
- ) -> Mapping[str, Any]:
101
- validated_obj_per_tag: dict[str, Any] = {}
102
- _schema: Any = None
103
+ ) -> Mapping[str, T]:
104
+ validated_obj_per_tag: dict[str, T] = {}
105
+ _schema: type[T] | None = None
103
106
  _tag: str | None = None
104
107
 
105
- try:
106
- for _tag, _schema in schema_by_xml_tag.items():
107
- match = re.search(rf"<{_tag}>\s*(.*?)\s*</{_tag}>", s, re.DOTALL)
108
- if not match:
109
- continue
108
+ def _raise(
109
+ tag: str,
110
+ schema: Any,
111
+ *,
112
+ not_found: bool = False,
113
+ base_exc: Exception | None = None,
114
+ ) -> NoReturn:
115
+ if not_found:
116
+ msg = f"Tag <{tag}> not found in string:\n{s}"
117
+ else:
118
+ msg = (
119
+ f"Failed to validate substring within tag <{tag}> against JSON schema:"
120
+ f"\n{s}\nExpected type: {schema}"
121
+ )
122
+ raise JSONSchemaValidationError(s, schema, message=msg) from base_exc
123
+
124
+ for _tag, _schema in schema_by_xml_tag.items():
125
+ match = re.search(rf"<{_tag}>\s*(.*?)\s*</{_tag}>", s, re.DOTALL)
126
+ if match is None:
127
+ continue
128
+ # _raise(_tag, _schema, not_found=True, base_exc=None)
129
+ try:
110
130
  tagged_substring = match.group(1).strip()
111
131
  validated_obj_per_tag[_tag] = validate_obj_from_json_or_py_string(
112
132
  tagged_substring, # type: ignore[assignment]
@@ -114,12 +134,8 @@ def validate_tagged_objs_from_json_or_py_string(
114
134
  from_substring=from_substring,
115
135
  strip_language_markdown=strip_language_markdown,
116
136
  )
117
- except JSONSchemaValidationError as exc:
118
- err_message = (
119
- f"Failed to validate substring within tag <{_tag}> against JSON schema:"
120
- f"\n{s}\nExpected type: {_schema}"
121
- )
122
- raise JSONSchemaValidationError(s, _schema, message=err_message) from exc
137
+ except JSONSchemaValidationError as exc:
138
+ _raise(_tag, _schema, base_exc=exc)
123
139
 
124
140
  return validated_obj_per_tag
125
141
 
File without changes
File without changes
File without changes