Metadata-Version: 2.4
Name: langchain-flowspeech
Version: 0.2.1
Summary: LangChain tools for FlowSpeech text-to-speech generation.
Project-URL: Homepage, https://flowspeech.io/
Project-URL: Documentation, https://flowspeech.io/docs/public-api
Project-URL: Source, https://github.com/waeckerlinfederowicz66-sketch/langchain-flowspeech
Project-URL: Issues, https://github.com/waeckerlinfederowicz66-sketch/langchain-flowspeech/issues
Author-email: FlowSpeech <support@flowspeech.io>
License-Expression: MIT
License-File: LICENSE
Keywords: ai-voice,flowspeech,langchain,text-to-speech,tts
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: langchain-core<2.0,>=0.3
Requires-Dist: pydantic<3,>=2
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Requires-Dist: respx>=0.21; extra == 'test'
Description-Content-Type: text/markdown

# langchain-flowspeech

LangChain tools for [FlowSpeech](https://flowspeech.io/) public API v1.

```bash
pip install langchain-flowspeech==0.2.1
```

Set FLOWSPEECH_API_KEY in your server environment, then:

```python
from langchain_flowspeech import FlowSpeechTextToSpeechTool

tool = FlowSpeechTextToSpeechTool(timeout=300)
try:
    result = tool.invoke({
        "text": "Welcome.[pause:2s]Let's begin.",
        "voice_name": "Kore",
        "output_path": "speech.wav",
    })
    print(result["job_id"], result["request_id"], result["usage"])
finally:
    tool.close()
```

Async callers can use await tool.ainvoke(...) with the same arguments and result.
FlowSpeechClient also provides agenerate_speech, acheck_quota and alist_voices.
These offload synchronous I/O to a worker thread; cancelling the await does not
cancel the server job or immediately stop the worker. The configured deadline
still bounds its work. Explicit cancel_job is available.

## Multi-speaker input

```python
from langchain_flowspeech import FlowSpeechClient

with FlowSpeechClient() as client:
    result = client.generate_speech(segments=[
        {"speaker": "Alice", "text": "Welcome.", "voiceName": "Kore"},
        {"speaker": "Bob", "text": "Thank you.", "voiceName": "Puck"},
    ])
    result.save("conversation.wav")
    print(client.check_quota().available)
    print(client.list_voices())
```

Legacy speakers assignments remain supported when every nonblank text line has
the form Label: utterance. Unknown/duplicate labels or unlabelled lines are rejected;
use segments for arbitrary multiline scripts. One unlabelled voice assignment maps
to single mode. original_text is accepted only when identical to text.

FlowSpeechTtsResult.audio_base64 remains available for compatibility but is
encoded locally from downloaded WAV bytes. The API itself does not return base64.
The legacy result quota field is None; use usage for this job or check_quota()
for a current account balance. Legacy limit/used/reset_at/is_guest fields are None.

Pass request_id to reuse the UUID only with unchanged input. After a timeout use
client.find_job(request_id), then client.get_job(job_id); for download failures,
client.download_audio(result_url) downloads again without generation.

## API v1 behavior

An active paid Basic, Pro or Scale subscription and a FlowSpeech API key are
required; trials do not include API generation. Keep FLOWSPEECH_API_KEY server-side.
See [public API documentation](https://flowspeech.io/docs/public-api).

Generation submits POST /api/v1/tts/jobs with {"input": ...} and one UUID
Idempotency-Key, waits through queued/running/merging/settling, then downloads
result.url after success. A 200 response alone does not mean generation succeeded.
Single input uses mode/text/voiceName. Multi input uses mode/segments, where each
turn contains speaker, text and voiceName. Model, source, speed and format options
are unsupported and must not be sent.

The default total deadline is 300 seconds, polling is at least five seconds, and
longer Retry-After values (seconds or HTTP dates) are honored. Transient transport
failures, 429, and explicitly retryable 5xx errors get at most two retries.
Create retries preserve the UUID and exact body. A deadline stops local waiting,
not the server job; use the saved IDs for recovery or explicit cancellation.

A deadline ends local waiting, not the remote job. A transport already in progress
may finish later; its worker performs no follow-up request. Each client permits
one active HTTP request, and this package permits at most eight HTTP workers per
process. An occupied client raises `request_in_progress`; exhausted capacity
raises `client_busy`, without queueing or sending another request. Do not use one
client concurrently, and recover the original request ID after a timeout instead
of submitting a new recording. Caller-owned HTTP clients are never closed.
Closing this client rejects future work; cleanup of owned HTTP clients is
deferred until any active request exits, rather than blocking local shutdown.

If reading a response body fails, known status codes and Retry-After are retained.
Authentication failures are not retried. Interrupted successful or server-error
responses use bounded transport retries with the original UUID and body.
Errors use a local HTTP-status message rather than echoing upstream text. Error
codes and trace IDs are bounded tokens, filtered for credentials and URLs;
request/job IDs remain available for recovery.

Errors expose status_code, code, retryable, trace_id, request_id, job_id and
result_url where available. Failed and cancelled jobs raise errors. Deleted
success results are reported as result_deleted, not as empty audio.

Audio is a separately downloaded, validated WAV (24000 Hz, mono, 16 bit).
Validation checks the entire RIFF size, chunk bounds/padding, exactly one PCM
format/data pair, byte rate 48000, block alignment 2 and nonempty complete samples.
Malformed audio raises invalid_audio with request/job IDs and the existing result
URL preserved; it does not trigger another recording.
Downloads use HTTPS without API Authorization, cookies, or default client
credentials, including across redirects. API redirects are rejected. Downloads
are bounded to 64 MiB by default. A download retry never submits another recording.
Anyone holding a result URL can access the recording; treat links as sensitive.

GET /api/v1/quota returns quota.remaining/reserved/available plus subscription
and limits. GET /api/v1/voices
returns voices. Recovery uses GET /api/v1/tts/jobs?requestId=UUID; status and cancel
use /api/v1/tts/jobs/{UUID} and /api/v1/tts/jobs/{UUID}/cancel.

## Development

```bash
python -m pip install -e ".[test]" build
python -m pytest -q
python -m build
```

Tests use fake HTTP transports and synthetic WAVs. No production generation.
See RELEASE.md for coordinator-only release instructions.
