simple-agents-py 0.1.5__tar.gz → 0.1.6__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 (118) hide show
  1. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/Cargo.lock +13 -11
  2. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/Cargo.toml +1 -1
  3. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/PKG-INFO +1 -1
  4. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-py/Cargo.toml +2 -1
  5. simple_agents_py-0.1.6/crates/simple-agents-py/py.typed +1 -0
  6. simple_agents_py-0.1.6/crates/simple-agents-py/simple_agents_py.pyi +45 -0
  7. simple_agents_py-0.1.6/crates/simple-agents-py/src/lib.rs +398 -0
  8. simple_agents_py-0.1.6/py.typed +1 -0
  9. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/pyproject.toml +2 -1
  10. simple_agents_py-0.1.6/simple_agents_py.pyi +45 -0
  11. simple_agents_py-0.1.5/crates/simple-agents-py/src/lib.rs +0 -182
  12. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/README.md +0 -0
  13. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/Cargo.toml +0 -0
  14. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/README.md +0 -0
  15. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/TEST_GUIDE.md +0 -0
  16. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/TODO.md +0 -0
  17. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/examples/basic_usage.rs +0 -0
  18. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/examples/mock_provider.rs +0 -0
  19. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/cache.rs +0 -0
  20. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/coercion.rs +0 -0
  21. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/config.rs +0 -0
  22. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/error.rs +0 -0
  23. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/lib.rs +0 -0
  24. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/message.rs +0 -0
  25. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/provider.rs +0 -0
  26. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/request.rs +0 -0
  27. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/response.rs +0 -0
  28. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/router.rs +0 -0
  29. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/src/validation.rs +0 -0
  30. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agent-type/tests/integration_test.rs +0 -0
  31. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-cache/Cargo.toml +0 -0
  32. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-cache/src/lib.rs +0 -0
  33. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-cache/src/memory.rs +0 -0
  34. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-cache/src/noop.rs +0 -0
  35. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/Cargo.toml +0 -0
  36. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/README.md +0 -0
  37. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/examples/basic_client.rs +0 -0
  38. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/src/client.rs +0 -0
  39. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/src/healing.rs +0 -0
  40. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/src/lib.rs +0 -0
  41. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/src/middleware.rs +0 -0
  42. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/src/routing.rs +0 -0
  43. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-core/tests/client_integration.rs +0 -0
  44. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/Cargo.toml +0 -0
  45. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/README.md +0 -0
  46. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/benches/parser_benchmarks.rs +0 -0
  47. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/examples/basic_healing.rs +0 -0
  48. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/examples/coercion_demo.rs +0 -0
  49. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/examples/streaming_annotations.rs +0 -0
  50. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/examples/streaming_partial_types.rs +0 -0
  51. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/src/coercion.rs +0 -0
  52. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/src/lib.rs +0 -0
  53. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/src/parser.rs +0 -0
  54. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/src/schema.rs +0 -0
  55. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/src/streaming.rs +0 -0
  56. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/src/string_utils.rs +0 -0
  57. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/tests/parser_tests.rs +0 -0
  58. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/tests/property_tests.rs +0 -0
  59. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/tests/stream_annotations_tests.rs +0 -0
  60. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-healing/tests/streaming_tests.rs +0 -0
  61. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-macros/Cargo.toml +0 -0
  62. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-macros/README.md +0 -0
  63. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-macros/src/lib.rs +0 -0
  64. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-macros/src/partial.rs +0 -0
  65. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-macros/tests/partial_type_tests.rs +0 -0
  66. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/Cargo.toml +0 -0
  67. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/README.md +0 -0
  68. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/anthropic_basic.rs +0 -0
  69. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/anthropic_structured_output.rs +0 -0
  70. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/cache_usage.rs +0 -0
  71. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/custom_api.rs +0 -0
  72. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/healing_fallback.rs +0 -0
  73. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/openai_basic.rs +0 -0
  74. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/openai_structured_output.rs +0 -0
  75. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/openrouter_basic.rs +0 -0
  76. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/retry_demo.rs +0 -0
  77. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/streaming.rs +0 -0
  78. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/streaming_structured.rs +0 -0
  79. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/streaming_with_healing.rs +0 -0
  80. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/test_local_api.rs +0 -0
  81. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/examples/test_reqwest.rs +0 -0
  82. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/run_integration_tests.sh +0 -0
  83. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/anthropic/error.rs +0 -0
  84. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/anthropic/mod.rs +0 -0
  85. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/anthropic/models.rs +0 -0
  86. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/anthropic/streaming.rs +0 -0
  87. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/common/error.rs +0 -0
  88. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/common/http_client.rs +0 -0
  89. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/common/mod.rs +0 -0
  90. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/healing_integration.rs +0 -0
  91. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/lib.rs +0 -0
  92. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/metrics.rs +0 -0
  93. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/openai/error.rs +0 -0
  94. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/openai/mod.rs +0 -0
  95. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/openai/models.rs +0 -0
  96. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/openai/streaming.rs +0 -0
  97. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/openrouter/mod.rs +0 -0
  98. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/rate_limit.rs +0 -0
  99. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/retry.rs +0 -0
  100. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/schema_converter.rs +0 -0
  101. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/streaming_structured.rs +0 -0
  102. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/src/utils.rs +0 -0
  103. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/tests/README.md +0 -0
  104. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/tests/healing_integration_tests.rs +0 -0
  105. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-providers/tests/openai_integration.rs +0 -0
  106. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-py/README.md +0 -0
  107. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-py/tests/test_client.py +0 -0
  108. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/Cargo.toml +0 -0
  109. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/examples/round_robin_router.rs +0 -0
  110. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/circuit_breaker.rs +0 -0
  111. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/cost.rs +0 -0
  112. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/fallback.rs +0 -0
  113. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/health.rs +0 -0
  114. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/latency.rs +0 -0
  115. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/lib.rs +0 -0
  116. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/retry.rs +0 -0
  117. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/src/round_robin.rs +0 -0
  118. {simple_agents_py-0.1.5 → simple_agents_py-0.1.6}/crates/simple-agents-router/tests/health_tracker_integration.rs +0 -0
