andrewyng--aisuite
41b1591f6d
Mechanical 'black .' over root package and platform/ (109 files; the Lint workflow checks the whole repo and has been red since platform landed unformatted, with a few more violations from community merges whose branches never ran CI). No functional change: full platform suite (264) and aisuite suites verified unchanged after formatting.
228 行
6.9 KiB
Python
228 行
6.9 KiB
Python
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
import aisuite as ai
|
|
from aisuite import Agent, Client, RunState, Runner
|
|
from tests.agents.helpers import chat_response
|
|
|
|
|
|
def test_run_result_to_state_round_trips_through_dict():
|
|
client = Client()
|
|
client.chat.completions.create = Mock(return_value=chat_response("first"))
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
|
|
result = Runner.run_sync(
|
|
agent,
|
|
"Hello",
|
|
client=client,
|
|
run_name="chat",
|
|
group_id="group_1",
|
|
tags=["tag"],
|
|
metadata={"task_type": "chat"},
|
|
)
|
|
|
|
state = RunState.from_dict(result.to_state().to_dict())
|
|
|
|
assert state.agent_name == "assistant"
|
|
assert state.run_name == "chat"
|
|
assert state.group_id == "group_1"
|
|
assert state.tags == ["tag"]
|
|
assert state.metadata == {"task_type": "chat"}
|
|
assert state.messages == result.messages
|
|
assert len(state.steps) == len(result.steps)
|
|
|
|
|
|
def test_run_sync_accepts_state_and_resumes_messages():
|
|
client = Client()
|
|
client.chat.completions.create = Mock(return_value=chat_response("second"))
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
state = RunState(
|
|
agent_name="assistant",
|
|
messages=[
|
|
{"role": "user", "content": "Hello"},
|
|
{"role": "assistant", "content": "first"},
|
|
],
|
|
group_id="group_1",
|
|
metadata={"task_type": "chat"},
|
|
)
|
|
state.add_user_message("Follow up")
|
|
|
|
result = Runner.run_sync(agent, state, client=client)
|
|
|
|
assert client.chat.completions.create.call_args.kwargs["messages"] == [
|
|
{"role": "user", "content": "Hello"},
|
|
{"role": "assistant", "content": "first"},
|
|
{"role": "user", "content": "Follow up"},
|
|
]
|
|
assert result.final_output == "second"
|
|
assert result.group_id == "group_1"
|
|
assert result.metadata == {"task_type": "chat"}
|
|
|
|
|
|
def test_continue_sync_reuses_result_context_and_appends_input():
|
|
client = Client()
|
|
client.chat.completions.create = Mock(
|
|
side_effect=[chat_response("first"), chat_response("second")]
|
|
)
|
|
agent = Agent(
|
|
name="assistant",
|
|
model="openai:gpt-4o",
|
|
tags=["agent"],
|
|
metadata={"team": "growth"},
|
|
)
|
|
|
|
first = Runner.run_sync(
|
|
agent,
|
|
"Hello",
|
|
client=client,
|
|
run_name="chat",
|
|
group_id="group_1",
|
|
tags=["run"],
|
|
metadata={"request_id": "req_1"},
|
|
)
|
|
second = Runner.continue_sync(first, "Follow up")
|
|
|
|
assert client.chat.completions.create.call_args_list[1].kwargs["messages"] == [
|
|
{"role": "user", "content": "Hello"},
|
|
{"role": "assistant", "content": "first"},
|
|
{"role": "user", "content": "Follow up"},
|
|
]
|
|
assert second.final_output == "second"
|
|
assert second.run_name == "chat"
|
|
assert second.group_id == "group_1"
|
|
assert second.tags == ["agent", "run"]
|
|
assert second.metadata == {"team": "growth", "request_id": "req_1"}
|
|
assert second.trace_id != first.trace_id
|
|
|
|
|
|
def test_run_sync_with_state_store_persists_first_turn():
|
|
client = Client()
|
|
client.chat.completions.create = Mock(return_value=chat_response("first"))
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
store = ai.InMemoryStateStore()
|
|
|
|
result = Runner.run_sync(
|
|
agent,
|
|
"Hello",
|
|
client=client,
|
|
state_store=store,
|
|
thread_id="thread_1",
|
|
run_name="chat",
|
|
)
|
|
|
|
stored = store.load_state("thread_1")
|
|
assert stored.revision == 1
|
|
assert stored.state.messages == result.messages
|
|
assert stored.state.run_name == "chat"
|
|
|
|
|
|
def test_run_sync_with_existing_thread_raises():
|
|
client = Client()
|
|
client.chat.completions.create = Mock(return_value=chat_response("first"))
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
store = ai.InMemoryStateStore()
|
|
Runner.run_sync(
|
|
agent, "Hello", client=client, state_store=store, thread_id="thread_1"
|
|
)
|
|
|
|
with pytest.raises(ai.ThreadAlreadyExistsError):
|
|
Runner.run_sync(
|
|
agent,
|
|
"Start over",
|
|
client=client,
|
|
state_store=store,
|
|
thread_id="thread_1",
|
|
)
|
|
|
|
|
|
def test_continue_sync_with_agent_loads_and_saves_persisted_state():
|
|
client = Client()
|
|
client.chat.completions.create = Mock(return_value=chat_response("second"))
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
store = ai.InMemoryStateStore()
|
|
store.save_state(
|
|
"thread_1",
|
|
RunState(
|
|
agent_name="assistant",
|
|
messages=[
|
|
{"role": "user", "content": "Hello"},
|
|
{"role": "assistant", "content": "first"},
|
|
],
|
|
run_name="chat",
|
|
metadata={"request_id": "req_1"},
|
|
),
|
|
)
|
|
|
|
result = Runner.continue_sync(
|
|
agent,
|
|
"Follow up",
|
|
client=client,
|
|
state_store=store,
|
|
thread_id="thread_1",
|
|
)
|
|
|
|
assert client.chat.completions.create.call_args.kwargs["messages"] == [
|
|
{"role": "user", "content": "Hello"},
|
|
{"role": "assistant", "content": "first"},
|
|
{"role": "user", "content": "Follow up"},
|
|
]
|
|
stored = store.load_state("thread_1")
|
|
assert stored.revision == 2
|
|
assert stored.state.messages == result.messages
|
|
assert stored.state.messages[-1] == {"role": "assistant", "content": "second"}
|
|
|
|
|
|
def test_continue_sync_with_missing_persisted_state_raises():
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
|
|
with pytest.raises(ai.StateNotFoundError):
|
|
Runner.continue_sync(
|
|
agent,
|
|
"Follow up",
|
|
state_store=ai.InMemoryStateStore(),
|
|
thread_id="missing",
|
|
)
|
|
|
|
|
|
def test_continue_sync_result_can_persist_after_in_memory_continuation():
|
|
client = Client()
|
|
client.chat.completions.create = Mock(
|
|
side_effect=[chat_response("first"), chat_response("second")]
|
|
)
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
store = ai.InMemoryStateStore()
|
|
|
|
first = Runner.run_sync(agent, "Hello", client=client)
|
|
second = Runner.continue_sync(
|
|
first,
|
|
"Follow up",
|
|
state_store=store,
|
|
thread_id="thread_1",
|
|
)
|
|
|
|
stored = store.load_state("thread_1")
|
|
assert stored.revision == 1
|
|
assert stored.state.messages == second.messages
|
|
|
|
|
|
def test_persisted_state_requires_store_and_thread_id_together():
|
|
agent = Agent(name="assistant", model="openai:gpt-4o")
|
|
|
|
with pytest.raises(ValueError, match="state_store and thread_id"):
|
|
Runner.run_sync(agent, "Hello", state_store=ai.InMemoryStateStore())
|
|
|
|
with pytest.raises(ValueError, match="Persisted continuation requires"):
|
|
Runner.continue_sync(agent, "Follow up", state_store=ai.InMemoryStateStore())
|
|
|
|
|
|
def test_state_serialization_rejects_non_json_metadata():
|
|
state = RunState(
|
|
agent_name="assistant",
|
|
messages=[],
|
|
metadata={"bad": object()},
|
|
)
|
|
|
|
with pytest.raises(TypeError, match="JSON serializable"):
|
|
state.to_dict()
|