evidor 1.10.1.dev1__tar.gz → 1.10.2.dev1__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 (87) hide show
  1. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/CHANGELOG.md +11 -0
  2. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/PKG-INFO +10 -2
  3. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/README.md +9 -1
  4. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/pyproject.toml +1 -1
  5. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/providers/base.py +5 -0
  6. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/providers/openai.py +18 -3
  7. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_providers.py +51 -1
  8. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/.github/workflows/release.yml +0 -0
  9. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/.gitignore +0 -0
  10. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/AGENTS.md +0 -0
  11. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/CODE_OF_CONDUCT.md +0 -0
  12. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/LICENSE +0 -0
  13. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/requirements.txt +0 -0
  14. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/__init__.py +0 -0
  15. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/__init__.py +0 -0
  16. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/core/__init__.py +0 -0
  17. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/core/agent.py +0 -0
  18. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/core/context.py +0 -0
  19. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/core/runner.py +0 -0
  20. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/core/telemetry_bridge.py +0 -0
  21. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/providers/__init__.py +0 -0
  22. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/providers/anthropic.py +0 -0
  23. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/agent/providers/gemini.py +0 -0
  24. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/mcp/__init__.py +0 -0
  25. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/mcp/client.py +0 -0
  26. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/mcp/config.py +0 -0
  27. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/mcp/runner.py +0 -0
  28. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/mcp/session.py +0 -0
  29. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/models/__init__.py +0 -0
  30. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/models/generation.py +0 -0
  31. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/models/streaming.py +0 -0
  32. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/models/thinking.py +0 -0
  33. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/__init__.py +0 -0
  34. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/adapters/__init__.py +0 -0
  35. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/adapters/langfuse.py +0 -0
  36. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/adapters/opentelemetry.py +0 -0
  37. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/adapters/phoenix.py +0 -0
  38. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/adapters/prometheus.py +0 -0
  39. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/context.py +0 -0
  40. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/events.py +0 -0
  41. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/runtime.py +0 -0
  42. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/semconv.py +0 -0
  43. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/telemetry/sink.py +0 -0
  44. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/__init__.py +0 -0
  45. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/core/__init__.py +0 -0
  46. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/core/tool_executor.py +0 -0
  47. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/core/tools.py +0 -0
  48. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/core/utils.py +0 -0
  49. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/__init__.py +0 -0
  50. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/calculator.py +0 -0
  51. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/evidence_store.py +0 -0
  52. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/filesystem/__init__.py +0 -0
  53. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/filesystem/_common.py +0 -0
  54. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/filesystem/list_files.py +0 -0
  55. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/filesystem/mutate_files.py +0 -0
  56. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/filesystem/read_file.py +0 -0
  57. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/filesystem/search_files.py +0 -0
  58. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/get_current_time.py +0 -0
  59. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/read_webpage.py +0 -0
  60. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/__init__.py +0 -0
  61. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/_base.py +0 -0
  62. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/http.py +0 -0
  63. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/providers/__init__.py +0 -0
  64. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/providers/brave.py +0 -0
  65. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/providers/exa.py +0 -0
  66. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/providers/tavily.py +0 -0
  67. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/tools/native/web_search/tool.py +0 -0
  68. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/utils/__init__.py +0 -0
  69. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/utils/execution.py +0 -0
  70. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/src/evidor/utils/schema.py +0 -0
  71. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_agent.py +0 -0
  72. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_builtin_tools.py +0 -0
  73. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_conversation.py +0 -0
  74. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_filesystem_tools.py +0 -0
  75. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_langfuse_adapter.py +0 -0
  76. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_mcp.py +0 -0
  77. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_models.py +0 -0
  78. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_native_tools.py +0 -0
  79. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_opentelemetry_adapter.py +0 -0
  80. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_phoenix_adapter.py +0 -0
  81. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_prometheus_adapter.py +0 -0
  82. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_streaming.py +0 -0
  83. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_telemetry_events.py +0 -0
  84. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_telemetry_integration.py +0 -0
  85. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_thinking.py +0 -0
  86. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_tools.py +0 -0
  87. {evidor-1.10.1.dev1 → evidor-1.10.2.dev1}/tests/test_web_search.py +0 -0