@@ -1494,6 +1494,7 @@ dependencies = [
1494
1494
  "pyo3-build-config",
1495
1495
  "pyo3-ffi",
1496
1496
  "pyo3-macros",
1497
+ "serde",
1497
1498
  "unindent",
1498
1499
  ]
1499
1500
 
@@ -2025,7 +2026,7 @@ dependencies = [
2025
2026
 
2026
2027
  [[package]]
2027
2028
  name = "simple-agent-type"
2028
- version = "0.1.5"
2029
+ version = "0.1.6"
2029
2030
  dependencies = [
2030
2031
  "async-trait",
2031
2032
  "blake3",
@@ -2040,7 +2041,7 @@ dependencies = [
2040
2041
 
2041
2042
  [[package]]
2042
2043
  name = "simple-agents-cache"
2043
- version = "0.1.5"
2044
+ version = "0.1.6"
2044
2045
  dependencies = [
2045
2046
  "async-trait",
2046
2047
  "simple-agent-type",
@@ -2049,7 +2050,7 @@ dependencies = [
2049
2050
 
2050
2051
  [[package]]
2051
2052
  name = "simple-agents-cli"
2052
- version = "0.1.5"
2053
+ version = "0.1.6"
2053
2054
  dependencies = [
2054
2055
  "clap",
2055
2056
  "serde",
@@ -2066,7 +2067,7 @@ dependencies = [
2066
2067
 
2067
2068
  [[package]]
2068
2069
  name = "simple-agents-core"
2069
- version = "0.1.5"
2070
+ version = "0.1.6"
2070
2071
  dependencies = [
2071
2072
  "async-trait",
2072
2073
  "serde",
@@ -2093,7 +2094,7 @@ dependencies = [
2093
2094
 
2094
2095
  [[package]]
2095
2096
  name = "simple-agents-ffi"
2096
- version = "0.1.5"
2097
+ version = "0.1.6"
2097
2098
  dependencies = [
2098
2099
  "async-trait",
2099
2100
  "simple-agent-type",
@@ -2104,7 +2105,7 @@ dependencies = [
2104
2105
 
2105
2106
  [[package]]
2106
2107
  name = "simple-agents-healing"
2107
- version = "0.1.5"
2108
+ version = "0.1.6"
2108
2109
  dependencies = [
2109
2110
  "criterion",
2110
2111
  "proptest",
@@ -2120,7 +2121,7 @@ dependencies = [
2120
2121
 
2121
2122
  [[package]]
2122
2123
  name = "simple-agents-macros"
2123
- version = "0.1.5"
2124
+ version = "0.1.6"
2124
2125
  dependencies = [
2125
2126
  "proc-macro2",
2126
2127
  "quote",
@@ -2132,7 +2133,7 @@ dependencies = [
2132
2133
 
2133
2134
  [[package]]
2134
2135
  name = "simple-agents-napi"
2135
- version = "0.1.5"
2136
+ version = "0.1.6"
2136
2137
  dependencies = [
2137
2138
  "napi",
2138
2139
  "napi-derive",
@@ -2144,7 +2145,7 @@ dependencies = [
2144
2145
 
2145
2146
  [[package]]
2146
2147
  name = "simple-agents-providers"
2147
- version = "0.1.5"
2148
+ version = "0.1.6"
2148
2149
  dependencies = [
2149
2150
  "async-trait",
2150
2151
  "bytes",
@@ -2172,10 +2173,11 @@ dependencies = [
2172
2173
 
2173
2174
  [[package]]
2174
2175
  name = "simple-agents-py"
2175
- version = "0.1.5"
2176
+ version = "0.1.6"
2176
2177
  dependencies = [
2177
2178
  "pyo3",
2178
2179
  "reqwest",
2180
+ "serde_json",
2179
2181
  "simple-agent-type",
2180
2182
  "simple-agents-core",
2181
2183
  "simple-agents-providers",
@@ -2184,7 +2186,7 @@ dependencies = [
2184
2186
 
2185
2187
  [[package]]
2186
2188
  name = "simple-agents-router"
2187
- version = "0.1.5"
2189
+ version = "0.1.6"
2188
2190
  dependencies = [
2189
2191
  "async-trait",
2190
2192
  "rand 0.8.5",
@@ -3,7 +3,7 @@ members = ["crates/*"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "0.1.5"
6
+ version = "0.1.6"
7
7
  edition = "2021"
8
8
  rust-version = "1.75"
9
9
  authors = ["SimpleAgents Contributors"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple-agents-py
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -10,9 +10,10 @@ name = "simple_agents_py"
10
10
  crate-type = ["cdylib"]
11
11
 
12
12
  [dependencies]
13
- pyo3 = { version = "0.22", features = ["extension-module", "abi3-py312"] }
13
+ pyo3 = { version = "0.22", features = ["extension-module", "abi3-py312", "serde"] }
14
14
  tokio = { workspace = true }
15
15
  reqwest = { workspace = true }
16
+ serde_json = { workspace = true }
16
17
 
17
18
  simple-agents-core = { path = "../simple-agents-core", version = "0.1.0" }
18
19
  simple-agents-providers = { path = "../simple-agents-providers", version = "0.1.0" }
@@ -0,0 +1,45 @@
1
+ class Client:
2
+ def __init__(
3
+ self,
4
+ provider: str,
5
+ api_key: str | None = None,
6
+ api_base: str | None = None,
7
+ ) -> None: ...
8
+
9
+ def complete(
10
+ self,
11
+ model: str,
12
+ prompt: str,
13
+ max_tokens: int | None = None,
14
+ temperature: float | None = None,
15
+ ) -> str: ...
16
+
17
+ def complete_messages(
18
+ self,
19
+ model: str,
20
+ messages: list[dict[str, object]],
21
+ max_tokens: int | None = None,
22
+ temperature: float | None = None,
23
+ top_p: float | None = None,
24
+ ) -> str: ...
25
+
26
+ def complete_json(
27
+ self,
28
+ model: str,
29
+ messages: list[dict[str, object]],
30
+ max_tokens: int | None = None,
31
+ temperature: float | None = None,
32
+ top_p: float | None = None,
33
+ ) -> str: ...
34
+
35
+ def complete_json_schema(
36
+ self,
37
+ model: str,
38
+ messages: list[dict[str, object]],
39
+ schema: dict[str, object],
40
+ schema_name: str,
41
+ max_tokens: int | None = None,
42
+ temperature: float | None = None,
43
+ top_p: float | None = None,
44
+ strict: bool = True,
45
+ ) -> str: ...
@@ -0,0 +1,398 @@
1
+ //! Python bindings for SimpleAgents using PyO3.
2
+
3
+ #![allow(clippy::useless_conversion)]
4
+
5
+ use pyo3::exceptions::PyRuntimeError;
6
+ use pyo3::prelude::*;
7
+ use pyo3::types::{PyDict, PyList};
8
+ use reqwest::Client as HttpClient;
9
+ use simple_agents_core::{SimpleAgentsClient, SimpleAgentsClientBuilder};
10
+ use simple_agents_providers::anthropic::AnthropicProvider;
11
+ use simple_agents_providers::openai::OpenAIProvider;
12
+ use simple_agents_providers::openrouter::OpenRouterProvider;
13
+ use simple_agent_type::message::Message;
14
+ use simple_agent_type::prelude::{ApiKey, CompletionRequest, Provider, Result, SimpleAgentsError};
15
+ use simple_agent_type::request::{JsonSchemaFormat, ResponseFormat};
16
+ use std::sync::{Arc, Mutex};
17
+ use std::time::Duration;
18
+
19
+ type Runtime = tokio::runtime::Runtime;
20
+
21
+ #[pyclass]
22
+ struct Client {
23
+ runtime: Mutex<Runtime>,
24
+ client: SimpleAgentsClient,
25
+ }
26
+
27
+ fn provider_from_params(
28
+ provider_name: &str,
29
+ api_key: Option<&str>,
30
+ api_base: Option<&str>,
31
+ ) -> Result<Arc<dyn Provider>> {
32
+ let api_key = match api_key {
33
+ Some(value) => Some(ApiKey::new(value)?),
34
+ None => None,
35
+ };
36
+
37
+ match provider_name {
38
+ "openai" => {
39
+ let provider = match api_key {
40
+ Some(api_key) => match api_base {
41
+ Some(api_base) => {
42
+ if is_local_base(api_base) {
43
+ let client = HttpClient::builder()
44
+ .timeout(Duration::from_secs(30))
45
+ .pool_max_idle_per_host(10)
46
+ .pool_idle_timeout(Duration::from_secs(90))
47
+ .no_proxy()
48
+ .build()
49
+ .map_err(|e| {
50
+ SimpleAgentsError::Config(format!(
51
+ "Failed to create HTTP client: {}",
52
+ e
53
+ ))
54
+ })?;
55
+ OpenAIProvider::with_client(api_key, api_base.to_string(), client)?
56
+ } else {
57
+ OpenAIProvider::with_base_url(api_key, api_base.to_string())?
58
+ }
59
+ }
60
+ None => OpenAIProvider::new(api_key)?,
61
+ },
62
+ None => OpenAIProvider::from_env()?,
63
+ };
64
+ Ok(Arc::new(provider))
65
+ }
66
+ "anthropic" => {
67
+ let provider = match api_key {
68
+ Some(api_key) => match api_base {
69
+ Some(api_base) => {
70
+ AnthropicProvider::with_base_url(api_key, api_base.to_string())?
71
+ }
72
+ None => AnthropicProvider::new(api_key)?,
73
+ },
74
+ None => AnthropicProvider::from_env()?,
75
+ };
76
+ Ok(Arc::new(provider))
77
+ }
78
+ "openrouter" => {
79
+ let provider = match api_key {
80
+ Some(api_key) => match api_base {
81
+ Some(api_base) => {
82
+ OpenRouterProvider::with_base_url(api_key, api_base.to_string())?
83
+ }
84
+ None => OpenRouterProvider::new(api_key)?,
85
+ },
86
+ None => OpenRouterProvider::from_env()?,
87
+ };
88
+ Ok(Arc::new(provider))
89
+ }
90
+ _ => Err(SimpleAgentsError::Config(format!(
91
+ "Unknown provider '{provider_name}'"
92
+ ))),
93
+ }
94
+ }
95
+
96
+ fn is_local_base(api_base: &str) -> bool {
97
+ api_base.contains("localhost") || api_base.contains("127.0.0.1")
98
+ }
99
+
100
+ fn build_request(
101
+ model: &str,
102
+ prompt: &str,
103
+ max_tokens: Option<u32>,
104
+ temperature: Option<f32>,
105
+ ) -> Result<CompletionRequest> {
106
+ if model.is_empty() {
107
+ return Err(SimpleAgentsError::Config(
108
+ "model cannot be empty".to_string(),
109
+ ));
110
+ }
111
+ if prompt.is_empty() {
112
+ return Err(SimpleAgentsError::Config(
113
+ "prompt cannot be empty".to_string(),
114
+ ));
115
+ }
116
+
117
+ let mut builder = CompletionRequest::builder()
118
+ .model(model)
119
+ .message(Message::user(prompt));
120
+
121
+ if let Some(max_tokens) = max_tokens {
122
+ builder = builder.max_tokens(max_tokens);
123
+ }
124
+ if let Some(temperature) = temperature {
125
+ builder = builder.temperature(temperature);
126
+ }
127
+
128
+ builder.build()
129
+ }
130
+
131
+ fn build_request_with_messages(
132
+ model: &str,
133
+ messages: Vec<Message>,
134
+ max_tokens: Option<u32>,
135
+ temperature: Option<f32>,
136
+ top_p: Option<f32>,
137
+ response_format: Option<ResponseFormat>,
138
+ ) -> Result<CompletionRequest> {
139
+ if model.is_empty() {
140
+ return Err(SimpleAgentsError::Config(
141
+ "model cannot be empty".to_string(),
142
+ ));
143
+ }
144
+ if messages.is_empty() {
145
+ return Err(SimpleAgentsError::Config(
146
+ "messages cannot be empty".to_string(),
147
+ ));
148
+ }
149
+
150
+ let mut builder = CompletionRequest::builder().model(model);
151
+ for message in messages {
152
+ builder = builder.message(message);
153
+ }
154
+
155
+ if let Some(max_tokens) = max_tokens {
156
+ builder = builder.max_tokens(max_tokens);
157
+ }
158
+ if let Some(temperature) = temperature {
159
+ builder = builder.temperature(temperature);
160
+ }
161
+ if let Some(top_p) = top_p {
162
+ builder = builder.top_p(top_p);
163
+ }
164
+ if let Some(format) = response_format {
165
+ builder = builder.response_format(format);
166
+ }
167
+
168
+ builder.build()
169
+ }
170
+
171
+ fn parse_messages(messages: &PyAny) -> Result<Vec<Message>> {
172
+ let list: &PyList = messages.downcast().map_err(|_| {
173
+ SimpleAgentsError::Config("messages must be a list of dicts".to_string())
174
+ })?;
175
+ let mut result = Vec::with_capacity(list.len());
176
+
177
+ for (idx, item) in list.iter().enumerate() {
178
+ let dict: &PyDict = item
179
+ .downcast()
180
+ .map_err(|_| SimpleAgentsError::Config(format!("message[{idx}] must be a dict")))?;
181
+
182
+ let role_obj = dict.get_item("role").ok_or_else(|| {
183
+ SimpleAgentsError::Config(format!("message[{idx}] missing 'role'"))
184
+ })?;
185
+ let role: &str = role_obj.extract().map_err(|_| {
186
+ SimpleAgentsError::Config(format!("message[{idx}].role must be a string"))
187
+ })?;
188
+
189
+ let content_obj = dict.get_item("content").ok_or_else(|| {
190
+ SimpleAgentsError::Config(format!("message[{idx}] missing 'content'"))
191
+ })?;
192
+ let content: &str = content_obj.extract().map_err(|_| {
193
+ SimpleAgentsError::Config(format!(
194
+ "message[{idx}].content must be a string"
195
+ ))
196
+ })?;
197
+
198
+ let mut message = match role {
199
+ "user" => Message::user(content),
200
+ "assistant" => Message::assistant(content),
201
+ "system" => Message::system(content),
202
+ "tool" => {
203
+ let tool_call_id = dict
204
+ .get_item("tool_call_id")
205
+ .ok_or_else(|| {
206
+ SimpleAgentsError::Config(format!(
207
+ "message[{idx}] missing 'tool_call_id' for tool role"
208
+ ))
209
+ })?
210
+ .extract::<String>()
211
+ .map_err(|_| {
212
+ SimpleAgentsError::Config(format!(
213
+ "message[{idx}].tool_call_id must be a string"
214
+ ))
215
+ })?;
216
+ Message::tool(content, tool_call_id)
217
+ }
218
+ _ => {
219
+ return Err(SimpleAgentsError::Config(format!(
220
+ "message[{idx}].role must be one of: user, assistant, system, tool"
221
+ )))
222
+ }
223
+ };
224
+
225
+ if let Some(name_obj) = dict.get_item("name") {
226
+ if !name_obj.is_none() {
227
+ let name: String = name_obj.extract().map_err(|_| {
228
+ SimpleAgentsError::Config(format!("message[{idx}].name must be a string"))
229
+ })?;
230
+ message = message.with_name(name);
231
+ }
232
+ }
233
+
234
+ result.push(message);
235
+ }
236
+
237
+ Ok(result)
238
+ }
239
+
240
+ fn py_err(error: SimpleAgentsError) -> PyErr {
241
+ PyRuntimeError::new_err(error.to_string())
242
+ }
243
+
244
+ #[pymethods]
245
+ #[allow(clippy::useless_conversion)]
246
+ impl Client {
247
+ #[new]
248
+ #[pyo3(signature = (provider, api_key=None, api_base=None))]
249
+ fn new(provider: &str, api_key: Option<String>, api_base: Option<String>) -> PyResult<Self> {
250
+ let provider = provider_from_params(
251
+ provider,
252
+ api_key.as_deref(),
253
+ api_base.as_deref(),
254
+ )
255
+ .map_err(py_err)?;
256
+ let client = SimpleAgentsClientBuilder::new()
257
+ .with_provider(provider)
258
+ .build()
259
+ .map_err(py_err)?;
260
+ let runtime = Runtime::new().map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
261
+
262
+ Ok(Self {
263
+ runtime: Mutex::new(runtime),
264
+ client,
265
+ })
266
+ }
267
+
268
+ #[pyo3(signature = (model, prompt, max_tokens=None, temperature=None))]
269
+ fn complete(
270
+ &self,
271
+ model: &str,
272
+ prompt: &str,
273
+ max_tokens: Option<u32>,
274
+ temperature: Option<f32>,
275
+ ) -> PyResult<String> {
276
+ let request = build_request(model, prompt, max_tokens, temperature).map_err(py_err)?;
277
+ let runtime = self
278
+ .runtime
279
+ .lock()
280
+ .map_err(|_| PyRuntimeError::new_err("runtime lock poisoned"))?;
281
+ let response = runtime
282
+ .block_on(self.client.complete(&request))
283
+ .map_err(py_err)?;
284
+
285
+ Ok(response.content().unwrap_or_default().to_string())
286
+ }
287
+
288
+ #[pyo3(signature = (model, messages, max_tokens=None, temperature=None, top_p=None))]
289
+ fn complete_messages(
290
+ &self,
291
+ model: &str,
292
+ messages: &PyAny,
293
+ max_tokens: Option<u32>,
294
+ temperature: Option<f32>,
295
+ top_p: Option<f32>,
296
+ ) -> PyResult<String> {
297
+ let messages = parse_messages(messages).map_err(py_err)?;
298
+ let request = build_request_with_messages(
299
+ model,
300
+ messages,
301
+ max_tokens,
302
+ temperature,
303
+ top_p,
304
+ None,
305
+ )
306
+ .map_err(py_err)?;
307
+ let runtime = self
308
+ .runtime
309
+ .lock()
310
+ .map_err(|_| PyRuntimeError::new_err("runtime lock poisoned"))?;
311
+ let response = runtime
312
+ .block_on(self.client.complete(&request))
313
+ .map_err(py_err)?;
314
+
315
+ Ok(response.content().unwrap_or_default().to_string())
316
+ }
317
+
318
+ #[pyo3(signature = (model, messages, max_tokens=None, temperature=None, top_p=None))]
319
+ fn complete_json(
320
+ &self,
321
+ model: &str,
322
+ messages: &PyAny,
323
+ max_tokens: Option<u32>,
324
+ temperature: Option<f32>,
325
+ top_p: Option<f32>,
326
+ ) -> PyResult<String> {
327
+ let messages = parse_messages(messages).map_err(py_err)?;
328
+ let request = build_request_with_messages(
329
+ model,
330
+ messages,
331
+ max_tokens,
332
+ temperature,
333
+ top_p,
334
+ Some(ResponseFormat::JsonObject),
335
+ )
336
+ .map_err(py_err)?;
337
+ let runtime = self
338
+ .runtime
339
+ .lock()
340
+ .map_err(|_| PyRuntimeError::new_err("runtime lock poisoned"))?;
341
+ let response = runtime
342
+ .block_on(self.client.complete(&request))
343
+ .map_err(py_err)?;
344
+
345
+ Ok(response.content().unwrap_or_default().to_string())
346
+ }
347
+
348
+ #[pyo3(signature = (model, messages, schema, schema_name, max_tokens=None, temperature=None, top_p=None, strict=True))]
349
+ fn complete_json_schema(
350
+ &self,
351
+ model: &str,
352
+ messages: &PyAny,
353
+ schema: &PyAny,
354
+ schema_name: &str,
355
+ max_tokens: Option<u32>,
356
+ temperature: Option<f32>,
357
+ top_p: Option<f32>,
358
+ strict: bool,
359
+ ) -> PyResult<String> {
360
+ let messages = parse_messages(messages).map_err(py_err)?;
361
+ let schema_value: serde_json::Value = schema.extract().map_err(|_| {
362
+ py_err(SimpleAgentsError::Config(
363
+ "schema must be JSON-serializable".to_string(),
364
+ ))
365
+ })?;
366
+ let response_format = ResponseFormat::JsonSchema {
367
+ json_schema: JsonSchemaFormat {
368
+ name: schema_name.to_string(),
369
+ schema: schema_value,
370
+ strict: Some(strict),
371
+ },
372
+ };
373
+ let request = build_request_with_messages(
374
+ model,
375
+ messages,
376
+ max_tokens,
377
+ temperature,
378
+ top_p,
379
+ Some(response_format),
380
+ )
381
+ .map_err(py_err)?;
382
+ let runtime = self
383
+ .runtime
384
+ .lock()
385
+ .map_err(|_| PyRuntimeError::new_err("runtime lock poisoned"))?;
386
+ let response = runtime
387
+ .block_on(self.client.complete(&request))
388
+ .map_err(py_err)?;
389
+
390
+ Ok(response.content().unwrap_or_default().to_string())
391
+ }
392
+ }
393
+
394
+ #[pymodule]
395
+ fn simple_agents_py(_py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
396
+ module.add_class::<Client>()?;
397
+ Ok(())
398
+ }
@@ -0,0 +1 @@
1
+
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "simple-agents-py"
7
- version = "0.1.5"
7
+ version = "0.1.6"
8
8
  description = "Python bindings for SimpleAgents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -23,6 +23,7 @@ dev = ["pytest>=8.0"]
23
23
  [tool.maturin]
24
24
  bindings = "pyo3"
25
25
  module-name = "simple_agents_py"
26
+ include = ["simple_agents_py.pyi", "py.typed"]
26
27
  manifest-path = "crates/simple-agents-py/Cargo.toml"
27
28
 
28
29
  [tool.pytest.ini_options]
@@ -0,0 +1,45 @@
1
+ class Client:
2
+ def __init__(
3
+ self,
4
+ provider: str,
5
+ api_key: str | None = None,
6
+ api_base: str | None = None,
7
+ ) -> None: ...
8
+
9
+ def complete(
10
+ self,
11
+ model: str,
12
+ prompt: str,
13
+ max_tokens: int | None = None,
14
+ temperature: float | None = None,
15
+ ) -> str: ...
16
+
17
+ def complete_messages(
18
+ self,
19
+ model: str,
20
+ messages: list[dict[str, object]],
21
+ max_tokens: int | None = None,
22
+ temperature: float | None = None,
23
+ top_p: float | None = None,
24
+ ) -> str: ...
25
+
26
+ def complete_json(
27
+ self,
28
+ model: str,
29
+ messages: list[dict[str, object]],
30
+ max_tokens: int | None = None,
31
+ temperature: float | None = None,
32
+ top_p: float | None = None,
33
+ ) -> str: ...
34
+
35
+ def complete_json_schema(
36
+ self,
37
+ model: str,
38
+ messages: list[dict[str, object]],
39
+ schema: dict[str, object],
40
+ schema_name: str,
41
+ max_tokens: int | None = None,
42
+ temperature: float | None = None,
43
+ top_p: float | None = None,
44
+ strict: bool = True,
45
+ ) -> str: ...
@@ -1,182 +0,0 @@
1
- //! Python bindings for SimpleAgents using PyO3.
2
-
3
- #![allow(clippy::useless_conversion)]
4
-
5
- use pyo3::exceptions::PyRuntimeError;
6
- use pyo3::prelude::*;
7
- use reqwest::Client as HttpClient;
8
- use simple_agents_core::{SimpleAgentsClient, SimpleAgentsClientBuilder};
9
- use simple_agents_providers::anthropic::AnthropicProvider;
10
- use simple_agents_providers::openai::OpenAIProvider;
11
- use simple_agents_providers::openrouter::OpenRouterProvider;
12
- use simple_agent_type::message::Message;
13
- use simple_agent_type::prelude::{ApiKey, CompletionRequest, Provider, Result, SimpleAgentsError};
14
- use std::sync::{Arc, Mutex};
15
- use std::time::Duration;
16
-
17
- type Runtime = tokio::runtime::Runtime;
18
-
19
- #[pyclass]
20
- struct Client {
21
- runtime: Mutex<Runtime>,
22
- client: SimpleAgentsClient,
23
- }
24
-
25
- fn provider_from_params(
26
- provider_name: &str,
27
- api_key: Option<&str>,
28
- api_base: Option<&str>,
29
- ) -> Result<Arc<dyn Provider>> {
30
- let api_key = match api_key {
31
- Some(value) => Some(ApiKey::new(value)?),
32
- None => None,
33
- };
34
-
35
- match provider_name {
36
- "openai" => {
37
- let provider = match api_key {
38
- Some(api_key) => match api_base {
39
- Some(api_base) => {
40
- if is_local_base(api_base) {
41
- let client = HttpClient::builder()
42
- .timeout(Duration::from_secs(30))
43
- .pool_max_idle_per_host(10)
44
- .pool_idle_timeout(Duration::from_secs(90))
45
- .no_proxy()
46
- .build()
47
- .map_err(|e| {
48
- SimpleAgentsError::Config(format!(
49
- "Failed to create HTTP client: {}",
50
- e
51
- ))
52
- })?;
53
- OpenAIProvider::with_client(api_key, api_base.to_string(), client)?
54
- } else {
55
- OpenAIProvider::with_base_url(api_key, api_base.to_string())?
56
- }
57
- }
58
- None => OpenAIProvider::new(api_key)?,
59
- },
60
- None => OpenAIProvider::from_env()?,
61
- };
62
- Ok(Arc::new(provider))
63
- }
64
- "anthropic" => {
65
- let provider = match api_key {
66
- Some(api_key) => match api_base {
67
- Some(api_base) => {
68
- AnthropicProvider::with_base_url(api_key, api_base.to_string())?
69
- }
70
- None => AnthropicProvider::new(api_key)?,
71
- },
72
- None => AnthropicProvider::from_env()?,
73
- };
74
- Ok(Arc::new(provider))
75
- }
76
- "openrouter" => {
77
- let provider = match api_key {
78
- Some(api_key) => match api_base {
79
- Some(api_base) => {
80
- OpenRouterProvider::with_base_url(api_key, api_base.to_string())?
81
- }
82
- None => OpenRouterProvider::new(api_key)?,
83
- },
84
- None => OpenRouterProvider::from_env()?,
85
- };
86
- Ok(Arc::new(provider))
87
- }
88
- _ => Err(SimpleAgentsError::Config(format!(
89
- "Unknown provider '{provider_name}'"
90
- ))),
91
- }
92
- }
93
-
94
- fn is_local_base(api_base: &str) -> bool {
95
- api_base.contains("localhost") || api_base.contains("127.0.0.1")
96
- }
97
-
98
- fn build_request(
99
- model: &str,
100
- prompt: &str,
101
- max_tokens: Option<u32>,
102
- temperature: Option<f32>,
103
- ) -> Result<CompletionRequest> {
104
- if model.is_empty() {
105
- return Err(SimpleAgentsError::Config(
106
- "model cannot be empty".to_string(),
107
- ));
108
- }
109
- if prompt.is_empty() {
110
- return Err(SimpleAgentsError::Config(
111
- "prompt cannot be empty".to_string(),
112
- ));
113
- }
114
-
115
- let mut builder = CompletionRequest::builder()
116
- .model(model)
117
- .message(Message::user(prompt));
118
-
119
- if let Some(max_tokens) = max_tokens {
120
- builder = builder.max_tokens(max_tokens);
121
- }
122
- if let Some(temperature) = temperature {
123
- builder = builder.temperature(temperature);
124
- }
125
-
126
- builder.build()
127
- }
128
-
129
- fn py_err(error: SimpleAgentsError) -> PyErr {
130
- PyRuntimeError::new_err(error.to_string())
131
- }
132
-
133
- #[pymethods]
134
- #[allow(clippy::useless_conversion)]
135
- impl Client {
136
- #[new]
137
- #[pyo3(signature = (provider, api_key=None, api_base=None))]
138
- fn new(provider: &str, api_key: Option<String>, api_base: Option<String>) -> PyResult<Self> {
139
- let provider = provider_from_params(
140
- provider,
141
- api_key.as_deref(),
142
- api_base.as_deref(),
143
- )
144
- .map_err(py_err)?;
145
- let client = SimpleAgentsClientBuilder::new()
146
- .with_provider(provider)
147
- .build()
148
- .map_err(py_err)?;
149
- let runtime = Runtime::new().map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
150
-
151
- Ok(Self {
152
- runtime: Mutex::new(runtime),
153
- client,
154
- })
155
- }
156
-
157
- #[pyo3(signature = (model, prompt, max_tokens=None, temperature=None))]
158
- fn complete(
159
- &self,
160
- model: &str,
161
- prompt: &str,
162
- max_tokens: Option<u32>,
163
- temperature: Option<f32>,
164
- ) -> PyResult<String> {
165
- let request = build_request(model, prompt, max_tokens, temperature).map_err(py_err)?;
166
- let runtime = self
167
- .runtime
168
- .lock()
169
- .map_err(|_| PyRuntimeError::new_err("runtime lock poisoned"))?;
170
- let response = runtime
171
- .block_on(self.client.complete(&request))
172
- .map_err(py_err)?;
173
-
174
- Ok(response.content().unwrap_or_default().to_string())
175
- }
176
- }
177
-
178
- #[pymodule]
179
- fn simple_agents_py(_py: Python<'_>, module: &Bound<'_, PyModule>) -> PyResult<()> {
180
- module.add_class::<Client>()?;
181
- Ok(())
182
- }