cevheri commited on
Commit
26e046b
·
1 Parent(s): ac9343b

feat: mock capabilities initial commit

Browse files
app/core/api_response.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import wraps
2
+ from fastapi import HTTPException, Request, status
3
+ from loguru import logger
4
+ import os
5
+ import json
6
+ from typing import Dict
7
+ from environs import Env
8
+
9
+ env = Env()
10
+ env.read_env()
11
+
12
+ USE_MOCK = env.bool("USE_MOCK", True)
13
+
14
+
15
+ def url_to_filename(url: str, method: str) -> str:
16
+ """
17
+ Convert API URL to mock filename.
18
+ Example:
19
+ - Input: GET "/v1/chat/completions" -> "chat_completions_GET.json"
20
+ - Input: GET "/v1/chat/completions/{completion_id}" -> "chat_completions_id_GET.json"
21
+ - Input: GET "/v1/chat/completions/123/messages" -> "chat_completions_id_messages_GET.json"
22
+ """
23
+ logger.trace(f"BEGIN: url: {url} method: {method}")
24
+ # Remove version prefix and leading/trailing slashes
25
+ path = url.strip("/")
26
+ if path.startswith("v1/"):
27
+ path = path[3:]
28
+
29
+ # Replace path parameters with descriptive names
30
+ path = path.replace("{completion_id}", "id")
31
+ path = path.replace("{message_id}", "id")
32
+
33
+ # Convert to filename format
34
+ filename = path.replace("/", "_")
35
+
36
+ # Add method suffix
37
+ result = f"{filename}_{method}"
38
+ logger.trace(f"END: result: {result}")
39
+ return result
40
+
41
+
42
+ def get_mock_response(url_path: str, method: str) -> Dict:
43
+ """Get mock response from JSON file."""
44
+ logger.trace(f"BEGIN: url_path: {url_path} method: {method}")
45
+ filename = None
46
+ try:
47
+ # Convert to filename
48
+ filename = url_to_filename(url_path, method)
49
+
50
+ # Load mock response
51
+ file_path = os.path.join(os.path.dirname(__file__), "mock", f"{filename}.json")
52
+ with open(file_path, "r") as f:
53
+ result = json.load(f)
54
+ logger.trace(f"END: result: {result}")
55
+ return result
56
+ except FileNotFoundError:
57
+ raise HTTPException(
58
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
59
+ detail=f"{filename} mock file not found for endpoint: {url_path} [{method}]"
60
+ )
61
+ except Exception as e:
62
+ raise HTTPException(
63
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
64
+ detail=f"{filename} error loading mock response: {str(e)}"
65
+ )
66
+
67
+
68
+ def api_response():
69
+ """Decorator to handle mock/real API responses."""
70
+ def decorator(func):
71
+ @wraps(func)
72
+ async def wrapper(request: Request, *args, **kwargs):
73
+ url_path = request.url.path
74
+ method = request.method
75
+ logger.debug(f"BEGIN: url_path: {url_path} method: {method}")
76
+ if USE_MOCK:
77
+ logger.warning("Using mock response")
78
+ result = get_mock_response(url_path, method)
79
+ logger.trace(f"END: result: {result}")
80
+ return result
81
+ else:
82
+ # Call original function for real response
83
+ result = await func(request, *args, **kwargs)
84
+ logger.trace(f"END: result: {result}")
85
+ return result
86
+ return wrapper
87
+ return decorator
docs/conversation.drawio ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <mxfile host="65bd71144e">
2
+ <diagram id="RUA-RFROyaZDF0aJ2uCW" name="Page-1">
3
+ <mxGraphModel dx="1098" dy="639" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" adaptiveColors="simple">
4
+ <root>
5
+ <mxCell id="0"/>
6
+ <mxCell id="1" parent="0"/>
7
+ <mxCell id="4" style="edgeStyle=none;html=1;" edge="1" parent="1" source="2" target="3">
8
+ <mxGeometry relative="1" as="geometry"/>
9
+ </mxCell>
10
+ <mxCell id="2" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
11
+ <mxGeometry x="70" y="230" width="30" height="60" as="geometry"/>
12
+ </mxCell>
13
+ <mxCell id="6" value="" style="edgeStyle=none;html=1;" edge="1" parent="1" source="3" target="5">
14
+ <mxGeometry relative="1" as="geometry"/>
15
+ </mxCell>
16
+ <mxCell id="3" value="frontend" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
17
+ <mxGeometry x="230" y="230" width="120" height="60" as="geometry"/>
18
+ </mxCell>
19
+ <mxCell id="8" style="edgeStyle=none;html=1;" edge="1" parent="1" source="5" target="7">
20
+ <mxGeometry relative="1" as="geometry"/>
21
+ </mxCell>
22
+ <mxCell id="10" value="" style="edgeStyle=none;html=1;" edge="1" parent="1" source="5" target="9">
23
+ <mxGeometry relative="1" as="geometry"/>
24
+ </mxCell>
25
+ <mxCell id="12" style="edgeStyle=none;html=1;" edge="1" parent="1" source="5" target="11">
26
+ <mxGeometry relative="1" as="geometry"/>
27
+ </mxCell>
28
+ <mxCell id="17" style="edgeStyle=none;html=1;" edge="1" parent="1" source="5" target="16">
29
+ <mxGeometry relative="1" as="geometry"/>
30
+ </mxCell>
31
+ <mxCell id="20" style="edgeStyle=none;html=1;" edge="1" parent="1" source="5" target="19">
32
+ <mxGeometry relative="1" as="geometry"/>
33
+ </mxCell>
34
+ <mxCell id="5" value="backend" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
35
+ <mxGeometry x="500" y="230" width="120" height="60" as="geometry"/>
36
+ </mxCell>
37
+ <mxCell id="7" value="mongo" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
38
+ <mxGeometry x="800" y="220" width="60" height="80" as="geometry"/>
39
+ </mxCell>
40
+ <mxCell id="22" value="" style="edgeStyle=none;html=1;" edge="1" parent="1" source="9" target="21">
41
+ <mxGeometry relative="1" as="geometry"/>
42
+ </mxCell>
43
+ <mxCell id="24" value="" style="edgeStyle=none;html=1;" edge="1" parent="1" source="9" target="23">
44
+ <mxGeometry relative="1" as="geometry"/>
45
+ </mxCell>
46
+ <mxCell id="9" value="schema" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
47
+ <mxGeometry x="180" y="400" width="120" height="60" as="geometry"/>
48
+ </mxCell>
49
+ <mxCell id="14" style="edgeStyle=none;html=1;" edge="1" parent="1" source="11" target="13">
50
+ <mxGeometry relative="1" as="geometry"/>
51
+ </mxCell>
52
+ <mxCell id="11" value="model" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
53
+ <mxGeometry x="490" y="400" width="120" height="60" as="geometry"/>
54
+ </mxCell>
55
+ <mxCell id="15" style="edgeStyle=none;html=1;" edge="1" parent="1" source="13" target="16">
56
+ <mxGeometry relative="1" as="geometry">
57
+ <mxPoint x="640" y="460" as="targetPoint"/>
58
+ </mxGeometry>
59
+ </mxCell>
60
+ <mxCell id="27" style="edgeStyle=none;html=1;" edge="1" parent="1" source="13" target="23">
61
+ <mxGeometry relative="1" as="geometry"/>
62
+ </mxCell>
63
+ <mxCell id="28" style="edgeStyle=none;html=1;" edge="1" parent="1" source="13" target="21">
64
+ <mxGeometry relative="1" as="geometry"/>
65
+ </mxCell>
66
+ <mxCell id="13" value="chat_completion" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
67
+ <mxGeometry x="660" y="640" width="120" height="120" as="geometry"/>
68
+ </mxCell>
69
+ <mxCell id="29" style="edgeStyle=none;html=1;" edge="1" parent="1" source="16" target="7">
70
+ <mxGeometry relative="1" as="geometry"/>
71
+ </mxCell>
72
+ <mxCell id="16" value="repository" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
73
+ <mxGeometry x="670" y="400" width="120" height="60" as="geometry"/>
74
+ </mxCell>
75
+ <mxCell id="18" value="api" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
76
+ <mxGeometry x="25" y="400" width="120" height="60" as="geometry"/>
77
+ </mxCell>
78
+ <mxCell id="19" value="service" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
79
+ <mxGeometry x="340" y="400" width="120" height="60" as="geometry"/>
80
+ </mxCell>
81
+ <mxCell id="21" value="conversation" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
82
+ <mxGeometry x="180" y="670" width="80" height="80" as="geometry"/>
83
+ </mxCell>
84
+ <mxCell id="23" value="&lt;span style=&quot;color: rgb(0, 0, 0);&quot;&gt;chat_completion&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
85
+ <mxGeometry x="260" y="520" width="120" height="90" as="geometry"/>
86
+ </mxCell>
87
+ </root>
88
+ </mxGraphModel>
89
+ </diagram>
90
+ </mxfile>
resources/mock/chat_completions_GET.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "chatcmpl-mock-123",
4
+ "object": "chat.completion",
5
+ "created": 1677858242,
6
+ "model": "gpt-3.5-turbo",
7
+ "choices": [
8
+ {
9
+ "index": 0,
10
+ "message": {
11
+ "role": "assistant",
12
+ "content": "This is a mock response from the assistant.",
13
+ "tool_calls": null,
14
+ "annotations": [],
15
+ "audio": null,
16
+ "refusal": ""
17
+ },
18
+ "finish_reason": "stop",
19
+ "logprobs": null
20
+ }
21
+ ],
22
+ "usage": {
23
+ "prompt_tokens": 10,
24
+ "completion_tokens": 20,
25
+ "total_tokens": 30
26
+ },
27
+ "service_tier": "standard",
28
+ "system_fingerprint": "fp_1234567890"
29
+ }
30
+ ]
resources/mock/chat_completions_POST.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "chatcmpl-mock-123",
3
+ "object": "chat.completion",
4
+ "created": 1677858242,
5
+ "model": "gpt-3.5-turbo",
6
+ "choices": [
7
+ {
8
+ "index": 0,
9
+ "message": {
10
+ "role": "assistant",
11
+ "content": "This is a mock response from the assistant.",
12
+ "tool_calls": null,
13
+ "annotations": [],
14
+ "audio": null,
15
+ "refusal": ""
16
+ },
17
+ "finish_reason": "stop",
18
+ "logprobs": null
19
+ }
20
+ ],
21
+ "usage": {
22
+ "prompt_tokens": 10,
23
+ "completion_tokens": 20,
24
+ "total_tokens": 30
25
+ },
26
+ "service_tier": "standard",
27
+ "system_fingerprint": "fp_1234567890"
28
+ }
resources/mock/chat_completions_id_GET.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "chatcmpl-mock-123",
3
+ "object": "chat.completion",
4
+ "created": 1677858242,
5
+ "model": "gpt-3.5-turbo",
6
+ "choices": [
7
+ {
8
+ "index": 0,
9
+ "message": {
10
+ "role": "assistant",
11
+ "content": "This is a mock response from the assistant.",
12
+ "tool_calls": null,
13
+ "annotations": [],
14
+ "audio": null,
15
+ "refusal": ""
16
+ },
17
+ "finish_reason": "stop",
18
+ "logprobs": null
19
+ }
20
+ ],
21
+ "usage": {
22
+ "prompt_tokens": 10,
23
+ "completion_tokens": 20,
24
+ "total_tokens": 30
25
+ },
26
+ "service_tier": "standard",
27
+ "system_fingerprint": "fp_1234567890"
28
+ }
resources/mock/chat_completions_messages_GET.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "object": "list",
3
+ "data": [
4
+ {
5
+ "id": "chatcmpl-mock-123",
6
+ "object": "chat.completion",
7
+ "created": 1677858242,
8
+ "model": "gpt-3.5-turbo",
9
+ "choices": [
10
+ {
11
+ "index": 0,
12
+ "message": {
13
+ "role": "assistant",
14
+ "content": "This is a mock response from the assistant.",
15
+ "tool_calls": null,
16
+ "annotations": [],
17
+ "audio": null,
18
+ "refusal": ""
19
+ },
20
+ "finish_reason": "stop",
21
+ "logprobs": null
22
+ }
23
+ ],
24
+ "usage": {
25
+ "prompt_tokens": 10,
26
+ "completion_tokens": 20,
27
+ "total_tokens": 30
28
+ },
29
+ "service_tier": "standard",
30
+ "system_fingerprint": "fp_1234567890"
31
+ }
32
+ ],
33
+ "first_id": "chatcmpl-mock-123",
34
+ "last_id": "chatcmpl-mock-123",
35
+ "has_more": false
36
+ }
resources/mock/chat_completions_plots_GET.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "chatcmpl-mock-123",
3
+ "name": "Mock Chat",
4
+ "created_at": 1677858242,
5
+ "updated_at": 1677858242,
6
+ "message": {
7
+ "message_id": "msg-mock-123",
8
+ "role": "assistant",
9
+ "content": "This is a mock response from the assistant.",
10
+ "name": null,
11
+ "function_call": null,
12
+ "tool_calls": null,
13
+ "annotations": [],
14
+ "audio": null,
15
+ "refusal": "",
16
+ "created_at": 1677858242
17
+ },
18
+ "plots": [
19
+ {
20
+ "type": "line",
21
+ "data": {
22
+ "x": [1, 2, 3, 4, 5],
23
+ "y": [1, 4, 9, 16, 25]
24
+ },
25
+ "title": "Mock Plot"
26
+ }
27
+ ]
28
+ }
resources/mock/conversations_GET.json ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "items": [
3
+ {
4
+ "id": "682f026d-23e8-800f-90a1-a1399b85ae64",
5
+ "title": "Customer status",
6
+ "create_time": "2025-05-22T10:54:37.569747Z",
7
+ "update_time": "2025-05-22T10:55:55.212705Z",
8
+ "mapping": null,
9
+ "current_node": null,
10
+ "conversation_template_id": null,
11
+ "gizmo_id": null,
12
+ "is_archived": false,
13
+ "is_starred": null,
14
+ "is_do_not_remember": false,
15
+ "memory_scope": "global_enabled",
16
+ "workspace_id": null,
17
+ "async_status": null,
18
+ "safe_urls": [],
19
+ "blocked_urls": [],
20
+ "conversation_origin": null,
21
+ "snippet": null
22
+ },
23
+ {
24
+ "id": "682ede89-cd84-800f-b80f-8389bcbee865",
25
+ "title": "Ticket count by status",
26
+ "create_time": "2025-05-22T08:21:30.215436Z",
27
+ "update_time": "2025-05-22T08:22:54.867192Z",
28
+ "mapping": null,
29
+ "current_node": null,
30
+ "conversation_template_id": null,
31
+ "gizmo_id": null,
32
+ "is_archived": false,
33
+ "is_starred": null,
34
+ "is_do_not_remember": false,
35
+ "memory_scope": "global_enabled",
36
+ "workspace_id": null,
37
+ "async_status": null,
38
+ "safe_urls": [],
39
+ "blocked_urls": [],
40
+ "conversation_origin": null,
41
+ "snippet": null
42
+ },
43
+ {
44
+ "id": "682ec852-4014-800f-8a98-d257668a391e",
45
+ "title": "Ticket comments count by customer",
46
+ "create_time": "2025-05-22T06:46:42.703604Z",
47
+ "update_time": "2025-05-22T06:50:05.464929Z",
48
+ "mapping": null,
49
+ "current_node": null,
50
+ "conversation_template_id": null,
51
+ "gizmo_id": null,
52
+ "is_archived": false,
53
+ "is_starred": null,
54
+ "is_do_not_remember": false,
55
+ "memory_scope": "global_enabled",
56
+ "workspace_id": null,
57
+ "async_status": null,
58
+ "safe_urls": [],
59
+ "blocked_urls": [],
60
+ "conversation_origin": null,
61
+ "snippet": null
62
+ }
63
+ ],
64
+ "total": 79,
65
+ "limit": 28,
66
+ "offset": 0
67
+ }
resources/mock/conversations_id_GET.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "682f026d-23e8-800f-90a1-a1399b85ae64",
3
+ "title": "Customer status",
4
+ "create_time": "2025-05-22T10:54:37.569747Z",
5
+ "update_time": "2025-05-22T10:55:55.212705Z",
6
+ "mapping": null,
7
+ "current_node": null,
8
+ "conversation_template_id": null,
9
+ "gizmo_id": null,
10
+ "is_archived": false,
11
+ "is_starred": null,
12
+ "is_do_not_remember": false,
13
+ "memory_scope": "global_enabled",
14
+ "workspace_id": null,
15
+ "async_status": null,
16
+ "safe_urls": [],
17
+ "blocked_urls": [],
18
+ "conversation_origin": null,
19
+ "snippet": null
20
+ }