@@ -2,6 +2,17 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.10.2-dev.1 (2026-10-21)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **agent**: Adding base url support
10
+ ([`f5f787f`](https://github.com/vedangiitb/evidor-core/commit/f5f787f3ab9015c73d6637b74c4af6fc7649035a))
11
+
12
+
13
+ ## v1.10.1 (2026-10-20)
14
+
15
+
5
16
  ## v1.10.1-dev.1 (2026-10-20)
6
17
 
7
18
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evidor
3
- Version: 1.10.1-dev.1
3
+ Version: 1.10.2-dev.1
4
4
  Summary: A small, provider-agnostic harness for LLM input and output.
5
5
  Project-URL: Homepage, https://github.com/evidor/evidor-core
6
6
  Project-URL: Repository, https://github.com/evidor/evidor-core
@@ -1255,7 +1255,15 @@ Supported providers, example models, and reasoning/thinking support:
1255
1255
  | `AnthropicProvider` | `claude-3-7-sonnet` | Extended budget (`budget_tokens >= 1024`) or adaptive | `ANTHROPIC_API_KEY` | `evidor[anthropic]` |
1256
1256
  | `GeminiProvider` | `gemini-3.8-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-pro-preview` | `thinking_level` (`"minimal"`, `"low"`, `"medium"`, `"high"`) or budget | `GEMINI_API_KEY` | `evidor[gemini]` |
1257
1257
 
1258
- API keys can be supplied explicitly via the `api_key` constructor argument or read automatically from environment variables.
1258
+ API keys can be supplied explicitly via the `api_key` constructor argument or read automatically from environment variables. Custom base URLs for OpenAI-compatible providers (such as DeepSeek) can be supplied via the `base_url` constructor argument or `OPENAI_BASE_URL` environment variable:
1259
+
1260
+ ```python
1261
+ provider = OpenAIProvider(
1262
+ model="deepseek-flash",
1263
+ api_key="your-deepseek-api-key",
1264
+ base_url="https://api.deepseek.com",
1265
+ )
1266
+ ```
1259
1267
 
1260
1268
  ### Modern Provider APIs & Zero Legacy Fallbacks
1261
1269
 
@@ -1168,7 +1168,15 @@ Supported providers, example models, and reasoning/thinking support:
1168
1168
  | `AnthropicProvider` | `claude-3-7-sonnet` | Extended budget (`budget_tokens >= 1024`) or adaptive | `ANTHROPIC_API_KEY` | `evidor[anthropic]` |
1169
1169
  | `GeminiProvider` | `gemini-3.8-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-pro-preview` | `thinking_level` (`"minimal"`, `"low"`, `"medium"`, `"high"`) or budget | `GEMINI_API_KEY` | `evidor[gemini]` |
1170
1170
 
1171
- API keys can be supplied explicitly via the `api_key` constructor argument or read automatically from environment variables.
1171
+ API keys can be supplied explicitly via the `api_key` constructor argument or read automatically from environment variables. Custom base URLs for OpenAI-compatible providers (such as DeepSeek) can be supplied via the `base_url` constructor argument or `OPENAI_BASE_URL` environment variable:
1172
+
1173
+ ```python
1174
+ provider = OpenAIProvider(
1175
+ model="deepseek-flash",
1176
+ api_key="your-deepseek-api-key",
1177
+ base_url="https://api.deepseek.com",
1178
+ )
1179
+ ```
1172
1180
 
1173
1181
  ### Modern Provider APIs & Zero Legacy Fallbacks
1174
1182
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "evidor"
7
- version = "1.10.1-dev.1"
7
+ version = "1.10.2-dev.1"
8
8
  description = "A small, provider-agnostic harness for LLM input and output."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -131,6 +131,11 @@ class BaseProvider:
131
131
  """The token threshold for provider-native context compaction, if configured."""
132
132
  return getattr(self, "_compact_threshold", None)
133
133
 
134
+ @property
135
+ def base_url(self) -> str | None:
136
+ """The custom base URL for the model provider endpoint, if configured."""
137
+ return getattr(self, "_base_url", None)
138
+
134
139
  async def generate_async(
135
140
  self, request: GenerationRequest, *, stream: bool = False
136
141
  ) -> GenerationResponse | AsyncStreamResponse:
@@ -65,6 +65,7 @@ class OpenAIProvider(BaseProvider):
65
65
  api_key: str | None = None,
66
66
  thinking: Any = None,
67
67
  compact_threshold: int | None = None,
68
+ base_url: str | None = None,
68
69
  ) -> None:
69
70
  if thinking is not None:
70
71
  from evidor.models import OpenAIThinkingConfig
@@ -80,6 +81,7 @@ class OpenAIProvider(BaseProvider):
80
81
 
81
82
  super().__init__(model=model, api_key=api_key, thinking=thinking_cfg)
82
83
  self._compact_threshold = compact_threshold
84
+ self._base_url = base_url
83
85
  self._sync_client: Any = None
84
86
  self._default_async_client: Any = None
85
87
 
@@ -91,12 +93,18 @@ class OpenAIProvider(BaseProvider):
91
93
  def compact_threshold(self) -> int | None:
92
94
  return self._compact_threshold
93
95
 
96
+ @property
97
+ def base_url(self) -> str | None:
98
+ """The custom base URL for OpenAI-compatible endpoints, if configured."""
99
+ return self._base_url
100
+
94
101
  def with_model(self, model: str) -> "OpenAIProvider":
95
102
  return OpenAIProvider(
96
103
  model=model,
97
104
  api_key=self._api_key,
98
105
  thinking=self._thinking,
99
106
  compact_threshold=self._compact_threshold,
107
+ base_url=self._base_url,
100
108
  )
101
109
 
102
110
  @overload
@@ -291,7 +299,10 @@ class OpenAIProvider(BaseProvider):
291
299
  except ImportError as error:
292
300
  raise ImportError("Install OpenAI support with: pip install 'evidor[openai]'") from error
293
301
 
294
- client = OpenAI(api_key=self._api_key)
302
+ client_kwargs: dict[str, Any] = {"api_key": self._api_key}
303
+ if self._base_url is not None:
304
+ client_kwargs["base_url"] = self._base_url
305
+ client = OpenAI(**client_kwargs)
295
306
  if not hasattr(client, "responses") or not hasattr(client.responses, "create"):
296
307
  raise RuntimeError(
297
308
  "Modern OpenAI Responses API (client.responses.create) is required. "
@@ -320,14 +331,18 @@ class OpenAIProvider(BaseProvider):
320
331
  for l in dead_loops:
321
332
  del self._async_clients[l]
322
333
 
334
+ client_kwargs: dict[str, Any] = {"api_key": self._api_key}
335
+ if self._base_url is not None:
336
+ client_kwargs["base_url"] = self._base_url
337
+
323
338
  if loop is not None:
324
339
  client = self._async_clients.get(loop)
325
340
  if is_client_closed(client):
326
- client = AsyncOpenAI(api_key=self._api_key)
341
+ client = AsyncOpenAI(**client_kwargs)
327
342
  self._async_clients[loop] = client
328
343
  else:
329
344
  if is_client_closed(self._default_async_client):
330
- self._default_async_client = AsyncOpenAI(api_key=self._api_key)
345
+ self._default_async_client = AsyncOpenAI(**client_kwargs)
331
346
  client = self._default_async_client
332
347
 
333
348
  if not hasattr(client, "responses") or not hasattr(client.responses, "create"):
@@ -22,12 +22,14 @@ from evidor import (
22
22
 
23
23
 
24
24
  def test_openai_provider_properties_and_with_model() -> None:
25
- provider = OpenAIProvider(model="gpt-4.1", api_key="sk-test")
25
+ provider = OpenAIProvider(model="gpt-4.1", api_key="sk-test", base_url="https://api.deepseek.com")
26
26
  assert provider.model == "gpt-4.1"
27
+ assert provider.base_url == "https://api.deepseek.com"
27
28
 
28
29
  new_provider = provider.with_model("gpt-4.1-mini")
29
30
  assert new_provider.model == "gpt-4.1-mini"
30
31
  assert new_provider._api_key == "sk-test"
32
+ assert new_provider.base_url == "https://api.deepseek.com"
31
33
 
32
34
 
33
35
  def test_openai_provider_generate(monkeypatch: pytest.MonkeyPatch) -> None:
@@ -60,6 +62,54 @@ def test_openai_provider_generate(monkeypatch: pytest.MonkeyPatch) -> None:
60
62
  )
61
63
 
62
64
 
65
+ def test_openai_provider_generate_with_base_url(monkeypatch: pytest.MonkeyPatch) -> None:
66
+ mock_openai = types.ModuleType("openai")
67
+ mock_client = MagicMock()
68
+ mock_response = MagicMock(output_text="DeepSeek text")
69
+ mock_client.responses.create.return_value = mock_response
70
+ mock_openai.OpenAI = MagicMock(return_value=mock_client) # type: ignore[attr-defined]
71
+
72
+ monkeypatch.setitem(sys.modules, "openai", mock_openai)
73
+
74
+ provider = OpenAIProvider(
75
+ model="deepseek-flash",
76
+ api_key="ds-key",
77
+ base_url="https://api.deepseek.com",
78
+ )
79
+ request = GenerationRequest(prompt="Hello DeepSeek")
80
+ response = provider.generate(request)
81
+
82
+ assert response.text == "DeepSeek text"
83
+ mock_openai.OpenAI.assert_called_once_with(api_key="ds-key", base_url="https://api.deepseek.com")
84
+
85
+
86
+ @pytest.mark.asyncio
87
+ async def test_openai_provider_async_with_base_url(monkeypatch: pytest.MonkeyPatch) -> None:
88
+ mock_openai = types.ModuleType("openai")
89
+ mock_client = MagicMock()
90
+ mock_client.is_closed = False
91
+ mock_response = MagicMock(output_text="DeepSeek async text")
92
+
93
+ async def _async_create(**kwargs: Any) -> Any:
94
+ return mock_response
95
+
96
+ mock_client.responses.create = _async_create
97
+ mock_openai.AsyncOpenAI = MagicMock(return_value=mock_client) # type: ignore[attr-defined]
98
+
99
+ monkeypatch.setitem(sys.modules, "openai", mock_openai)
100
+
101
+ provider = OpenAIProvider(
102
+ model="deepseek-flash",
103
+ api_key="ds-key",
104
+ base_url="https://api.deepseek.com",
105
+ )
106
+ request = GenerationRequest(prompt="Hello DeepSeek async")
107
+ response = await provider.generate_async(request)
108
+
109
+ assert response.text == "DeepSeek async text"
110
+ mock_openai.AsyncOpenAI.assert_called_once_with(api_key="ds-key", base_url="https://api.deepseek.com")
111
+
112
+
63
113
 
64
114
 
65
115
  def test_openai_provider_import_error(monkeypatch: pytest.MonkeyPatch) -> None:
File without changes
File without changes
File without changes