-
-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
llm.get_async_model(), llm.AsyncModel base class and OpenAI async models #613
Conversation
Turns out Claude can help resolve merge conflicts: https://gist.github.com/simonw/84104386e788e2797a93581c7985ea32 |
Nasty test failure:
|
Got the existing tests passing again! |
It does not matter that this is a blocking call, since it is a classmethod
I am unhappy with this, had to duplicate some code.
I broke this, found out while trying to write tests for it: >>> import asyncio
>>> import llm
llm.model = llm.get_async_model("gpt-4o-mini")-4o-mini")
>>> model
<AsyncChat 'gpt-4o-mini'>
>>> async for token in model.prompt("describe a good dog in french"):
... print(token, end="", flush=True)
...
Traceback (most recent call last):
File "/Users/simon/.pyenv/versions/3.10.4/lib/python3.10/asyncio/__main__.py", line 58, in runcode
return future.result()
File "/Users/simon/.pyenv/versions/3.10.4/lib/python3.10/concurrent/futures/_base.py", line 446, in result
return self.__get_result()
File "/Users/simon/.pyenv/versions/3.10.4/lib/python3.10/concurrent/futures/_base.py", line 391, in __get_result
raise self._exception
File "<console>", line 1, in <module>
File "/Users/simon/Dropbox/Development/llm/llm/models.py", line 376, in __aiter__
async for chunk in await self.model.execute(
TypeError: object async_generator can't be used in 'await' expression |
Found the fix diff --git a/llm/models.py b/llm/models.py
index 25a016b..1e6c165 100644
--- a/llm/models.py
+++ b/llm/models.py
@@ -373,7 +373,7 @@ class AsyncResponse(_BaseResponse["AsyncModel", Optional["AsyncConversation"]]):
yield chunk
return
- async for chunk in await self.model.execute(
+ async for chunk in self.model.execute(
self.prompt,
stream=self.stream,
response=self, |
One last mypy problem to figure out:
|
Trying this: files-to-prompt llm/models.py -c | llm -m o1-preview 'llm/models.py:390: error: "Coroutine[Any, Any, AsyncIterator[str]]" has no attribute "__aiter__" (not async iterable) [attr-defined]
llm/models.py:390: note: Maybe you forgot to use "await"?' Wow that cost 20 cents! https://gist.github.com/simonw/8447d433e5924bfa11999f445af9010f |
The fix to all of my horrible class AsyncModel(_BaseModel["AsyncResponse", "AsyncConversation"]):
def conversation(self) -> "AsyncConversation":
return AsyncConversation(model=self)
@abstractmethod
async def execute(
self,
prompt: Prompt,
stream: bool,
response: "AsyncResponse",
conversation: Optional["AsyncConversation"],
) -> AsyncGenerator[str, None]:
"""
Returns an async generator that executes the prompt and yields chunks of text,
or yields a single big chunk.
"""
yield "" Note the
|
I might make |
llm/models.py
Outdated
ModelT = TypeVar("ModelT", bound=Union["Model", "AsyncModel"]) | ||
ConversationT = TypeVar( | ||
"ConversationT", bound=Optional[Union["Conversation", "AsyncConversation"]] | ||
) | ||
ResponseT = TypeVar("ResponseT") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not happy about this at all, it's way too hard to understand.
llm/models.py
Outdated
|
||
class Response(_BaseResponse["Model", Optional["Conversation"]]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this - I'm going to try refactoring to not use generics like this.
class Options(SharedOptions): | ||
json_object: Optional[bool] = Field( | ||
description="Output a valid JSON object {...}. Prompt must mention JSON.", | ||
default=None, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I refactor this to avoid duplication?
... and after all of this, I'm thinking maybe a single model class with an |
I got this working: >>> await llm.get_async_model("gpt-4o-mini").prompt("hi")
'Hello! How can I assist you today?' Using this: diff --git a/llm/models.py b/llm/models.py
index 3ed61bf..8539d08 100644
--- a/llm/models.py
+++ b/llm/models.py
@@ -416,6 +416,9 @@ class AsyncResponse(_BaseResponse):
await self._force()
return self._start_utcnow.isoformat() if self._start_utcnow else ""
+ def __await__(self):
+ return self.text().__await__()
+
@classmethod
def fake(
cls,
|
I'm going to add a |
This is an API design mistake. It means you can't get the I think awaiting this should return a fully resolved |
This is better: >>> import asyncio
>>> import llm
>>> m = llm.get_async_model("gpt-4o-mini")
>>> response = await m.prompt("say hi in spanish")
>>> response
<Response prompt='say hi in spanish' text='¡Hola!'>
>>> await response.text()
'¡Hola!' |
Refs #25 Refs simonw/llm#507 Refs simonw/llm#613
* Tip about pytest --record-mode once Plus mechanism for setting API key during tests with PYTEST_ANTHROPIC_API_KEY * Async support for Claude models Closes #25 Refs simonw/llm#507 Refs simonw/llm#613 * Depend on llm>=0.18a0, refs #25
Refs #25 Refs simonw/llm#507 Refs simonw/llm#613
Refs #25, #20, #24 Refs simonw/llm#507 Refs simonw/llm#613
Refs:
Still to figure out:
register_async_models()
hook.Model
, needs to move out and an async version needs to be considered.Punted on these:
llm-gguf
plugin to work with this? Might help confirm the API design further by showing it working with a blocking plugin (maybe via a run-in-thread hack). SmolLM2 might be good for this.