-
Notifications
You must be signed in to change notification settings - Fork 152
Expand file tree
/
Copy pathtest_builtin_model_runner.py
More file actions
138 lines (109 loc) · 5.19 KB
/
Copy pathtest_builtin_model_runner.py
File metadata and controls
138 lines (109 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations
from collections.abc import AsyncIterator, Iterator
from pathlib import Path
from typing import Any
import pytest
from any_llm.constants import LLMProvider
from any_llm.providers.anthropic.base import BaseAnthropicProvider
from any_llm.providers.openai.base import BaseOpenAIProvider
from any_llm.types.completion import ChatCompletionChunk
from bub.builtin.model_runner import ModelRunner
from bub.builtin.settings import AgentSettings, ModelCandidate
from bub.builtin.tape import Tape
from bub.tape import AsyncTapeStoreAdapter, InMemoryTapeStore, TapeContext
class _FakeStreamingOpenAIProvider(BaseOpenAIProvider):
SUPPORTS_COMPLETION_STREAMING = True
def __init__(self) -> None:
self.completion_kwargs: dict[str, Any] | None = None
async def acompletion(self, **kwargs: Any) -> AsyncIterator[ChatCompletionChunk]:
self.completion_kwargs = kwargs
include_usage = kwargs.get("stream_options") == {"include_usage": True}
async def stream() -> AsyncIterator[ChatCompletionChunk]:
yield ChatCompletionChunk.model_validate({
"id": "chatcmpl_test",
"object": "chat.completion.chunk",
"created": 0,
"model": "gpt-test",
"choices": [
{
"index": 0,
"finish_reason": None,
"delta": {"role": "assistant", "content": "done"},
}
],
})
final_chunk: dict[str, Any] = {
"id": "chatcmpl_test",
"object": "chat.completion.chunk",
"created": 0,
"model": "gpt-test",
"choices": [],
}
if include_usage:
final_chunk["usage"] = {"prompt_tokens": 3, "completion_tokens": 2, "total_tokens": 5}
yield ChatCompletionChunk.model_validate(final_chunk)
return stream()
class _FakeStreamingAnthropicProvider(BaseAnthropicProvider):
def __init__(self) -> None:
self.completion_kwargs: dict[str, Any] | None = None
def _init_client(self, api_key: str | None = None, api_base: str | None = None, **kwargs: Any) -> None:
pass
async def acompletion(self, **kwargs: Any) -> AsyncIterator[ChatCompletionChunk]:
self.completion_kwargs = kwargs
async def stream() -> AsyncIterator[ChatCompletionChunk]:
if False:
yield
return stream()
class _FakeOpenAIModelRunner(ModelRunner):
def __init__(self, settings: AgentSettings, llm: _FakeStreamingOpenAIProvider) -> None:
super().__init__(settings)
self._llm = llm
def iter_llm_clients(self, model: str) -> Iterator[tuple[ModelCandidate, _FakeStreamingOpenAIProvider]]:
yield ModelCandidate(provider=LLMProvider.OPENAI, model_id=model, name=f"openai:{model}"), self._llm
class _FakeAnthropicModelRunner(ModelRunner):
def __init__(self, settings: AgentSettings, llm: _FakeStreamingAnthropicProvider) -> None:
super().__init__(settings)
self._llm = llm
def iter_llm_clients(self, model: str) -> Iterator[tuple[ModelCandidate, _FakeStreamingAnthropicProvider]]:
yield ModelCandidate(provider=LLMProvider.ANTHROPIC, model_id=model, name=f"anthropic:{model}"), self._llm
@pytest.mark.asyncio
async def test_streaming_openai_usage_is_requested_and_recorded_in_tape(tmp_path: Path) -> None:
store = InMemoryTapeStore()
tape = Tape(tmp_path, AsyncTapeStoreAdapter(store), TapeContext()).scoped("test-tape")
llm = _FakeStreamingOpenAIProvider()
runner = _FakeOpenAIModelRunner(
AgentSettings.model_construct(model="openai:gpt-test", max_tokens=100, model_timeout_seconds=None),
llm,
)
await tape.ensure_bootstrap_anchor()
events = [
event async for event in runner.run(tape=tape, model="gpt-test", tools=[], system_prompt=None, prompt="hello")
]
assert llm.completion_kwargs is not None
assert llm.completion_kwargs["stream"] is True
assert llm.completion_kwargs["stream_options"] == {"include_usage": True}
assert [(event.kind, event.data) for event in events] == [
("text", {"delta": "done"}),
("final", {"ok": True, "text": "done"}),
]
run_events = [
entry for entry in store.read("test-tape") or [] if entry.kind == "event" and entry.payload.get("name") == "run"
]
assert len(run_events) == 1
assert run_events[0].payload["data"]["usage"] == {
"completion_tokens": 2,
"prompt_tokens": 3,
"total_tokens": 5,
}
@pytest.mark.asyncio
async def test_anthropic_prompt_caching_is_requested() -> None:
llm = _FakeStreamingAnthropicProvider()
runner = _FakeAnthropicModelRunner(
AgentSettings.model_construct(model="anthropic:claude-test", max_tokens=100),
llm,
)
await runner.completion_response(model="claude-test", messages=[{"role": "user", "content": "hello"}], tools=[])
assert llm.completion_kwargs is not None
assert llm.completion_kwargs["stream"] is True
assert llm.completion_kwargs["cache_control"] == {"type": "ephemeral"}
assert "stream_options" not in llm.completion_kwargs