fora-sdk 0.1.7__py3-none-any.whl
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.
- fora_sdk/__init__.py +35 -0
- fora_sdk/_generated_client.py +114 -0
- fora_sdk/_generated_models.py +801 -0
- fora_sdk/_validation.py +166 -0
- fora_sdk/client.py +177 -0
- fora_sdk/codegen.py +332 -0
- fora_sdk/errors.py +32 -0
- fora_sdk/typed/__init__.py +27 -0
- fora_sdk/typed/_base.py +49 -0
- fora_sdk/typed/agent_node.py +96 -0
- fora_sdk/typed/end_call.py +82 -0
- fora_sdk/typed/global_node.py +38 -0
- fora_sdk/typed/qa.py +87 -0
- fora_sdk/typed/start_call.py +142 -0
- fora_sdk/typed/trigger.py +55 -0
- fora_sdk/typed/tuner.py +50 -0
- fora_sdk/typed/webhook.py +78 -0
- fora_sdk/workflow.py +247 -0
- fora_sdk-0.1.7.dist-info/METADATA +103 -0
- fora_sdk-0.1.7.dist-info/RECORD +23 -0
- fora_sdk-0.1.7.dist-info/WHEEL +4 -0
- fora_sdk-0.1.7.dist-info/entry_points.txt +2 -0
- fora_sdk-0.1.7.dist-info/licenses/LICENSE +24 -0
fora_sdk/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Fora SDK — typed builder for voice-AI workflows.
|
|
2
|
+
|
|
3
|
+
Runtime SDK: fetches the spec catalog from the Fora backend at session
|
|
4
|
+
start and validates every `Workflow.add()` call against it. LLMs don't
|
|
5
|
+
need to import per-node-type classes — the `type` argument is a string
|
|
6
|
+
keyed against the fetched spec catalog.
|
|
7
|
+
|
|
8
|
+
from fora_sdk import ForaClient, Workflow
|
|
9
|
+
|
|
10
|
+
with ForaClient(base_url="http://localhost:8000", api_key=...) as client:
|
|
11
|
+
wf = Workflow(client=client, name="loan_qualification")
|
|
12
|
+
start = wf.add(type="startCall", name="greeting", prompt="...")
|
|
13
|
+
qualify = wf.add(type="agentNode", name="qualify", prompt="...")
|
|
14
|
+
wf.edge(start, qualify, label="interested", condition="...")
|
|
15
|
+
client.save_workflow(workflow_id=123, workflow=wf)
|
|
16
|
+
|
|
17
|
+
For typed IDE autocomplete, generate per-node dataclasses via the SDK
|
|
18
|
+
codegen (Phase 6) — the runtime and typed SDKs share this same core.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .client import ForaClient
|
|
22
|
+
from .errors import ApiError, ForaSdkError, SpecMismatchError, ValidationError
|
|
23
|
+
from .typed._base import TypedNode
|
|
24
|
+
from .workflow import NodeRef, Workflow
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"ApiError",
|
|
28
|
+
"ForaClient",
|
|
29
|
+
"ForaSdkError",
|
|
30
|
+
"NodeRef",
|
|
31
|
+
"SpecMismatchError",
|
|
32
|
+
"TypedNode",
|
|
33
|
+
"ValidationError",
|
|
34
|
+
"Workflow",
|
|
35
|
+
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""GENERATED — do not edit. Source: filtered OpenAPI from `api.app`.
|
|
2
|
+
|
|
3
|
+
Regenerate with `./scripts/generate_sdk.sh`.
|
|
4
|
+
|
|
5
|
+
`ForaClient` mixes in this class to get HTTP methods for every route
|
|
6
|
+
decorated with `sdk_expose(...)` on the backend. Request/response types
|
|
7
|
+
come from `_generated_models` (datamodel-codegen output).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from fora_sdk._generated_models import (
|
|
15
|
+
CreateToolRequest,
|
|
16
|
+
CreateWorkflowRequest,
|
|
17
|
+
CredentialResponse,
|
|
18
|
+
DocumentListResponseSchema,
|
|
19
|
+
InitiateCallRequest,
|
|
20
|
+
NodeSpec,
|
|
21
|
+
NodeTypesResponse,
|
|
22
|
+
RecordingListResponseSchema,
|
|
23
|
+
ToolResponse,
|
|
24
|
+
UpdateWorkflowRequest,
|
|
25
|
+
WorkflowListResponse,
|
|
26
|
+
WorkflowResponse,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _GeneratedClient:
|
|
31
|
+
# `ForaClient.__init__` installs `self._request` (see client.py).
|
|
32
|
+
|
|
33
|
+
def create_tool(self, *, body: CreateToolRequest) -> ToolResponse:
|
|
34
|
+
"""Create a reusable tool for the authenticated organization."""
|
|
35
|
+
data = self._request("POST", "/tools/", json=body.model_dump(mode="json", exclude_none=True))
|
|
36
|
+
return ToolResponse.model_validate(data)
|
|
37
|
+
|
|
38
|
+
def create_workflow(self, *, body: CreateWorkflowRequest) -> WorkflowResponse:
|
|
39
|
+
"""Create a new workflow from a workflow definition."""
|
|
40
|
+
data = self._request("POST", "/workflow/create/definition", json=body.model_dump(mode="json", exclude_none=True))
|
|
41
|
+
return WorkflowResponse.model_validate(data)
|
|
42
|
+
|
|
43
|
+
def get_node_type(self, name: str) -> NodeSpec:
|
|
44
|
+
"""Fetch a single node spec by name."""
|
|
45
|
+
data = self._request("GET", f"/node-types/{name}")
|
|
46
|
+
return NodeSpec.model_validate(data)
|
|
47
|
+
|
|
48
|
+
def get_workflow(self, workflow_id: int) -> WorkflowResponse:
|
|
49
|
+
"""Get a single workflow by ID (returns draft if one exists, else published)."""
|
|
50
|
+
data = self._request("GET", f"/workflow/fetch/{workflow_id}")
|
|
51
|
+
return WorkflowResponse.model_validate(data)
|
|
52
|
+
|
|
53
|
+
def list_credentials(self) -> list[CredentialResponse]:
|
|
54
|
+
"""List webhook credentials available to the authenticated organization."""
|
|
55
|
+
data = self._request("GET", "/credentials/")
|
|
56
|
+
return [CredentialResponse.model_validate(x) for x in data]
|
|
57
|
+
|
|
58
|
+
def list_documents(self, *, status: str | None = None, limit: int | None = None, offset: int | None = None) -> DocumentListResponseSchema:
|
|
59
|
+
"""List knowledge base documents available to the authenticated organization."""
|
|
60
|
+
params: dict[str, Any] = {}
|
|
61
|
+
if status is not None:
|
|
62
|
+
params["status"] = status
|
|
63
|
+
if limit is not None:
|
|
64
|
+
params["limit"] = limit
|
|
65
|
+
if offset is not None:
|
|
66
|
+
params["offset"] = offset
|
|
67
|
+
data = self._request("GET", "/knowledge-base/documents", params=params)
|
|
68
|
+
return DocumentListResponseSchema.model_validate(data)
|
|
69
|
+
|
|
70
|
+
def list_node_types(self) -> NodeTypesResponse:
|
|
71
|
+
"""List every registered node type with its spec. Pinned to spec_version."""
|
|
72
|
+
data = self._request("GET", "/node-types")
|
|
73
|
+
return NodeTypesResponse.model_validate(data)
|
|
74
|
+
|
|
75
|
+
def list_recordings(self, *, workflow_id: int | None = None, tts_provider: str | None = None, tts_model: str | None = None, tts_voice_id: str | None = None) -> RecordingListResponseSchema:
|
|
76
|
+
"""List workflow recordings available to the authenticated organization."""
|
|
77
|
+
params: dict[str, Any] = {}
|
|
78
|
+
if workflow_id is not None:
|
|
79
|
+
params["workflow_id"] = workflow_id
|
|
80
|
+
if tts_provider is not None:
|
|
81
|
+
params["tts_provider"] = tts_provider
|
|
82
|
+
if tts_model is not None:
|
|
83
|
+
params["tts_model"] = tts_model
|
|
84
|
+
if tts_voice_id is not None:
|
|
85
|
+
params["tts_voice_id"] = tts_voice_id
|
|
86
|
+
data = self._request("GET", "/workflow-recordings/", params=params)
|
|
87
|
+
return RecordingListResponseSchema.model_validate(data)
|
|
88
|
+
|
|
89
|
+
def list_tools(self, *, status: str | None = None, category: str | None = None) -> list[ToolResponse]:
|
|
90
|
+
"""List tools available to the authenticated organization."""
|
|
91
|
+
params: dict[str, Any] = {}
|
|
92
|
+
if status is not None:
|
|
93
|
+
params["status"] = status
|
|
94
|
+
if category is not None:
|
|
95
|
+
params["category"] = category
|
|
96
|
+
data = self._request("GET", "/tools/", params=params)
|
|
97
|
+
return [ToolResponse.model_validate(x) for x in data]
|
|
98
|
+
|
|
99
|
+
def list_workflows(self, *, status: str | None = None) -> list[WorkflowListResponse]:
|
|
100
|
+
"""List all workflows in the authenticated organization."""
|
|
101
|
+
params: dict[str, Any] = {}
|
|
102
|
+
if status is not None:
|
|
103
|
+
params["status"] = status
|
|
104
|
+
data = self._request("GET", "/workflow/fetch", params=params)
|
|
105
|
+
return [WorkflowListResponse.model_validate(x) for x in data]
|
|
106
|
+
|
|
107
|
+
def test_phone_call(self, *, body: InitiateCallRequest) -> Any:
|
|
108
|
+
"""Place a test call from a workflow to a phone number."""
|
|
109
|
+
return self._request("POST", "/telephony/initiate-call", json=body.model_dump(mode="json", exclude_none=True))
|
|
110
|
+
|
|
111
|
+
def update_workflow(self, workflow_id: int, *, body: UpdateWorkflowRequest) -> WorkflowResponse:
|
|
112
|
+
"""Update a workflow's name and/or definition. Saves as a new draft."""
|
|
113
|
+
data = self._request("PUT", f"/workflow/{workflow_id}", json=body.model_dump(mode="json", exclude_none=True))
|
|
114
|
+
return WorkflowResponse.model_validate(data)
|