jims-api 0.0.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.
- jims_api-0.0.0/PKG-INFO +67 -0
- jims_api-0.0.0/README.md +55 -0
- jims_api-0.0.0/pyproject.toml +38 -0
- jims_api-0.0.0/setup.cfg +4 -0
- jims_api-0.0.0/src/jims_api/__init__.py +0 -0
- jims_api-0.0.0/src/jims_api/main.py +184 -0
- jims_api-0.0.0/src/jims_api/py.typed +0 -0
- jims_api-0.0.0/src/jims_api.egg-info/PKG-INFO +67 -0
- jims_api-0.0.0/src/jims_api.egg-info/SOURCES.txt +11 -0
- jims_api-0.0.0/src/jims_api.egg-info/dependency_links.txt +1 -0
- jims_api-0.0.0/src/jims_api.egg-info/entry_points.txt +2 -0
- jims_api-0.0.0/src/jims_api.egg-info/requires.txt +5 -0
- jims_api-0.0.0/src/jims_api.egg-info/top_level.txt +1 -0
jims_api-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jims-api
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: universal API for JIMS application
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: click>=8.0
|
|
8
|
+
Requires-Dist: fastapi>=0.128.0
|
|
9
|
+
Requires-Dist: jims-core>=0.4.2
|
|
10
|
+
Requires-Dist: loguru>=0.7.3
|
|
11
|
+
Requires-Dist: uvicorn>=0.35.0
|
|
12
|
+
|
|
13
|
+
# jims-api
|
|
14
|
+
|
|
15
|
+
`jims-api` exposes any JIMS application as a FastAPI HTTP service.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Run
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
jims-api --app my_project.app:app --port 8080
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Environment variables:
|
|
25
|
+
|
|
26
|
+
- `JIMS_APP` - JIMS app import path (`module:attr`)
|
|
27
|
+
- `JIMS_PORT` - HTTP port
|
|
28
|
+
- `JIMS_HOST` - HTTP host
|
|
29
|
+
- `JIMS_API_KEY` - optional bearer token for auth
|
|
30
|
+
|
|
31
|
+
## Endpoints
|
|
32
|
+
|
|
33
|
+
- `GET /health`
|
|
34
|
+
- `POST /api/v1/chat`
|
|
35
|
+
|
|
36
|
+
### `POST /api/v1/chat`
|
|
37
|
+
|
|
38
|
+
Request:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"contact_id": "customer:42",
|
|
43
|
+
"message": "Hello",
|
|
44
|
+
"thread_id": null,
|
|
45
|
+
"thread_config": {"interface": "api"},
|
|
46
|
+
"event_type": "comm.user_message",
|
|
47
|
+
"run_conversation_start_on_new_thread": false
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Response:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"thread_id": "0194f0f3-d88d-7cca-8f37-ff44f911f539",
|
|
56
|
+
"created_new_thread": true,
|
|
57
|
+
"assistant_messages": ["Hi! How can I help?"],
|
|
58
|
+
"events": [
|
|
59
|
+
{
|
|
60
|
+
"event_type": "comm.assistant_message",
|
|
61
|
+
"event_data": {"role": "assistant", "content": "Hi! How can I help?"}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If `JIMS_API_KEY` is set, send it as `Authorization: Bearer <token>`
|
jims_api-0.0.0/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# jims-api
|
|
2
|
+
|
|
3
|
+
`jims-api` exposes any JIMS application as a FastAPI HTTP service.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Run
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
jims-api --app my_project.app:app --port 8080
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Environment variables:
|
|
13
|
+
|
|
14
|
+
- `JIMS_APP` - JIMS app import path (`module:attr`)
|
|
15
|
+
- `JIMS_PORT` - HTTP port
|
|
16
|
+
- `JIMS_HOST` - HTTP host
|
|
17
|
+
- `JIMS_API_KEY` - optional bearer token for auth
|
|
18
|
+
|
|
19
|
+
## Endpoints
|
|
20
|
+
|
|
21
|
+
- `GET /health`
|
|
22
|
+
- `POST /api/v1/chat`
|
|
23
|
+
|
|
24
|
+
### `POST /api/v1/chat`
|
|
25
|
+
|
|
26
|
+
Request:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"contact_id": "customer:42",
|
|
31
|
+
"message": "Hello",
|
|
32
|
+
"thread_id": null,
|
|
33
|
+
"thread_config": {"interface": "api"},
|
|
34
|
+
"event_type": "comm.user_message",
|
|
35
|
+
"run_conversation_start_on_new_thread": false
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Response:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"thread_id": "0194f0f3-d88d-7cca-8f37-ff44f911f539",
|
|
44
|
+
"created_new_thread": true,
|
|
45
|
+
"assistant_messages": ["Hi! How can I help?"],
|
|
46
|
+
"events": [
|
|
47
|
+
{
|
|
48
|
+
"event_type": "comm.assistant_message",
|
|
49
|
+
"event_data": {"role": "assistant", "content": "Hi! How can I help?"}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If `JIMS_API_KEY` is set, send it as `Authorization: Bearer <token>`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "jims-api"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "universal API for JIMS application"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"click>=8.0",
|
|
9
|
+
"fastapi>=0.128.0",
|
|
10
|
+
"jims-core>=0.4.2",
|
|
11
|
+
"loguru>=0.7.3",
|
|
12
|
+
"uvicorn>=0.35.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.uv.sources]
|
|
16
|
+
jims-core = { workspace = true }
|
|
17
|
+
|
|
18
|
+
[tool.uv-workspace-codegen]
|
|
19
|
+
generate = true
|
|
20
|
+
template_type = ["lib", "publish"]
|
|
21
|
+
generate_standard_pytest_step = true
|
|
22
|
+
|
|
23
|
+
[tool.pytest.ini_options]
|
|
24
|
+
asyncio_mode = "auto"
|
|
25
|
+
testpaths = ["tests"]
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"aiosqlite>=0.20.0",
|
|
30
|
+
"httpx>=0.28.0",
|
|
31
|
+
"mypy>=1.19.1",
|
|
32
|
+
"pytest>=8.4.1",
|
|
33
|
+
"pytest-asyncio>=1.1.0",
|
|
34
|
+
"ruff>=0.15.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
jims-api = "jims_api.main:main"
|
jims_api-0.0.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, Awaitable
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import uvicorn
|
|
7
|
+
from fastapi import Depends, FastAPI, Header, HTTPException
|
|
8
|
+
from jims_core.app import JimsApp
|
|
9
|
+
from jims_core.thread.thread_controller import ThreadController
|
|
10
|
+
from jims_core.util import (
|
|
11
|
+
load_jims_app,
|
|
12
|
+
setup_monitoring_and_tracing_with_sentry,
|
|
13
|
+
setup_prometheus_metrics,
|
|
14
|
+
setup_verbose_logging,
|
|
15
|
+
uuid7,
|
|
16
|
+
)
|
|
17
|
+
from loguru import logger
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ChatRequest(BaseModel):
|
|
22
|
+
contact_id: str
|
|
23
|
+
message: str
|
|
24
|
+
thread_id: UUID | None = None # if none provided - create new thread
|
|
25
|
+
thread_config: dict[str, Any] = Field(default_factory=lambda: {"interface": "api"})
|
|
26
|
+
event_type: str = "comm.user_message"
|
|
27
|
+
run_conversation_start_on_new_thread: bool = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ApiEvent(BaseModel):
|
|
31
|
+
event_type: str
|
|
32
|
+
event_data: dict[str, Any]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ChatResponse(BaseModel):
|
|
36
|
+
thread_id: UUID
|
|
37
|
+
created_new_thread: bool
|
|
38
|
+
assistant_messages: list[str]
|
|
39
|
+
events: list[ApiEvent]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _extract_token(authorization: str | None) -> str | None:
|
|
43
|
+
if authorization is None:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
if authorization.lower().startswith("bearer "):
|
|
47
|
+
return authorization.split(" ", 1)[1]
|
|
48
|
+
|
|
49
|
+
return authorization
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _auth_dependency(api_key: str | None):
|
|
53
|
+
async def require_auth(authorization: str | None = Header(default=None, alias="Authorization")) -> None:
|
|
54
|
+
if api_key is None:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
token = _extract_token(authorization)
|
|
58
|
+
if token != api_key:
|
|
59
|
+
raise HTTPException(status_code=401, detail="Unauthorized")
|
|
60
|
+
|
|
61
|
+
return require_auth
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def _resolve_jims_app(app_name: str) -> JimsApp:
|
|
65
|
+
loaded_app = load_jims_app(app_name)
|
|
66
|
+
if isinstance(loaded_app, Awaitable):
|
|
67
|
+
loaded_app = await loaded_app
|
|
68
|
+
return loaded_app
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def create_api(jims_app: JimsApp, api_key: str | None) -> FastAPI:
|
|
72
|
+
app = FastAPI(title="JIMS API", version="0.1.0")
|
|
73
|
+
require_auth = _auth_dependency(api_key)
|
|
74
|
+
|
|
75
|
+
@app.get("/health")
|
|
76
|
+
async def health() -> dict[str, str]:
|
|
77
|
+
return {"status": "ok"}
|
|
78
|
+
|
|
79
|
+
@app.post("/api/v1/chat", response_model=ChatResponse, dependencies=[Depends(require_auth)])
|
|
80
|
+
async def chat(req: ChatRequest) -> ChatResponse:
|
|
81
|
+
created_new_thread = False
|
|
82
|
+
|
|
83
|
+
if req.thread_id is not None:
|
|
84
|
+
ctl = await ThreadController.from_thread_id(jims_app.sessionmaker, req.thread_id)
|
|
85
|
+
if ctl is None:
|
|
86
|
+
raise HTTPException(status_code=404, detail=f"Thread '{req.thread_id}' not found")
|
|
87
|
+
else:
|
|
88
|
+
ctl = await ThreadController.latest_thread_from_contact_id(jims_app.sessionmaker, req.contact_id)
|
|
89
|
+
|
|
90
|
+
if ctl is None:
|
|
91
|
+
created_new_thread = True
|
|
92
|
+
thread_id = uuid7()
|
|
93
|
+
ctl = await jims_app.new_thread(
|
|
94
|
+
contact_id=req.contact_id,
|
|
95
|
+
thread_id=thread_id,
|
|
96
|
+
thread_config=req.thread_config,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if req.run_conversation_start_on_new_thread and jims_app.conversation_start_pipeline is not None:
|
|
100
|
+
await ctl.run_pipeline_with_context(jims_app.conversation_start_pipeline)
|
|
101
|
+
|
|
102
|
+
await ctl.store_event_dict(
|
|
103
|
+
event_id=uuid7(),
|
|
104
|
+
event_type=req.event_type,
|
|
105
|
+
event_data={"role": "user", "content": req.message},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
outgoing_events = await ctl.run_pipeline_with_context(jims_app.pipeline)
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
logger.exception("Pipeline execution failed")
|
|
112
|
+
raise HTTPException(status_code=500, detail=f"Pipeline error: {exc}") from exc
|
|
113
|
+
|
|
114
|
+
assistant_messages = [
|
|
115
|
+
str(ev.event_data.get("content", ""))
|
|
116
|
+
for ev in outgoing_events
|
|
117
|
+
if ev.event_type == "comm.assistant_message" and isinstance(ev.event_data, dict)
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
response_events = [
|
|
121
|
+
ApiEvent(
|
|
122
|
+
event_type=ev.event_type,
|
|
123
|
+
event_data=ev.event_data if isinstance(ev.event_data, dict) else {},
|
|
124
|
+
# todo filter comm.* events only?
|
|
125
|
+
)
|
|
126
|
+
for ev in outgoing_events
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
return ChatResponse(
|
|
130
|
+
thread_id=ctl.thread.thread_id,
|
|
131
|
+
created_new_thread=created_new_thread,
|
|
132
|
+
assistant_messages=assistant_messages,
|
|
133
|
+
events=response_events,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return app
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@click.command()
|
|
140
|
+
@click.option("--app", type=click.STRING, default="app", help="JIMS app in module:attr format.")
|
|
141
|
+
@click.option("--host", type=click.STRING, default="0.0.0.0")
|
|
142
|
+
@click.option("--port", type=click.INT, default=8080)
|
|
143
|
+
@click.option("--api-key", type=click.STRING, default=None, help="Optional bearer token for API auth.")
|
|
144
|
+
@click.option("--enable-sentry", is_flag=True, help="Enable tracing to Sentry", default=False)
|
|
145
|
+
@click.option("--metrics-port", type=click.INT, default=8000)
|
|
146
|
+
@click.option("--verbose", is_flag=True, default=False)
|
|
147
|
+
def cli(
|
|
148
|
+
app: str,
|
|
149
|
+
host: str,
|
|
150
|
+
port: int,
|
|
151
|
+
api_key: str | None,
|
|
152
|
+
enable_sentry: bool,
|
|
153
|
+
metrics_port: int,
|
|
154
|
+
verbose: bool,
|
|
155
|
+
) -> None:
|
|
156
|
+
if verbose:
|
|
157
|
+
setup_verbose_logging()
|
|
158
|
+
|
|
159
|
+
setup_prometheus_metrics(port=metrics_port)
|
|
160
|
+
|
|
161
|
+
if enable_sentry:
|
|
162
|
+
setup_monitoring_and_tracing_with_sentry()
|
|
163
|
+
|
|
164
|
+
async def run_api() -> None:
|
|
165
|
+
jims_app = await _resolve_jims_app(app)
|
|
166
|
+
api = create_api(jims_app, api_key=api_key)
|
|
167
|
+
server = uvicorn.Server(uvicorn.Config(api, host=host, port=port, log_level="info"))
|
|
168
|
+
await server.serve()
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
asyncio.run(run_api())
|
|
172
|
+
except KeyboardInterrupt:
|
|
173
|
+
logger.info("API stopped by user")
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
logger.exception(f"API crashed: {exc}")
|
|
176
|
+
raise SystemExit(1) from exc
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main() -> None:
|
|
180
|
+
cli(auto_envvar_prefix="JIMS")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jims-api
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: universal API for JIMS application
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: click>=8.0
|
|
8
|
+
Requires-Dist: fastapi>=0.128.0
|
|
9
|
+
Requires-Dist: jims-core>=0.4.2
|
|
10
|
+
Requires-Dist: loguru>=0.7.3
|
|
11
|
+
Requires-Dist: uvicorn>=0.35.0
|
|
12
|
+
|
|
13
|
+
# jims-api
|
|
14
|
+
|
|
15
|
+
`jims-api` exposes any JIMS application as a FastAPI HTTP service.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Run
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
jims-api --app my_project.app:app --port 8080
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Environment variables:
|
|
25
|
+
|
|
26
|
+
- `JIMS_APP` - JIMS app import path (`module:attr`)
|
|
27
|
+
- `JIMS_PORT` - HTTP port
|
|
28
|
+
- `JIMS_HOST` - HTTP host
|
|
29
|
+
- `JIMS_API_KEY` - optional bearer token for auth
|
|
30
|
+
|
|
31
|
+
## Endpoints
|
|
32
|
+
|
|
33
|
+
- `GET /health`
|
|
34
|
+
- `POST /api/v1/chat`
|
|
35
|
+
|
|
36
|
+
### `POST /api/v1/chat`
|
|
37
|
+
|
|
38
|
+
Request:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"contact_id": "customer:42",
|
|
43
|
+
"message": "Hello",
|
|
44
|
+
"thread_id": null,
|
|
45
|
+
"thread_config": {"interface": "api"},
|
|
46
|
+
"event_type": "comm.user_message",
|
|
47
|
+
"run_conversation_start_on_new_thread": false
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Response:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"thread_id": "0194f0f3-d88d-7cca-8f37-ff44f911f539",
|
|
56
|
+
"created_new_thread": true,
|
|
57
|
+
"assistant_messages": ["Hi! How can I help?"],
|
|
58
|
+
"events": [
|
|
59
|
+
{
|
|
60
|
+
"event_type": "comm.assistant_message",
|
|
61
|
+
"event_data": {"role": "assistant", "content": "Hi! How can I help?"}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If `JIMS_API_KEY` is set, send it as `Authorization: Bearer <token>`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/jims_api/__init__.py
|
|
4
|
+
src/jims_api/main.py
|
|
5
|
+
src/jims_api/py.typed
|
|
6
|
+
src/jims_api.egg-info/PKG-INFO
|
|
7
|
+
src/jims_api.egg-info/SOURCES.txt
|
|
8
|
+
src/jims_api.egg-info/dependency_links.txt
|
|
9
|
+
src/jims_api.egg-info/entry_points.txt
|
|
10
|
+
src/jims_api.egg-info/requires.txt
|
|
11
|
+
src/jims_api.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jims_api
|