talqing 0.1.0__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.
@@ -0,0 +1,43 @@
1
+ # secrets / env
2
+ .env
3
+ backend/.env
4
+ # Per-env app configs (secrets; not committed). example.config.yaml is the
5
+ # committed template and must stay free of real credentials.
6
+ backend/configs/*.config.yaml
7
+ !backend/configs/example.config.yaml
8
+ *.local
9
+ client_secret_*.json
10
+
11
+ # python
12
+ __pycache__/
13
+ *.py[cod]
14
+ .venv/
15
+ venv/
16
+ *.egg-info/
17
+ .pytest_cache/
18
+ .mypy_cache/
19
+ .ruff_cache/
20
+
21
+ # node / next
22
+ node_modules/
23
+ .next/
24
+ out/
25
+ clients/typescript/dist/
26
+ mcp/dist/
27
+ npm-debug.log*
28
+ .pnpm-debug.log*
29
+ *.tsbuildinfo
30
+
31
+ # data / volumes
32
+ pgdata*/
33
+ *.log
34
+ .tmp-*.json
35
+
36
+ # os / editor
37
+ .DS_Store
38
+ .idea/
39
+ .vscode/
40
+
41
+ # Monitoring inputs for create-secrets.sh — droplet IPs, the LiveKit metrics
42
+ # password and the Grafana OAuth client. Same reason as the config files.
43
+ deployment/prod/scripts/monitoring.env
talqing-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Talqing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
talqing-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.5
2
+ Name: talqing
3
+ Version: 0.1.0
4
+ Summary: Python client for the Talqing API
5
+ Project-URL: Homepage, https://talqing.com
6
+ Author: talqing
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: agents,ai,talqing,voice
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: httpx<1,>=0.27
20
+ Requires-Dist: typing-extensions>=4.12
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
23
+ Requires-Dist: pytest>=8; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Talqing Python SDK
27
+
28
+ The Python client for the Talqing `/v1` API, generated from `openapi/openapi.json`.
29
+ It is the same set of operations, under the same names, as the TypeScript SDK —
30
+ both are generated from the surface `backend/api/sdk_surface.py` declares.
31
+
32
+ `httpx` is the only runtime dependency. Responses come back as decoded JSON,
33
+ described by TypedDicts: nothing is validated, coerced or renamed on arrival.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install talqing
39
+ ```
40
+
41
+ Python 3.10 or newer. The package ships a `py.typed` marker, so a type checker
42
+ in your project sees every TypedDict it declares.
43
+
44
+ To work against a checkout instead: `uv pip install -e clients/python`.
45
+
46
+ ## Authentication
47
+
48
+ Create a personal access token on the dashboard's Tokens page. The client sends
49
+ it as `Authorization: Bearer <token>`.
50
+
51
+ ```python
52
+ from talqing import Talqing
53
+
54
+ with Talqing(token="tq_...", base_url="https://api.in.talqing.com") as talqing:
55
+ print(talqing.auth.me())
56
+ ```
57
+
58
+ `Talqing.from_env()` reads the same two values from `TALQING_API_KEY` and
59
+ `TALQING_BASE_URL`:
60
+
61
+ ```bash
62
+ export TALQING_API_KEY="your-personal-access-token"
63
+ export TALQING_BASE_URL="http://localhost:8000"
64
+ ```
65
+
66
+ ```python
67
+ with Talqing.from_env() as talqing:
68
+ print(talqing.auth.me())
69
+ ```
70
+
71
+ `base_url` has no default. A client that quietly points at localhost fails in
72
+ production as a connection error three layers down; one that refuses to start
73
+ says what is actually wrong.
74
+
75
+ ## The shape of it
76
+
77
+ Operations are reached by resource, exactly as the API names them:
78
+
79
+ ```python
80
+ talqing.agents.list(limit=50)
81
+ talqing.agents.get(agent_id)
82
+ talqing.agents.versions.rollback(agent_id, 3)
83
+ talqing.calls.batches.pause(batch_id)
84
+ talqing.telephony.phone_numbers.assign(number_id, agent_id=agent_id)
85
+ ```
86
+
87
+ Path parameters are positional; everything else — request body fields and query
88
+ parameters alike — is a keyword argument:
89
+
90
+ ```python
91
+ agent = talqing.agents.create(
92
+ config={"name": "Support bot", "channel": "text", "prompt": "Be concise."}
93
+ )
94
+ talqing.agents.update(agent["id"], config={**agent["config"], "greeting": None})
95
+ talqing.agents.publish(agent["id"])
96
+ ```
97
+
98
+ **An argument you do not pass is not sent.** That is what makes a PATCH able to
99
+ say `null`:
100
+
101
+ ```python
102
+ talqing.orgs.update(retention_days=None) # keep call content forever
103
+ talqing.orgs.update(name="Acme") # leave the retention policy alone
104
+ ```
105
+
106
+ The one name that is not the API's own is `telephony.phone_numbers.import_()` —
107
+ `import` is a Python keyword.
108
+
109
+ ## Errors
110
+
111
+ Every non-2xx raises `TalqingAPIError`. There is one error shape, so there is
112
+ nothing to branch on:
113
+
114
+ ```python
115
+ from talqing import TalqingAPIError
116
+
117
+ try:
118
+ talqing.agents.publish(agent_id)
119
+ except TalqingAPIError as error:
120
+ print(error.status_code, error) # 400 config is invalid
121
+ for problem in error.errors: # ['llm: unknown model gpt-4.9']
122
+ print(problem)
123
+ ```
124
+
125
+ ## Pagination
126
+
127
+ Every list endpoint pages the same way, so one helper covers all of them.
128
+ Anything else the endpoint filters on passes straight through:
129
+
130
+ ```python
131
+ from talqing import paginate
132
+
133
+ for agent in paginate(talqing.agents.list):
134
+ print(agent["config"]["name"])
135
+
136
+ for call in paginate(talqing.calls.list, agent_id=agent_id, type="SIP_INBOUND"):
137
+ print(call["id"], call["cost"])
138
+ ```
139
+
140
+ ## Live streams
141
+
142
+ Six endpoints stay open and push events. Each frame is decoded and repeats its
143
+ own name in `event`, which is what tells the frames apart:
144
+
145
+ ```python
146
+ for event in talqing.conversations.events(conversation_id):
147
+ if event["event"] == "assistant.delta":
148
+ print(event["text"], end="", flush=True)
149
+ ```
150
+
151
+ Use it as a context manager when the loop may exit early, so the connection
152
+ closes with it:
153
+
154
+ ```python
155
+ with talqing.knowledge.events(kb_id) as events:
156
+ for event in events:
157
+ if event["event"] == "status":
158
+ print("build finished:", event["status"])
159
+ break
160
+ ```
161
+
162
+ ## Text conversations
163
+
164
+ Text agents do not use LiveKit rooms. Open the conversation, then send turns —
165
+ both addressed by your own `contact_key`, so the same key always reaches the same
166
+ thread — and watch it happen on the stream above:
167
+
168
+ ```python
169
+ conversation = talqing.conversations.create(contact_key="user-42", agent_id=agent_id)
170
+
171
+ talqing.conversations.messages.create(
172
+ contact_key="user-42", message="Hello", client_message_id=str(uuid4())
173
+ )
174
+ ```
175
+
176
+ A message returns as soon as it is accepted, carrying the user's own item. The
177
+ agent's reply arrives on the event stream, or from
178
+ `talqing.conversations.items.list(conversation["id"])`.
179
+
180
+ Voice and video agents take a LiveKit room token instead:
181
+
182
+ ```python
183
+ token = talqing.calls.token(agent_id=agent_id)
184
+ print(token["server_url"], token["participant_token"])
185
+ ```
186
+
187
+ ## Async
188
+
189
+ `AsyncTalqing` has the same surface with every operation a coroutine. A stream
190
+ is the exception: it is not awaited on either client, so `async for` reads the
191
+ way `for` does.
192
+
193
+ ```python
194
+ import asyncio
195
+ from talqing import AsyncTalqing, paginate_async
196
+
197
+
198
+ async def main() -> None:
199
+ async with AsyncTalqing.from_env() as talqing:
200
+ await talqing.agents.list()
201
+
202
+ async for agent in paginate_async(talqing.agents.list):
203
+ print(agent["id"])
204
+
205
+ async for event in talqing.copilot.agents.stream(agent_id):
206
+ print(event["event"])
207
+
208
+
209
+ asyncio.run(main())
210
+ ```
211
+
212
+ ## Types
213
+
214
+ Every request and response shape is exported from `talqing`, named as the API
215
+ names it:
216
+
217
+ ```python
218
+ from talqing import AgentConfig, AgentResponse, CallOutcome, OperationRequest
219
+ ```
220
+
221
+ A response is a plain `dict` at runtime — the TypedDicts describe it for your
222
+ type checker and your editor, and cost nothing when you run. That also means the
223
+ SDK can never reject a payload the API considers valid.
224
+
225
+ `Page[T]` is the shape every list endpoint returns:
226
+
227
+ ```python
228
+ page = talqing.tools.list(limit=50)
229
+ page["items"], page["has_more"], page["limit"], page["offset"]
230
+ ```
231
+
232
+ ## Escape hatches
233
+
234
+ `talqing.request(...)` calls a path directly with this client's credentials and
235
+ error handling, for an endpoint that shipped since this SDK was generated.
236
+ `talqing.http` is the underlying `httpx.Client`, already carrying the base URL
237
+ and the token, for anything else.
238
+
239
+ `talqing.google_login_url()` and `talqing.oauth_start_url(provider)` build the
240
+ two browser redirects, which are not operations and so cannot be generated.
241
+
242
+ ## Regenerating
243
+
244
+ `src/talqing/gen` is generated and checked in. After re-running
245
+ `openapi/export.py`:
246
+
247
+ ```bash
248
+ python clients/python/generate.py # rewrite it
249
+ python clients/python/generate.py --check # or just assert it is current
250
+ ```
251
+
252
+ Everything else in `src/talqing` is hand-written: the client, the transport, the
253
+ error, and the pagination helper — what the document cannot say.
254
+
255
+ ```bash
256
+ cd clients/python && pytest && mypy
257
+ ```
@@ -0,0 +1,232 @@
1
+ # Talqing Python SDK
2
+
3
+ The Python client for the Talqing `/v1` API, generated from `openapi/openapi.json`.
4
+ It is the same set of operations, under the same names, as the TypeScript SDK —
5
+ both are generated from the surface `backend/api/sdk_surface.py` declares.
6
+
7
+ `httpx` is the only runtime dependency. Responses come back as decoded JSON,
8
+ described by TypedDicts: nothing is validated, coerced or renamed on arrival.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install talqing
14
+ ```
15
+
16
+ Python 3.10 or newer. The package ships a `py.typed` marker, so a type checker
17
+ in your project sees every TypedDict it declares.
18
+
19
+ To work against a checkout instead: `uv pip install -e clients/python`.
20
+
21
+ ## Authentication
22
+
23
+ Create a personal access token on the dashboard's Tokens page. The client sends
24
+ it as `Authorization: Bearer <token>`.
25
+
26
+ ```python
27
+ from talqing import Talqing
28
+
29
+ with Talqing(token="tq_...", base_url="https://api.in.talqing.com") as talqing:
30
+ print(talqing.auth.me())
31
+ ```
32
+
33
+ `Talqing.from_env()` reads the same two values from `TALQING_API_KEY` and
34
+ `TALQING_BASE_URL`:
35
+
36
+ ```bash
37
+ export TALQING_API_KEY="your-personal-access-token"
38
+ export TALQING_BASE_URL="http://localhost:8000"
39
+ ```
40
+
41
+ ```python
42
+ with Talqing.from_env() as talqing:
43
+ print(talqing.auth.me())
44
+ ```
45
+
46
+ `base_url` has no default. A client that quietly points at localhost fails in
47
+ production as a connection error three layers down; one that refuses to start
48
+ says what is actually wrong.
49
+
50
+ ## The shape of it
51
+
52
+ Operations are reached by resource, exactly as the API names them:
53
+
54
+ ```python
55
+ talqing.agents.list(limit=50)
56
+ talqing.agents.get(agent_id)
57
+ talqing.agents.versions.rollback(agent_id, 3)
58
+ talqing.calls.batches.pause(batch_id)
59
+ talqing.telephony.phone_numbers.assign(number_id, agent_id=agent_id)
60
+ ```
61
+
62
+ Path parameters are positional; everything else — request body fields and query
63
+ parameters alike — is a keyword argument:
64
+
65
+ ```python
66
+ agent = talqing.agents.create(
67
+ config={"name": "Support bot", "channel": "text", "prompt": "Be concise."}
68
+ )
69
+ talqing.agents.update(agent["id"], config={**agent["config"], "greeting": None})
70
+ talqing.agents.publish(agent["id"])
71
+ ```
72
+
73
+ **An argument you do not pass is not sent.** That is what makes a PATCH able to
74
+ say `null`:
75
+
76
+ ```python
77
+ talqing.orgs.update(retention_days=None) # keep call content forever
78
+ talqing.orgs.update(name="Acme") # leave the retention policy alone
79
+ ```
80
+
81
+ The one name that is not the API's own is `telephony.phone_numbers.import_()` —
82
+ `import` is a Python keyword.
83
+
84
+ ## Errors
85
+
86
+ Every non-2xx raises `TalqingAPIError`. There is one error shape, so there is
87
+ nothing to branch on:
88
+
89
+ ```python
90
+ from talqing import TalqingAPIError
91
+
92
+ try:
93
+ talqing.agents.publish(agent_id)
94
+ except TalqingAPIError as error:
95
+ print(error.status_code, error) # 400 config is invalid
96
+ for problem in error.errors: # ['llm: unknown model gpt-4.9']
97
+ print(problem)
98
+ ```
99
+
100
+ ## Pagination
101
+
102
+ Every list endpoint pages the same way, so one helper covers all of them.
103
+ Anything else the endpoint filters on passes straight through:
104
+
105
+ ```python
106
+ from talqing import paginate
107
+
108
+ for agent in paginate(talqing.agents.list):
109
+ print(agent["config"]["name"])
110
+
111
+ for call in paginate(talqing.calls.list, agent_id=agent_id, type="SIP_INBOUND"):
112
+ print(call["id"], call["cost"])
113
+ ```
114
+
115
+ ## Live streams
116
+
117
+ Six endpoints stay open and push events. Each frame is decoded and repeats its
118
+ own name in `event`, which is what tells the frames apart:
119
+
120
+ ```python
121
+ for event in talqing.conversations.events(conversation_id):
122
+ if event["event"] == "assistant.delta":
123
+ print(event["text"], end="", flush=True)
124
+ ```
125
+
126
+ Use it as a context manager when the loop may exit early, so the connection
127
+ closes with it:
128
+
129
+ ```python
130
+ with talqing.knowledge.events(kb_id) as events:
131
+ for event in events:
132
+ if event["event"] == "status":
133
+ print("build finished:", event["status"])
134
+ break
135
+ ```
136
+
137
+ ## Text conversations
138
+
139
+ Text agents do not use LiveKit rooms. Open the conversation, then send turns —
140
+ both addressed by your own `contact_key`, so the same key always reaches the same
141
+ thread — and watch it happen on the stream above:
142
+
143
+ ```python
144
+ conversation = talqing.conversations.create(contact_key="user-42", agent_id=agent_id)
145
+
146
+ talqing.conversations.messages.create(
147
+ contact_key="user-42", message="Hello", client_message_id=str(uuid4())
148
+ )
149
+ ```
150
+
151
+ A message returns as soon as it is accepted, carrying the user's own item. The
152
+ agent's reply arrives on the event stream, or from
153
+ `talqing.conversations.items.list(conversation["id"])`.
154
+
155
+ Voice and video agents take a LiveKit room token instead:
156
+
157
+ ```python
158
+ token = talqing.calls.token(agent_id=agent_id)
159
+ print(token["server_url"], token["participant_token"])
160
+ ```
161
+
162
+ ## Async
163
+
164
+ `AsyncTalqing` has the same surface with every operation a coroutine. A stream
165
+ is the exception: it is not awaited on either client, so `async for` reads the
166
+ way `for` does.
167
+
168
+ ```python
169
+ import asyncio
170
+ from talqing import AsyncTalqing, paginate_async
171
+
172
+
173
+ async def main() -> None:
174
+ async with AsyncTalqing.from_env() as talqing:
175
+ await talqing.agents.list()
176
+
177
+ async for agent in paginate_async(talqing.agents.list):
178
+ print(agent["id"])
179
+
180
+ async for event in talqing.copilot.agents.stream(agent_id):
181
+ print(event["event"])
182
+
183
+
184
+ asyncio.run(main())
185
+ ```
186
+
187
+ ## Types
188
+
189
+ Every request and response shape is exported from `talqing`, named as the API
190
+ names it:
191
+
192
+ ```python
193
+ from talqing import AgentConfig, AgentResponse, CallOutcome, OperationRequest
194
+ ```
195
+
196
+ A response is a plain `dict` at runtime — the TypedDicts describe it for your
197
+ type checker and your editor, and cost nothing when you run. That also means the
198
+ SDK can never reject a payload the API considers valid.
199
+
200
+ `Page[T]` is the shape every list endpoint returns:
201
+
202
+ ```python
203
+ page = talqing.tools.list(limit=50)
204
+ page["items"], page["has_more"], page["limit"], page["offset"]
205
+ ```
206
+
207
+ ## Escape hatches
208
+
209
+ `talqing.request(...)` calls a path directly with this client's credentials and
210
+ error handling, for an endpoint that shipped since this SDK was generated.
211
+ `talqing.http` is the underlying `httpx.Client`, already carrying the base URL
212
+ and the token, for anything else.
213
+
214
+ `talqing.google_login_url()` and `talqing.oauth_start_url(provider)` build the
215
+ two browser redirects, which are not operations and so cannot be generated.
216
+
217
+ ## Regenerating
218
+
219
+ `src/talqing/gen` is generated and checked in. After re-running
220
+ `openapi/export.py`:
221
+
222
+ ```bash
223
+ python clients/python/generate.py # rewrite it
224
+ python clients/python/generate.py --check # or just assert it is current
225
+ ```
226
+
227
+ Everything else in `src/talqing` is hand-written: the client, the transport, the
228
+ error, and the pagination helper — what the document cannot say.
229
+
230
+ ```bash
231
+ cd clients/python && pytest && mypy
232
+ ```