q-bot 1.4.0__tar.gz → 2.0.0.dev2__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.
- q_bot-2.0.0.dev2/PKG-INFO +188 -0
- q_bot-2.0.0.dev2/README.md +169 -0
- q_bot-2.0.0.dev2/pyproject.toml +37 -0
- q_bot-2.0.0.dev2/q/__init__.py +1 -0
- q_bot-2.0.0.dev2/q/agents.py +63 -0
- q_bot-2.0.0.dev2/q/cli/commands.py +396 -0
- q_bot-2.0.0.dev2/q/cli/main.py +22 -0
- q_bot-2.0.0.dev2/q/cli/models.py +90 -0
- q_bot-2.0.0.dev2/q/cli/parser.py +138 -0
- q_bot-2.0.0.dev2/q/cli/session.py +135 -0
- q_bot-2.0.0.dev2/q/cli/terminal.py +66 -0
- q_bot-2.0.0.dev2/q/client.py +53 -0
- q_bot-2.0.0.dev2/q/message.py +14 -0
- q_bot-2.0.0.dev2/q/providers/__init__.py +21 -0
- q_bot-2.0.0.dev2/q/providers/anthropic.py +66 -0
- q_bot-2.0.0.dev2/q/providers/openai.py +69 -0
- q_bot-2.0.0.dev2/q_bot.egg-info/PKG-INFO +188 -0
- q_bot-2.0.0.dev2/q_bot.egg-info/SOURCES.txt +21 -0
- q_bot-2.0.0.dev2/q_bot.egg-info/entry_points.txt +2 -0
- q_bot-2.0.0.dev2/q_bot.egg-info/requires.txt +9 -0
- q_bot-1.4.0/PKG-INFO +0 -237
- q_bot-1.4.0/README.md +0 -223
- q_bot-1.4.0/pyproject.toml +0 -31
- q_bot-1.4.0/q.py +0 -403
- q_bot-1.4.0/q_bot.egg-info/PKG-INFO +0 -237
- q_bot-1.4.0/q_bot.egg-info/SOURCES.txt +0 -9
- q_bot-1.4.0/q_bot.egg-info/entry_points.txt +0 -2
- q_bot-1.4.0/q_bot.egg-info/requires.txt +0 -4
- {q_bot-1.4.0 → q_bot-2.0.0.dev2}/q_bot.egg-info/dependency_links.txt +0 -0
- {q_bot-1.4.0 → q_bot-2.0.0.dev2}/q_bot.egg-info/top_level.txt +0 -0
- {q_bot-1.4.0 → q_bot-2.0.0.dev2}/setup.cfg +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: q-bot
|
|
3
|
+
Version: 2.0.0.dev2
|
|
4
|
+
Summary: An LLM agent from the comfort of your command line
|
|
5
|
+
Author-email: Tushar Khan <dev@tusharkhan.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/tk755/q
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: anthropic==0.75.0
|
|
11
|
+
Requires-Dist: colorama==0.4.6
|
|
12
|
+
Requires-Dist: distro==1.9.0
|
|
13
|
+
Requires-Dist: openai==2.9.0
|
|
14
|
+
Requires-Dist: psutil==7.2.1
|
|
15
|
+
Requires-Dist: pydantic==2.12.5
|
|
16
|
+
Requires-Dist: pyperclip==1.11.0
|
|
17
|
+
Requires-Dist: python-dotenv==1.2.1
|
|
18
|
+
Requires-Dist: termcolor==3.2.0
|
|
19
|
+
|
|
20
|
+
# Overview
|
|
21
|
+
|
|
22
|
+
`q` is a provider-agnostic command-line agent and LLM framework.
|
|
23
|
+
|
|
24
|
+
> I originally built this as a personal CLI tool before Claude Code existed. I still find it more useful for quick shell interactions and running multi-model experiments.
|
|
25
|
+
|
|
26
|
+
# Installation
|
|
27
|
+
|
|
28
|
+
Install using any pip-compatible package manager (e.g. `pip`, `pipx`, `uv`, etc.):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pipx install q-bot
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requires Python 3.12+.
|
|
35
|
+
|
|
36
|
+
# CLI Usage
|
|
37
|
+
|
|
38
|
+
`q` uses a simple paradigm where each character from a-z is mapped to a single flag representing a command or option. This enables concise combinations of flags to achieve complex behavior.
|
|
39
|
+
|
|
40
|
+
## Flag Reference
|
|
41
|
+
|
|
42
|
+
| Flag | Name | Arg | Description | Type |
|
|
43
|
+
| ---- | ------------ | ------- | ------------------------------ | ------: |
|
|
44
|
+
| `-a` | agent | | *[reserved for future use]* | Command |
|
|
45
|
+
| `-b` | batch | | *[reserved for future use]* | |
|
|
46
|
+
| `-c` | code | str | generate code | Command |
|
|
47
|
+
| `-d` | directory | - / str | add a directory to context | Option |
|
|
48
|
+
| `-e` | explain | - / str | explain code or text | Command |
|
|
49
|
+
| `-f` | file | str | read input from file | Option |
|
|
50
|
+
| `-g` | | | | |
|
|
51
|
+
| `-h` | help | - / str | help message / help agent | Command |
|
|
52
|
+
| `-i` | image | str | generate/edit an image | Command |
|
|
53
|
+
| `-j` | json | - | output as JSON | Option |
|
|
54
|
+
| `-k` | api key | str | override API key | Option |
|
|
55
|
+
| `-l` | | | | |
|
|
56
|
+
| `-m` | model | str | override model/provider | Option |
|
|
57
|
+
| `-n` | new session | - | clear the session history | Option |
|
|
58
|
+
| `-o` | output | str | output file | Option |
|
|
59
|
+
| `-p` | | | | |
|
|
60
|
+
| `-q` | | | | |
|
|
61
|
+
| `-r` | rag | - / str | *[reserved for future use]* | Command |
|
|
62
|
+
| `-s` | shell | - / str | generate a shell command | Command |
|
|
63
|
+
| `-t` | text | str | generate text | Command |
|
|
64
|
+
| `-u` | user command | str | *[reserved for future use]* | Command |
|
|
65
|
+
| `-v` | verbose | - | debug logging | Option |
|
|
66
|
+
| `-w` | web search | str | search the web | Command |
|
|
67
|
+
| `-x` | execute | - | execute a shell command | Option |
|
|
68
|
+
| `-y` | | | | |
|
|
69
|
+
| `-z` | undo | - / int | undo exchanges (default 1) | Option |
|
|
70
|
+
|
|
71
|
+
## Sessions
|
|
72
|
+
|
|
73
|
+
Each terminal or script that runs `q` maintains an isolated **session** that persists conversation history across calls. Use `-n` to clear the session history for a new conversation or one-shot prompt. Sessions are automatically deleted when the parent shell process exits.
|
|
74
|
+
|
|
75
|
+
# Library Usage
|
|
76
|
+
|
|
77
|
+
The `q` library is built on two principles:
|
|
78
|
+
|
|
79
|
+
**Clients are single-capability.** Each client does one thing (e.g. text generation, image generation, web search, etc.) and has a static return type. No mode switching or tool selection logic is necessary.
|
|
80
|
+
|
|
81
|
+
**Agents are provider- and capability-agnostic.** Every agent accepts any client and inherits its return type, regardless of what the underlying client does or which provider it calls.
|
|
82
|
+
|
|
83
|
+
## Clients
|
|
84
|
+
|
|
85
|
+
A **client** wraps a provider's API for one capability.
|
|
86
|
+
|
|
87
|
+
Clients extend `Client[T]` and are instantiated with an API key, model name, and optionally provider- and model-specific argument overrides. All clients expose the same `generate` method which returns a value of type `T`:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
Client[T](api_key: str, model: str, **model_args)
|
|
91
|
+
Client[T].generate(messages: list[Message]) -> T
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A number of built-in clients with sensible defaults are provided for the following providers and capabilities:
|
|
95
|
+
|
|
96
|
+
| Client | T | Description | `openai` | `anthropic` |
|
|
97
|
+
| ------------- | ------- | ---------------------------- | :------: | :---------: |
|
|
98
|
+
| `TextClient` | `str` | text generation | ✓ | ✓ |
|
|
99
|
+
| `WebClient` | `str` | web-grounded text generation | ✓ | ✗ |
|
|
100
|
+
| `ImageClient` | `bytes` | image generation | ✓ | ✗ |
|
|
101
|
+
|
|
102
|
+
### Dynamic Loading
|
|
103
|
+
|
|
104
|
+
Client classes are typically imported from their provider module:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from q.providers.openai import ImageClient
|
|
108
|
+
|
|
109
|
+
client = ImageClient(api_key, model, **model_args)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
They can also be dynamically loaded at runtime by specifying a provider and capability using the `load_client_class` utility:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from q.providers import load_client_class
|
|
116
|
+
|
|
117
|
+
client_class = load_client_class('openai', 'ImageClient')
|
|
118
|
+
client = client_class(api_key, model, **model_args)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Agents
|
|
122
|
+
|
|
123
|
+
An **agent** manages conversation state and delegates generation to a client.
|
|
124
|
+
|
|
125
|
+
`ChatAgent[T]` maintains a message history and prepends an optional system prompt:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
ChatAgent[T](client: Client[T], system: str | None = None)
|
|
129
|
+
ChatAgent[T].prompt(text: str) -> T
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`BatchAgent[T]` processes multiple inputs concurrently using a shared system prompt, with no conversation history:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
BatchAgent[T](client: Client[T], system: str | None = None)
|
|
136
|
+
BatchAgent[T].batch_prompt(text_list: list[str], n_threads: int = 8) -> list[T]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
<!-- ## Examples
|
|
140
|
+
|
|
141
|
+
This design enables full LLM functionality with minimal code.
|
|
142
|
+
|
|
143
|
+
**Example 1:** Generate an image via OpenAI
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from q.providers.openai import ImageClient
|
|
147
|
+
from q.agents import ChatAgent
|
|
148
|
+
|
|
149
|
+
client = ImageClient(api_key, "gpt-image-2", quality="high")
|
|
150
|
+
agent = ChatAgent(client)
|
|
151
|
+
image_bytes = await agent.prompt("a cat in space")
|
|
152
|
+
Path("cat.png").write_bytes(image_bytes)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Example 2:** Generate batch text via Anthropic
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from q.providers.anthropic import TextClient
|
|
159
|
+
from q.agents import BatchAgent
|
|
160
|
+
|
|
161
|
+
client = TextClient(api_key, "claude-opus-4-8")
|
|
162
|
+
agent = BatchAgent(client, system="Identify the language of the text.")
|
|
163
|
+
inputs = ["How are you?", "¿Cómo estás?", "Comment ça va?"]
|
|
164
|
+
langs = await agent.batch_prompt(inputs)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Example 3:** Multi-model orchestration
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from q.providers import load_client_class
|
|
171
|
+
from q.agents import ChatAgent
|
|
172
|
+
|
|
173
|
+
client1 = load_client_class('openai', 'TextClient')(oai_key, "gpt-5-5")
|
|
174
|
+
client2 = load_client_class('anthropic', 'TextClient')(anthropic_key, "claude-opus-4-8")
|
|
175
|
+
|
|
176
|
+
system = "You are an AI speaking with another AI. Engage in a discussion about the future of AI."
|
|
177
|
+
|
|
178
|
+
agent1 = ChatAgent(client1, system=system)
|
|
179
|
+
agent2 = ChatAgent(client2, system=system)
|
|
180
|
+
|
|
181
|
+
text = "What are your thoughts on the future of AI?"
|
|
182
|
+
|
|
183
|
+
for _ in range(5):
|
|
184
|
+
text = await agent1.prompt(text)
|
|
185
|
+
print("agent1:", text)
|
|
186
|
+
text = await agent2.prompt(text)
|
|
187
|
+
print("agent2:", text)
|
|
188
|
+
``` -->
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
`q` is a provider-agnostic command-line agent and LLM framework.
|
|
4
|
+
|
|
5
|
+
> I originally built this as a personal CLI tool before Claude Code existed. I still find it more useful for quick shell interactions and running multi-model experiments.
|
|
6
|
+
|
|
7
|
+
# Installation
|
|
8
|
+
|
|
9
|
+
Install using any pip-compatible package manager (e.g. `pip`, `pipx`, `uv`, etc.):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pipx install q-bot
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.12+.
|
|
16
|
+
|
|
17
|
+
# CLI Usage
|
|
18
|
+
|
|
19
|
+
`q` uses a simple paradigm where each character from a-z is mapped to a single flag representing a command or option. This enables concise combinations of flags to achieve complex behavior.
|
|
20
|
+
|
|
21
|
+
## Flag Reference
|
|
22
|
+
|
|
23
|
+
| Flag | Name | Arg | Description | Type |
|
|
24
|
+
| ---- | ------------ | ------- | ------------------------------ | ------: |
|
|
25
|
+
| `-a` | agent | | *[reserved for future use]* | Command |
|
|
26
|
+
| `-b` | batch | | *[reserved for future use]* | |
|
|
27
|
+
| `-c` | code | str | generate code | Command |
|
|
28
|
+
| `-d` | directory | - / str | add a directory to context | Option |
|
|
29
|
+
| `-e` | explain | - / str | explain code or text | Command |
|
|
30
|
+
| `-f` | file | str | read input from file | Option |
|
|
31
|
+
| `-g` | | | | |
|
|
32
|
+
| `-h` | help | - / str | help message / help agent | Command |
|
|
33
|
+
| `-i` | image | str | generate/edit an image | Command |
|
|
34
|
+
| `-j` | json | - | output as JSON | Option |
|
|
35
|
+
| `-k` | api key | str | override API key | Option |
|
|
36
|
+
| `-l` | | | | |
|
|
37
|
+
| `-m` | model | str | override model/provider | Option |
|
|
38
|
+
| `-n` | new session | - | clear the session history | Option |
|
|
39
|
+
| `-o` | output | str | output file | Option |
|
|
40
|
+
| `-p` | | | | |
|
|
41
|
+
| `-q` | | | | |
|
|
42
|
+
| `-r` | rag | - / str | *[reserved for future use]* | Command |
|
|
43
|
+
| `-s` | shell | - / str | generate a shell command | Command |
|
|
44
|
+
| `-t` | text | str | generate text | Command |
|
|
45
|
+
| `-u` | user command | str | *[reserved for future use]* | Command |
|
|
46
|
+
| `-v` | verbose | - | debug logging | Option |
|
|
47
|
+
| `-w` | web search | str | search the web | Command |
|
|
48
|
+
| `-x` | execute | - | execute a shell command | Option |
|
|
49
|
+
| `-y` | | | | |
|
|
50
|
+
| `-z` | undo | - / int | undo exchanges (default 1) | Option |
|
|
51
|
+
|
|
52
|
+
## Sessions
|
|
53
|
+
|
|
54
|
+
Each terminal or script that runs `q` maintains an isolated **session** that persists conversation history across calls. Use `-n` to clear the session history for a new conversation or one-shot prompt. Sessions are automatically deleted when the parent shell process exits.
|
|
55
|
+
|
|
56
|
+
# Library Usage
|
|
57
|
+
|
|
58
|
+
The `q` library is built on two principles:
|
|
59
|
+
|
|
60
|
+
**Clients are single-capability.** Each client does one thing (e.g. text generation, image generation, web search, etc.) and has a static return type. No mode switching or tool selection logic is necessary.
|
|
61
|
+
|
|
62
|
+
**Agents are provider- and capability-agnostic.** Every agent accepts any client and inherits its return type, regardless of what the underlying client does or which provider it calls.
|
|
63
|
+
|
|
64
|
+
## Clients
|
|
65
|
+
|
|
66
|
+
A **client** wraps a provider's API for one capability.
|
|
67
|
+
|
|
68
|
+
Clients extend `Client[T]` and are instantiated with an API key, model name, and optionally provider- and model-specific argument overrides. All clients expose the same `generate` method which returns a value of type `T`:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
Client[T](api_key: str, model: str, **model_args)
|
|
72
|
+
Client[T].generate(messages: list[Message]) -> T
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A number of built-in clients with sensible defaults are provided for the following providers and capabilities:
|
|
76
|
+
|
|
77
|
+
| Client | T | Description | `openai` | `anthropic` |
|
|
78
|
+
| ------------- | ------- | ---------------------------- | :------: | :---------: |
|
|
79
|
+
| `TextClient` | `str` | text generation | ✓ | ✓ |
|
|
80
|
+
| `WebClient` | `str` | web-grounded text generation | ✓ | ✗ |
|
|
81
|
+
| `ImageClient` | `bytes` | image generation | ✓ | ✗ |
|
|
82
|
+
|
|
83
|
+
### Dynamic Loading
|
|
84
|
+
|
|
85
|
+
Client classes are typically imported from their provider module:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from q.providers.openai import ImageClient
|
|
89
|
+
|
|
90
|
+
client = ImageClient(api_key, model, **model_args)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
They can also be dynamically loaded at runtime by specifying a provider and capability using the `load_client_class` utility:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from q.providers import load_client_class
|
|
97
|
+
|
|
98
|
+
client_class = load_client_class('openai', 'ImageClient')
|
|
99
|
+
client = client_class(api_key, model, **model_args)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Agents
|
|
103
|
+
|
|
104
|
+
An **agent** manages conversation state and delegates generation to a client.
|
|
105
|
+
|
|
106
|
+
`ChatAgent[T]` maintains a message history and prepends an optional system prompt:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
ChatAgent[T](client: Client[T], system: str | None = None)
|
|
110
|
+
ChatAgent[T].prompt(text: str) -> T
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`BatchAgent[T]` processes multiple inputs concurrently using a shared system prompt, with no conversation history:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
BatchAgent[T](client: Client[T], system: str | None = None)
|
|
117
|
+
BatchAgent[T].batch_prompt(text_list: list[str], n_threads: int = 8) -> list[T]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
<!-- ## Examples
|
|
121
|
+
|
|
122
|
+
This design enables full LLM functionality with minimal code.
|
|
123
|
+
|
|
124
|
+
**Example 1:** Generate an image via OpenAI
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from q.providers.openai import ImageClient
|
|
128
|
+
from q.agents import ChatAgent
|
|
129
|
+
|
|
130
|
+
client = ImageClient(api_key, "gpt-image-2", quality="high")
|
|
131
|
+
agent = ChatAgent(client)
|
|
132
|
+
image_bytes = await agent.prompt("a cat in space")
|
|
133
|
+
Path("cat.png").write_bytes(image_bytes)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Example 2:** Generate batch text via Anthropic
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from q.providers.anthropic import TextClient
|
|
140
|
+
from q.agents import BatchAgent
|
|
141
|
+
|
|
142
|
+
client = TextClient(api_key, "claude-opus-4-8")
|
|
143
|
+
agent = BatchAgent(client, system="Identify the language of the text.")
|
|
144
|
+
inputs = ["How are you?", "¿Cómo estás?", "Comment ça va?"]
|
|
145
|
+
langs = await agent.batch_prompt(inputs)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Example 3:** Multi-model orchestration
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from q.providers import load_client_class
|
|
152
|
+
from q.agents import ChatAgent
|
|
153
|
+
|
|
154
|
+
client1 = load_client_class('openai', 'TextClient')(oai_key, "gpt-5-5")
|
|
155
|
+
client2 = load_client_class('anthropic', 'TextClient')(anthropic_key, "claude-opus-4-8")
|
|
156
|
+
|
|
157
|
+
system = "You are an AI speaking with another AI. Engage in a discussion about the future of AI."
|
|
158
|
+
|
|
159
|
+
agent1 = ChatAgent(client1, system=system)
|
|
160
|
+
agent2 = ChatAgent(client2, system=system)
|
|
161
|
+
|
|
162
|
+
text = "What are your thoughts on the future of AI?"
|
|
163
|
+
|
|
164
|
+
for _ in range(5):
|
|
165
|
+
text = await agent1.prompt(text)
|
|
166
|
+
print("agent1:", text)
|
|
167
|
+
text = await agent2.prompt(text)
|
|
168
|
+
print("agent2:", text)
|
|
169
|
+
``` -->
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "q-bot"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "An LLM agent from the comfort of your command line"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"anthropic==0.75.0",
|
|
12
|
+
"colorama==0.4.6",
|
|
13
|
+
"distro==1.9.0",
|
|
14
|
+
"openai==2.9.0",
|
|
15
|
+
"psutil==7.2.1",
|
|
16
|
+
"pydantic==2.12.5",
|
|
17
|
+
"pyperclip==1.11.0",
|
|
18
|
+
"python-dotenv==1.2.1",
|
|
19
|
+
"termcolor==3.2.0",
|
|
20
|
+
]
|
|
21
|
+
authors = [
|
|
22
|
+
{name = "Tushar Khan", email = "dev@tusharkhan.com"}
|
|
23
|
+
]
|
|
24
|
+
readme = "README.md"
|
|
25
|
+
license = "MIT"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
q = "q.cli.main:main"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Repository = "https://github.com/tk755/q"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
include = ["q*"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.dynamic]
|
|
37
|
+
version = {attr = "q.__version__"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.0.dev2"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from .client import Client
|
|
4
|
+
from .message import Message, Role
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatAgent[T]:
|
|
8
|
+
"""Conversational agent with persistent message history."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, client: Client[T], system: str | None = None, messages: list[Message] | None = None):
|
|
11
|
+
self.client = client
|
|
12
|
+
self.system = system
|
|
13
|
+
self.messages: list[Message] = messages.copy() if messages else []
|
|
14
|
+
|
|
15
|
+
async def prompt(self, text: str) -> T:
|
|
16
|
+
"""Generate response and update conversation history."""
|
|
17
|
+
self.messages.append(Message(role=Role.USER, content=text))
|
|
18
|
+
|
|
19
|
+
messages = self.messages
|
|
20
|
+
if self.system:
|
|
21
|
+
messages = [Message(role=Role.SYSTEM, content=self.system), *self.messages]
|
|
22
|
+
response = await self.client.generate(messages)
|
|
23
|
+
|
|
24
|
+
if isinstance(response, str):
|
|
25
|
+
self.messages.append(Message(role=Role.ASSISTANT, content=response))
|
|
26
|
+
|
|
27
|
+
return response
|
|
28
|
+
|
|
29
|
+
def drop_exchanges(self, n: int = 1) -> None:
|
|
30
|
+
"""Drop the last N conversation exchanges (user message + responses)."""
|
|
31
|
+
if n <= 0:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
user_messages_found = 0
|
|
35
|
+
for i in range(len(self.messages) - 1, -1, -1):
|
|
36
|
+
if self.messages[i].role == Role.USER:
|
|
37
|
+
user_messages_found += 1
|
|
38
|
+
if user_messages_found == n:
|
|
39
|
+
self.messages = self.messages[:i]
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BatchAgent[T]:
|
|
44
|
+
"""Batch agent for applying a single prompt to multiple inputs concurrently."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, client: Client[T], system: str | None = None):
|
|
47
|
+
self.client = client
|
|
48
|
+
self.system = system
|
|
49
|
+
|
|
50
|
+
async def batch_prompt(self, text_list: list[str], n_threads: int = 8) -> list[T]:
|
|
51
|
+
"""Process multiple inputs concurrently and return the outputs in order."""
|
|
52
|
+
semaphore = asyncio.Semaphore(n_threads)
|
|
53
|
+
|
|
54
|
+
async def process(text: str) -> T:
|
|
55
|
+
async with semaphore:
|
|
56
|
+
messages: list[Message] = []
|
|
57
|
+
if self.system:
|
|
58
|
+
messages.append(Message(role=Role.SYSTEM, content=self.system))
|
|
59
|
+
messages.append(Message(role=Role.USER, content=text))
|
|
60
|
+
return await self.client.generate(messages)
|
|
61
|
+
|
|
62
|
+
tasks = [process(text) for text in text_list]
|
|
63
|
+
return await asyncio.gather(*tasks)
|