agentforge-openai 0.2.3__tar.gz → 0.2.4__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.
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/PKG-INFO +2 -2
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/pyproject.toml +2 -2
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/src/agentforge_openai/client.py +22 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/tests/unit/test_openai_client.py +59 -1
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/.gitignore +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/LICENSE +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/README.md +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/src/agentforge_openai/__init__.py +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/src/agentforge_openai/_inmem_runner.py +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/src/agentforge_openai/_pricing.py +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/src/agentforge_openai/_runner.py +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/src/agentforge_openai/embedding.py +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/src/agentforge_openai/py.typed +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/tests/conftest.py +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/tests/integration/test_openai_live.py +0 -0
- {agentforge_openai-0.2.3 → agentforge_openai-0.2.4}/tests/unit/test_openai_embedding.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentforge-openai
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: OpenAI LLM + embeddings provider for AgentForge — gpt-4o / o-series + text-embedding-3-*
|
|
5
5
|
Project-URL: Homepage, https://github.com/Scaffoldic/agentforge-py
|
|
6
6
|
Project-URL: Repository, https://github.com/Scaffoldic/agentforge-py
|
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.13
|
|
22
|
-
Requires-Dist: agentforge-core~=0.2.
|
|
22
|
+
Requires-Dist: agentforge-core~=0.2.4
|
|
23
23
|
Provides-Extra: openai
|
|
24
24
|
Requires-Dist: openai>=1.50; extra == 'openai'
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[project]
|
|
10
10
|
name = "agentforge-openai"
|
|
11
|
-
version = "0.2.
|
|
11
|
+
version = "0.2.4"
|
|
12
12
|
description = "OpenAI LLM + embeddings provider for AgentForge — gpt-4o / o-series + text-embedding-3-*"
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
requires-python = ">=3.13"
|
|
@@ -29,7 +29,7 @@ classifiers = [
|
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
dependencies = [
|
|
32
|
-
"agentforge-core ~= 0.2.
|
|
32
|
+
"agentforge-core ~= 0.2.4",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
|
@@ -14,10 +14,12 @@ Finish reasons normalised:
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import json
|
|
17
18
|
from collections.abc import AsyncIterator
|
|
18
19
|
from typing import TYPE_CHECKING, Any
|
|
19
20
|
|
|
20
21
|
from agentforge_core.contracts.llm import LLMClient
|
|
22
|
+
from agentforge_core.contracts.tool import validate_tool_name
|
|
21
23
|
from agentforge_core.production.exceptions import ModuleError
|
|
22
24
|
from agentforge_core.resolver import register_provider
|
|
23
25
|
from agentforge_core.values.messages import (
|
|
@@ -298,12 +300,32 @@ def _message_to_openai(message: Message) -> dict[str, Any]:
|
|
|
298
300
|
if message.role == "system":
|
|
299
301
|
# Should be hoisted by the caller; defensive passthrough.
|
|
300
302
|
return {"role": "system", "content": message.content}
|
|
303
|
+
if message.role == "assistant" and message.tool_calls:
|
|
304
|
+
# Round-trip tool_calls so the subsequent role="tool" message
|
|
305
|
+
# pairs cleanly via tool_call_id (bug-009).
|
|
306
|
+
return {
|
|
307
|
+
"role": "assistant",
|
|
308
|
+
"content": message.content or None,
|
|
309
|
+
"tool_calls": [
|
|
310
|
+
{
|
|
311
|
+
"id": tc.id,
|
|
312
|
+
"type": "function",
|
|
313
|
+
"function": {
|
|
314
|
+
"name": tc.name,
|
|
315
|
+
"arguments": json.dumps(dict(tc.arguments)),
|
|
316
|
+
},
|
|
317
|
+
}
|
|
318
|
+
for tc in message.tool_calls
|
|
319
|
+
],
|
|
320
|
+
}
|
|
301
321
|
return {"role": message.role, "content": message.content}
|
|
302
322
|
|
|
303
323
|
|
|
304
324
|
def _tools_to_openai(tools: list[ToolSpec] | None) -> list[dict[str, Any]] | None:
|
|
305
325
|
if not tools:
|
|
306
326
|
return None
|
|
327
|
+
for t in tools:
|
|
328
|
+
validate_tool_name(t.name)
|
|
307
329
|
return [
|
|
308
330
|
{
|
|
309
331
|
"type": "function",
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
|
|
5
7
|
import pytest
|
|
6
|
-
from agentforge_core.
|
|
8
|
+
from agentforge_core.production.exceptions import ToolNameInvalidError
|
|
9
|
+
from agentforge_core.values.messages import Message, ToolCall, ToolSpec
|
|
7
10
|
from agentforge_openai import OpenAIClient
|
|
8
11
|
from agentforge_openai._inmem_runner import FakeOpenAIRunner
|
|
9
12
|
from agentforge_openai._pricing import chat_cost_usd
|
|
@@ -227,6 +230,46 @@ async def test_tool_role_messages_pass_with_tool_call_id(
|
|
|
227
230
|
assert sent["content"] == "42"
|
|
228
231
|
|
|
229
232
|
|
|
233
|
+
@pytest.mark.asyncio
|
|
234
|
+
async def test_assistant_turn_with_tool_calls_emits_openai_tool_calls(
|
|
235
|
+
client: OpenAIClient,
|
|
236
|
+
fake_runner: FakeOpenAIRunner,
|
|
237
|
+
) -> None:
|
|
238
|
+
"""bug-009: an assistant Message carrying framework tool_calls must
|
|
239
|
+
serialise to OpenAI's `tool_calls` array with JSON-encoded arguments,
|
|
240
|
+
so the subsequent role="tool" message pairs cleanly via tool_call_id."""
|
|
241
|
+
fake_runner.set_chat_response(
|
|
242
|
+
{
|
|
243
|
+
"model": "gpt-4o-mini",
|
|
244
|
+
"choices": [
|
|
245
|
+
{
|
|
246
|
+
"index": 0,
|
|
247
|
+
"message": {"role": "assistant", "content": ""},
|
|
248
|
+
"finish_reason": "stop",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
await client.call(
|
|
255
|
+
system="",
|
|
256
|
+
messages=[
|
|
257
|
+
Message(
|
|
258
|
+
role="assistant",
|
|
259
|
+
content="calling",
|
|
260
|
+
tool_calls=(ToolCall(id="call_1", name="search", arguments={"q": "x"}),),
|
|
261
|
+
),
|
|
262
|
+
Message(role="tool", content="result", tool_call_id="call_1"),
|
|
263
|
+
],
|
|
264
|
+
)
|
|
265
|
+
sent = fake_runner.chat_calls[0].messages[0]
|
|
266
|
+
assert sent["role"] == "assistant"
|
|
267
|
+
assert sent["tool_calls"][0]["id"] == "call_1"
|
|
268
|
+
assert sent["tool_calls"][0]["type"] == "function"
|
|
269
|
+
assert sent["tool_calls"][0]["function"]["name"] == "search"
|
|
270
|
+
assert json.loads(sent["tool_calls"][0]["function"]["arguments"]) == {"q": "x"}
|
|
271
|
+
|
|
272
|
+
|
|
230
273
|
@pytest.mark.asyncio
|
|
231
274
|
async def test_call_finish_reason_length_maps_to_max_tokens(
|
|
232
275
|
client: OpenAIClient,
|
|
@@ -325,6 +368,21 @@ async def test_call_passes_tool_specs(
|
|
|
325
368
|
assert tools_arg[0]["function"]["parameters"] == schema
|
|
326
369
|
|
|
327
370
|
|
|
371
|
+
@pytest.mark.asyncio
|
|
372
|
+
async def test_call_rejects_illegal_tool_name_locally(
|
|
373
|
+
client: OpenAIClient,
|
|
374
|
+
fake_runner: FakeOpenAIRunner,
|
|
375
|
+
) -> None:
|
|
376
|
+
"""bug-017: an illegal tool name fails locally before any request."""
|
|
377
|
+
with pytest.raises(ToolNameInvalidError, match="kb_search"):
|
|
378
|
+
await client.call(
|
|
379
|
+
system="",
|
|
380
|
+
messages=[_user("hi")],
|
|
381
|
+
tools=[ToolSpec(name="kb.search", description="d", schema={"type": "object"})],
|
|
382
|
+
)
|
|
383
|
+
assert fake_runner.chat_calls == []
|
|
384
|
+
|
|
385
|
+
|
|
328
386
|
# ----------------------------------------------------------------------
|
|
329
387
|
# Streaming
|
|
330
388
|
# ----------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|