grip-sdk 0.3.0__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.
- grip/__init__.py +49 -0
- grip/_version.py +3 -0
- grip/async_client.py +161 -0
- grip/cli/__init__.py +5 -0
- grip/cli/main.py +160 -0
- grip/client.py +415 -0
- grip/config.py +35 -0
- grip/exceptions.py +24 -0
- grip/resources/__init__.py +13 -0
- grip/resources/search.py +129 -0
- grip/resources/sessions.py +140 -0
- grip/types/__init__.py +17 -0
- grip/types/response.py +7 -0
- grip/types/session.py +7 -0
- grip/types/source.py +5 -0
- grip_core/__init__.py +52 -0
- grip_core/config.py +86 -0
- grip_core/errors.py +35 -0
- grip_core/models/__init__.py +17 -0
- grip_core/models/request.py +28 -0
- grip_core/models/response.py +137 -0
- grip_core/models/session.py +93 -0
- grip_core/models/source.py +47 -0
- grip_core/protocols.py +59 -0
- grip_core/security/__init__.py +5 -0
- grip_core/security/validation.py +32 -0
- grip_sdk-0.3.0.dist-info/METADATA +31 -0
- grip_sdk-0.3.0.dist-info/RECORD +32 -0
- grip_sdk-0.3.0.dist-info/WHEEL +5 -0
- grip_sdk-0.3.0.dist-info/entry_points.txt +2 -0
- grip_sdk-0.3.0.dist-info/licenses/LICENSE +21 -0
- grip_sdk-0.3.0.dist-info/top_level.txt +2 -0
grip/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Grip: Enterprise AI Search, Dynamic Provider Orchestration, and Multi-Agent SDK.
|
|
2
|
+
|
|
3
|
+
https://grip.theshoaib.me/
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from ._version import __version__
|
|
9
|
+
from .async_client import AsyncClient, AsyncGrip, AsyncGripClient
|
|
10
|
+
from .client import Client, Grip, GripClient
|
|
11
|
+
from .config import ClientConfig, Config, get_config
|
|
12
|
+
from .exceptions import BotBlockedError, ConfigurationError, GripError, ProviderError
|
|
13
|
+
from .types import (
|
|
14
|
+
Metadata,
|
|
15
|
+
SearchResponse,
|
|
16
|
+
SessionDetail,
|
|
17
|
+
SessionMessage,
|
|
18
|
+
SessionSummary,
|
|
19
|
+
SourceItem,
|
|
20
|
+
StreamChunk,
|
|
21
|
+
)
|
|
22
|
+
from grip_engine.infrastructure.cache import TTLCache
|
|
23
|
+
from grip_engine.infrastructure.rate_limit import RateLimiter
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"__version__",
|
|
27
|
+
"Grip",
|
|
28
|
+
"Client",
|
|
29
|
+
"GripClient",
|
|
30
|
+
"AsyncGrip",
|
|
31
|
+
"AsyncClient",
|
|
32
|
+
"AsyncGripClient",
|
|
33
|
+
"Config",
|
|
34
|
+
"ClientConfig",
|
|
35
|
+
"get_config",
|
|
36
|
+
"RateLimiter",
|
|
37
|
+
"TTLCache",
|
|
38
|
+
"SearchResponse",
|
|
39
|
+
"StreamChunk",
|
|
40
|
+
"Metadata",
|
|
41
|
+
"SourceItem",
|
|
42
|
+
"SessionDetail",
|
|
43
|
+
"SessionSummary",
|
|
44
|
+
"SessionMessage",
|
|
45
|
+
"GripError",
|
|
46
|
+
"ProviderError",
|
|
47
|
+
"BotBlockedError",
|
|
48
|
+
"ConfigurationError",
|
|
49
|
+
]
|
grip/_version.py
ADDED
grip/async_client.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Grip Python SDK Asynchronous Client.
|
|
2
|
+
|
|
3
|
+
Enterprise asynchronous AI Search, Provider Agnostic Orchestration,
|
|
4
|
+
and Multi-turn Conversational Sessions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from collections.abc import AsyncGenerator
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from grip_core.config import GripConfig, load_config
|
|
14
|
+
from grip_core.models.response import Metadata, SearchResponse, StreamChunk
|
|
15
|
+
from grip_core.models.session import SessionDetail, SessionSummary
|
|
16
|
+
from grip_core.models.source import SourceItem
|
|
17
|
+
|
|
18
|
+
from .client import Grip
|
|
19
|
+
from .config import ClientConfig
|
|
20
|
+
from .resources.search import AsyncSearchResource
|
|
21
|
+
from .resources.sessions import AsyncSessionsResource
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncGrip:
|
|
25
|
+
"""Primary asynchronous client for Grip Platform.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
>>> import asyncio
|
|
29
|
+
>>> from grip import AsyncGrip
|
|
30
|
+
>>>
|
|
31
|
+
>>> async def main():
|
|
32
|
+
... client = AsyncGrip()
|
|
33
|
+
... response = await client.search.query("Quantum computing breakthroughs")
|
|
34
|
+
... print(response.answer)
|
|
35
|
+
... await client.close()
|
|
36
|
+
>>> asyncio.run(main())
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
base_url: str | None = None,
|
|
42
|
+
api_key: str | None = None,
|
|
43
|
+
config: GripConfig | ClientConfig | None = None,
|
|
44
|
+
engine: Any = None,
|
|
45
|
+
provider: str = "google",
|
|
46
|
+
timeout: float = 60.0,
|
|
47
|
+
) -> None:
|
|
48
|
+
self._sync_client = Grip(
|
|
49
|
+
base_url=base_url,
|
|
50
|
+
api_key=api_key,
|
|
51
|
+
config=config,
|
|
52
|
+
engine=engine,
|
|
53
|
+
provider=provider,
|
|
54
|
+
timeout=timeout,
|
|
55
|
+
)
|
|
56
|
+
self.provider = provider
|
|
57
|
+
self.search = AsyncSearchResource(client=self)
|
|
58
|
+
self.sessions = AsyncSessionsResource(client=self)
|
|
59
|
+
|
|
60
|
+
async def _execute_query(
|
|
61
|
+
self,
|
|
62
|
+
query: str,
|
|
63
|
+
session_id: str | None = None,
|
|
64
|
+
provider: str | None = None,
|
|
65
|
+
thread_id: str | None = None,
|
|
66
|
+
timeout: float | None = None,
|
|
67
|
+
) -> SearchResponse:
|
|
68
|
+
return await asyncio.to_thread(
|
|
69
|
+
self._sync_client._execute_query,
|
|
70
|
+
query=query,
|
|
71
|
+
session_id=session_id,
|
|
72
|
+
provider=provider or self.provider,
|
|
73
|
+
thread_id=thread_id,
|
|
74
|
+
timeout=timeout,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
async def _execute_stream(
|
|
78
|
+
self,
|
|
79
|
+
query: str,
|
|
80
|
+
session_id: str | None = None,
|
|
81
|
+
provider: str | None = None,
|
|
82
|
+
thread_id: str | None = None,
|
|
83
|
+
) -> AsyncGenerator[StreamChunk, None]:
|
|
84
|
+
def _get_chunks() -> list[StreamChunk]:
|
|
85
|
+
return list(
|
|
86
|
+
self._sync_client._execute_stream(
|
|
87
|
+
query=query,
|
|
88
|
+
session_id=session_id,
|
|
89
|
+
provider=provider or self.provider,
|
|
90
|
+
thread_id=thread_id,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
chunks = await asyncio.to_thread(_get_chunks)
|
|
95
|
+
for chunk in chunks:
|
|
96
|
+
yield chunk
|
|
97
|
+
|
|
98
|
+
async def _create_session(
|
|
99
|
+
self,
|
|
100
|
+
title: str | None = None,
|
|
101
|
+
session_id: str | None = None,
|
|
102
|
+
metadata: dict[str, Any] | None = None,
|
|
103
|
+
) -> SessionDetail:
|
|
104
|
+
return await asyncio.to_thread(
|
|
105
|
+
self._sync_client._create_session,
|
|
106
|
+
title=title,
|
|
107
|
+
session_id=session_id,
|
|
108
|
+
metadata=metadata,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
async def _get_session(self, session_id: str) -> SessionDetail | None:
|
|
112
|
+
return await asyncio.to_thread(self._sync_client._get_session, session_id)
|
|
113
|
+
|
|
114
|
+
async def _list_sessions(self) -> list[SessionSummary]:
|
|
115
|
+
return await asyncio.to_thread(self._sync_client._list_sessions)
|
|
116
|
+
|
|
117
|
+
async def _delete_session(self, session_id: str) -> bool:
|
|
118
|
+
return await asyncio.to_thread(self._sync_client._delete_session, session_id)
|
|
119
|
+
|
|
120
|
+
async def _clear_sessions(self) -> int:
|
|
121
|
+
return await asyncio.to_thread(self._sync_client._clear_sessions)
|
|
122
|
+
|
|
123
|
+
async def query(
|
|
124
|
+
self,
|
|
125
|
+
query: str,
|
|
126
|
+
session_id: str | None = None,
|
|
127
|
+
provider: str | None = None,
|
|
128
|
+
timeout: float | None = None,
|
|
129
|
+
) -> SearchResponse:
|
|
130
|
+
return await self.search.query(
|
|
131
|
+
query=query,
|
|
132
|
+
session_id=session_id,
|
|
133
|
+
provider=provider,
|
|
134
|
+
timeout=timeout,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
async def stream(
|
|
138
|
+
self,
|
|
139
|
+
query: str,
|
|
140
|
+
session_id: str | None = None,
|
|
141
|
+
provider: str | None = None,
|
|
142
|
+
) -> AsyncGenerator[StreamChunk, None]:
|
|
143
|
+
async for chunk in self.search.stream(
|
|
144
|
+
query=query,
|
|
145
|
+
session_id=session_id,
|
|
146
|
+
provider=provider,
|
|
147
|
+
):
|
|
148
|
+
yield chunk
|
|
149
|
+
|
|
150
|
+
async def close(self) -> None:
|
|
151
|
+
await asyncio.to_thread(self._sync_client.close)
|
|
152
|
+
|
|
153
|
+
async def __aenter__(self) -> AsyncGrip:
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
157
|
+
await self.close()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
AsyncClient = AsyncGrip
|
|
161
|
+
AsyncGripClient = AsyncGrip
|
grip/cli/__init__.py
ADDED
grip/cli/main.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Grip CLI: Command-line interface for Grip AI Search & Web Agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Sequence
|
|
9
|
+
|
|
10
|
+
from ..client import Grip
|
|
11
|
+
from .._version import __version__
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="grip",
|
|
17
|
+
description="Grip Platform: Dynamic AI Search & Multi-Agent SDK (https://grip.theshoaib.me/)",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument("--version", "-v", action="version", version=f"Grip Platform v{__version__}")
|
|
20
|
+
subparsers = parser.add_subparsers(dest="command", help="Sub-commands")
|
|
21
|
+
|
|
22
|
+
# Search sub-command
|
|
23
|
+
search_parser = subparsers.add_parser("search", aliases=["query"], help="Execute an AI search query")
|
|
24
|
+
search_parser.add_argument("query", help="Search query string")
|
|
25
|
+
search_parser.add_argument("--json", action="store_true", help="Output raw JSON response")
|
|
26
|
+
search_parser.add_argument("--session-id", help="Attach query to existing session ID")
|
|
27
|
+
search_parser.add_argument("--provider", default="google", help="Search AI provider (google, openai, etc.)")
|
|
28
|
+
search_parser.add_argument("--no-stream", action="store_true", help="Disable streaming and wait for full response")
|
|
29
|
+
|
|
30
|
+
# Interactive chat sub-command
|
|
31
|
+
chat_parser = subparsers.add_parser("chat", aliases=["session"], help="Start an interactive multi-turn session in terminal")
|
|
32
|
+
chat_parser.add_argument("--title", default="Grip Chat", help="Session title")
|
|
33
|
+
chat_parser.add_argument("--provider", default="google", help="Search AI provider (google, openai, etc.)")
|
|
34
|
+
|
|
35
|
+
# Providers sub-command
|
|
36
|
+
subparsers.add_parser("providers", help="List available AI search providers")
|
|
37
|
+
|
|
38
|
+
# Serve sub-command
|
|
39
|
+
serve_parser = subparsers.add_parser("serve", help="Start the Grip AI web server and UI")
|
|
40
|
+
serve_parser.add_argument("--host", default="0.0.0.0", help="Host interface (default: 0.0.0.0)")
|
|
41
|
+
serve_parser.add_argument("--port", type=int, default=8000, help="Port (default: 8000)")
|
|
42
|
+
serve_parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
|
43
|
+
|
|
44
|
+
# Rules sub-command
|
|
45
|
+
rules_parser = subparsers.add_parser("rules", help="Manage custom system rules and persona")
|
|
46
|
+
rules_parser.add_argument("--identity", help="Set assistant identity/persona")
|
|
47
|
+
rules_parser.add_argument("--format", help="Set response format specification")
|
|
48
|
+
rules_parser.add_argument("--add", help="Add a behavioral constraint rule")
|
|
49
|
+
rules_parser.add_argument("--list", action="store_true", help="List active rules")
|
|
50
|
+
|
|
51
|
+
args = parser.parse_args(argv)
|
|
52
|
+
|
|
53
|
+
if not args.command:
|
|
54
|
+
parser.print_help()
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
if args.command == "providers":
|
|
58
|
+
from grip_engine.providers.registry import default_provider_registry
|
|
59
|
+
providers = default_provider_registry.list_providers()
|
|
60
|
+
print("\nRegistered AI Search Providers:")
|
|
61
|
+
for p in providers:
|
|
62
|
+
print(f"• {p}")
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
if args.command == "serve":
|
|
66
|
+
try:
|
|
67
|
+
import uvicorn
|
|
68
|
+
from services.api.app.main import app
|
|
69
|
+
print(f"🚀 Starting Grip AI Platform on http://{args.host}:{args.port}")
|
|
70
|
+
uvicorn.run(app, host=args.host, port=args.port)
|
|
71
|
+
return 0
|
|
72
|
+
except Exception:
|
|
73
|
+
# Fallback to app.server
|
|
74
|
+
from app.server import create_app
|
|
75
|
+
from google_ai_search.config import get_config
|
|
76
|
+
cfg = get_config()
|
|
77
|
+
cfg.port = args.port
|
|
78
|
+
cfg.debug = args.debug
|
|
79
|
+
flask_app = create_app(cfg)
|
|
80
|
+
print(f"🚀 Starting Grip AI Search server on http://{args.host}:{args.port}")
|
|
81
|
+
flask_app.run(host=args.host, port=args.port, debug=args.debug)
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
if args.command in ("search", "query"):
|
|
85
|
+
client = Grip(provider=args.provider)
|
|
86
|
+
if args.json:
|
|
87
|
+
res = client.query(args.query, session_id=args.session_id, provider=args.provider)
|
|
88
|
+
print(json.dumps(res.to_dict(), indent=2))
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
if args.no_stream:
|
|
92
|
+
res = client.query(args.query, session_id=args.session_id, provider=args.provider)
|
|
93
|
+
print(res.answer)
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
thought_printed = False
|
|
98
|
+
for chunk in client.stream(args.query, session_id=args.session_id, provider=args.provider):
|
|
99
|
+
if chunk.event == "thinking" and chunk.thinking and not thought_printed:
|
|
100
|
+
thought_printed = True
|
|
101
|
+
print(f"💭 [{chunk.thinking}]", flush=True)
|
|
102
|
+
elif chunk.event == "delta" and chunk.delta:
|
|
103
|
+
sys.stdout.write(chunk.delta)
|
|
104
|
+
sys.stdout.flush()
|
|
105
|
+
sys.stdout.write("\n")
|
|
106
|
+
return 0
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
109
|
+
return 1
|
|
110
|
+
|
|
111
|
+
if args.command in ("chat", "session"):
|
|
112
|
+
client = Grip(provider=args.provider)
|
|
113
|
+
session = client.create_session(title=args.title)
|
|
114
|
+
print(f"✨ Started Grip interactive session ({session.session_id}) [Provider: {args.provider}]")
|
|
115
|
+
print("Type your questions below. Type 'exit' or 'quit' to end.\n")
|
|
116
|
+
|
|
117
|
+
while True:
|
|
118
|
+
try:
|
|
119
|
+
query = input("\n[You] > ").strip()
|
|
120
|
+
if not query:
|
|
121
|
+
continue
|
|
122
|
+
if query.lower() in ("exit", "quit"):
|
|
123
|
+
print("Goodbye!")
|
|
124
|
+
break
|
|
125
|
+
print("\n[Grip AI] > ", end="", flush=True)
|
|
126
|
+
for chunk in client.stream(query, session_id=session.session_id, provider=args.provider):
|
|
127
|
+
if chunk.event == "delta" and chunk.delta:
|
|
128
|
+
sys.stdout.write(chunk.delta)
|
|
129
|
+
sys.stdout.flush()
|
|
130
|
+
sys.stdout.write("\n")
|
|
131
|
+
except (KeyboardInterrupt, EOFError):
|
|
132
|
+
print("\nSession ended.")
|
|
133
|
+
break
|
|
134
|
+
except Exception as exc:
|
|
135
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
if args.command == "rules":
|
|
139
|
+
client = Grip()
|
|
140
|
+
if args.identity:
|
|
141
|
+
client.rules.set_identity(args.identity)
|
|
142
|
+
print(f"✅ Set identity: {args.identity}")
|
|
143
|
+
if args.format:
|
|
144
|
+
client.rules.set_response_format(args.format)
|
|
145
|
+
print(f"✅ Set response format: {args.format}")
|
|
146
|
+
if args.add:
|
|
147
|
+
r = client.rules.add(args.add)
|
|
148
|
+
print(f"✅ Added rule: {r.name}")
|
|
149
|
+
if args.list or (not args.identity and not args.format and not args.add):
|
|
150
|
+
active_rules = client.rules.list(enabled_only=True)
|
|
151
|
+
print("\nActive System Rules:")
|
|
152
|
+
for rule in active_rules:
|
|
153
|
+
print(f"• [P{rule.priority}] {rule.name}: {rule.content}")
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
return 0
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
sys.exit(main())
|