dobby-sdk 0.2.3__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.
Files changed (67) hide show
  1. dobby_sdk-0.2.3/.agent/rules/backend.md +395 -0
  2. dobby_sdk-0.2.3/.agent/rules/coding-standards.md +433 -0
  3. dobby_sdk-0.2.3/.agent/rules/commenting.md +95 -0
  4. dobby_sdk-0.2.3/.agent/rules/ultracite.md +289 -0
  5. dobby_sdk-0.2.3/.gitignore +14 -0
  6. dobby_sdk-0.2.3/.python-version +1 -0
  7. dobby_sdk-0.2.3/AGENTS.md +1 -0
  8. dobby_sdk-0.2.3/CLAUDE.md +1 -0
  9. dobby_sdk-0.2.3/LICENSE +21 -0
  10. dobby_sdk-0.2.3/PKG-INFO +89 -0
  11. dobby_sdk-0.2.3/README.md +56 -0
  12. dobby_sdk-0.2.3/TODO.md +11 -0
  13. dobby_sdk-0.2.3/dobby/__init__.py +18 -0
  14. dobby_sdk-0.2.3/dobby/_logging.py +35 -0
  15. dobby_sdk-0.2.3/dobby/common_tools/__init__.py +9 -0
  16. dobby_sdk-0.2.3/dobby/common_tools/tavily.py +104 -0
  17. dobby_sdk-0.2.3/dobby/exceptions/__init__.py +3 -0
  18. dobby_sdk-0.2.3/dobby/exceptions/tool.py +29 -0
  19. dobby_sdk-0.2.3/dobby/executor.py +344 -0
  20. dobby_sdk-0.2.3/dobby/providers/__init__.py +6 -0
  21. dobby_sdk-0.2.3/dobby/providers/anthropic/.gitkeep +0 -0
  22. dobby_sdk-0.2.3/dobby/providers/openai/__init__.py +6 -0
  23. dobby_sdk-0.2.3/dobby/providers/openai/completions.py +461 -0
  24. dobby_sdk-0.2.3/dobby/providers/openai/converters.py +78 -0
  25. dobby_sdk-0.2.3/dobby/providers/openai/provider.py +474 -0
  26. dobby_sdk-0.2.3/dobby/py.typed +0 -0
  27. dobby_sdk-0.2.3/dobby/retriever/__init__.py +1 -0
  28. dobby_sdk-0.2.3/dobby/retriever/base.py +45 -0
  29. dobby_sdk-0.2.3/dobby/retriever/hybrid_retriever.py +121 -0
  30. dobby_sdk-0.2.3/dobby/tools/__init__.py +6 -0
  31. dobby_sdk-0.2.3/dobby/tools/base.py +136 -0
  32. dobby_sdk-0.2.3/dobby/tools/injected.py +18 -0
  33. dobby_sdk-0.2.3/dobby/tools/schema_utils.py +414 -0
  34. dobby_sdk-0.2.3/dobby/tools/tool.py +141 -0
  35. dobby_sdk-0.2.3/dobby/types/__init__.py +52 -0
  36. dobby_sdk-0.2.3/dobby/types/document_part.py +54 -0
  37. dobby_sdk-0.2.3/dobby/types/image_part.py +34 -0
  38. dobby_sdk-0.2.3/dobby/types/message.py +49 -0
  39. dobby_sdk-0.2.3/dobby/types/reasoning_part.py +11 -0
  40. dobby_sdk-0.2.3/dobby/types/stream_events.py +108 -0
  41. dobby_sdk-0.2.3/dobby/types/text_part.py +11 -0
  42. dobby_sdk-0.2.3/dobby/types/tool_events.py +31 -0
  43. dobby_sdk-0.2.3/dobby/types/tool_part.py +15 -0
  44. dobby_sdk-0.2.3/dobby/types/tool_result_part.py +22 -0
  45. dobby_sdk-0.2.3/dobby/types/usage.py +25 -0
  46. dobby_sdk-0.2.3/dobby/vector_store/__init__.py +3 -0
  47. dobby_sdk-0.2.3/dobby/vector_store/azure_ai_search.py +423 -0
  48. dobby_sdk-0.2.3/dobby/vector_store/base.py +131 -0
  49. dobby_sdk-0.2.3/docs/executor.md +196 -0
  50. dobby_sdk-0.2.3/docs/getting-started.md +109 -0
  51. dobby_sdk-0.2.3/docs/providers/index.md +51 -0
  52. dobby_sdk-0.2.3/docs/providers/openai.md +128 -0
  53. dobby_sdk-0.2.3/docs/retrievers/hybrid-retriever.md +108 -0
  54. dobby_sdk-0.2.3/docs/tools/built-in-tools.md +77 -0
  55. dobby_sdk-0.2.3/docs/tools/creating-tools.md +199 -0
  56. dobby_sdk-0.2.3/docs/types/messages.md +209 -0
  57. dobby_sdk-0.2.3/docs/vector-stores/azure-ai-search.md +128 -0
  58. dobby_sdk-0.2.3/docs/vector-stores/index.md +64 -0
  59. dobby_sdk-0.2.3/examples/README.md +47 -0
  60. dobby_sdk-0.2.3/examples/multi_agent/README.md +40 -0
  61. dobby_sdk-0.2.3/examples/multi_agent/company_research_tool.py +161 -0
  62. dobby_sdk-0.2.3/examples/multi_agent/jd_analyzer.py +293 -0
  63. dobby_sdk-0.2.3/examples/web_search_agent.py +166 -0
  64. dobby_sdk-0.2.3/pyproject.toml +142 -0
  65. dobby_sdk-0.2.3/test.py +26 -0
  66. dobby_sdk-0.2.3/test_structured_output.py +333 -0
  67. dobby_sdk-0.2.3/uv.lock +963 -0
@@ -0,0 +1,395 @@
1
+ ---
2
+ trigger: model_decision
3
+ description: Python Backend Coding Standards - FastAPI + SQLAlchemy
4
+ globs: "**/*.py"
5
+ ---
6
+
7
+ # Python Backend Coding Standards
8
+
9
+ FastAPI backend with SQLAlchemy 2.0, Pydantic, and Ruff linter. Follow these conventions for all Python code.
10
+
11
+ ## Key Principles
12
+ - Python 3.12+ with type hints everywhere
13
+ - Google-style docstrings
14
+ - Ruff for linting and formatting
15
+ - Repository pattern for database access
16
+ - Service classes for business logic
17
+
18
+ ---
19
+
20
+ ## Technology Stack
21
+
22
+ - Python 3.12+
23
+ - FastAPI (latest stable)
24
+ - SQLAlchemy 2.0 with async support
25
+ - Pydantic and pydantic-settings for validation/config
26
+ - Alembic for migrations
27
+ - Ruff for linting/formatting
28
+ - uv for package management
29
+ - Loguru for logging
30
+ - Argon2 for password hashing
31
+ - python-jose for JWT tokens
32
+ - asyncpg for PostgreSQL async driver
33
+
34
+ ---
35
+
36
+ ## Folder Structure
37
+
38
+ server/
39
+ ├── main.py # FastAPI app entry point with lifespan
40
+ ├── pyproject.toml # Project dependencies (uv)
41
+ ├── ruff.toml # Linter configuration
42
+ ├── alembic/ # Database migrations
43
+ ├── src/
44
+ │ ├── api/ # API layer (routes, schemas, services)
45
+ │ │ ├── __init__.py # Router aggregation
46
+ │ │ └── {feature}/ # Feature module
47
+ │ │ ├── __init__.py
48
+ │ │ ├── router.py # FastAPI routes
49
+ │ │ ├── schemas.py # Pydantic models
50
+ │ │ ├── service.py # Business logic
51
+ │ │ └── dependencies.py # Route dependencies (optional)
52
+ │ ├── core/ # Application core
53
+ │ │ ├── config.py # Settings via pydantic-settings
54
+ │ │ ├── exceptions.py # Custom HTTP exceptions
55
+ │ │ ├── security.py # JWT and password utilities
56
+ │ │ └── logger.py # Loguru configuration
57
+ │ └── lib/ # Shared libraries
58
+ │ ├── db/ # Database layer
59
+ │ │ ├── base.py # SQLAlchemy Base and mixins
60
+ │ │ ├── session.py # Async session management
61
+ │ │ ├── models/ # SQLAlchemy models
62
+ │ │ ├── repositories/ # Data access layer
63
+ │ │ ├── enums.py # Database enums
64
+ │ │ └── types.py # Custom types (UUID7)
65
+ │ └── {domain}/ # Domain-specific libraries
66
+ └── tests/ # Test files
67
+
68
+ ---
69
+
70
+ ## File Conventions
71
+
72
+ ### Module Docstrings
73
+ Every Python file starts with a docstring describing its purpose:
74
+
75
+ """User model for authentication and ownership.
76
+
77
+ Users can be regular (password-authenticated) or guests.
78
+ Each user owns chats and their messages.
79
+ """
80
+
81
+ ### Router Files
82
+ Routes list endpoints in the module docstring:
83
+
84
+ """Chat streaming API routes.
85
+
86
+ Endpoints:
87
+ - POST /chat/stream - Stream chat response from LLM via SSE
88
+ """
89
+
90
+ from fastapi import APIRouter, Depends
91
+
92
+ router = APIRouter(prefix="/chat", tags=["chat-stream"])
93
+
94
+ @router.post("/stream")
95
+ async def chat_stream(
96
+ request: ChatStreamRequest,
97
+ user: User = Depends(require_auth),
98
+ db: AsyncSession = Depends(get_db),
99
+ ):
100
+ """Stream chat response from LLM.
101
+
102
+ Delegates to ChatService for:
103
+ 1. Save user message to DB
104
+ 2. Get chat history
105
+ 3. Stream LLM response
106
+ 4. Save assistant message to DB
107
+ """
108
+ service = ChatService(db)
109
+ return StreamingResponse(...)
110
+
111
+ ---
112
+
113
+ ## Docstring Format (Google Style)
114
+
115
+ Use Google-style docstrings for all functions and classes:
116
+
117
+ def process_data(input_data: str, max_length: int = 100) -> dict[str, Any]:
118
+ """Process input data and return results.
119
+
120
+ Args:
121
+ input_data: Raw input string to process.
122
+ max_length: Maximum allowed length (default 100).
123
+
124
+ Returns:
125
+ Dictionary containing processed results with keys:
126
+ - status: Processing status
127
+ - data: Processed content
128
+
129
+ Raises:
130
+ ValueError: If input_data is empty.
131
+ """
132
+
133
+ For classes, document attributes in the class docstring:
134
+
135
+ class User(Base, TimestampMixin):
136
+ """User account model.
137
+
138
+ Attributes:
139
+ id: Unique UUID7 identifier
140
+ email: User's email address (unique, indexed)
141
+ password: Bcrypt hashed password (nullable for guest users)
142
+ is_active: Whether the account is enabled
143
+ user_type: Account type (regular or guest)
144
+ chats: User's chat sessions (relationship)
145
+ """
146
+
147
+ ---
148
+
149
+ ## Pydantic Schemas
150
+
151
+ Use Field with alias for camelCase JSON serialization:
152
+
153
+ from pydantic import BaseModel, Field
154
+
155
+ class ChatStreamRequest(BaseModel):
156
+ """Request body for chat streaming endpoint."""
157
+
158
+ chat_id: UUID = Field(alias="chatId")
159
+ message: ChatMessage = Field(alias="message")
160
+ chat_model_config: ChatModelConfig = Field(alias="chatModelConfig")
161
+
162
+
163
+ class MessagePart(BaseModel):
164
+ """A part of a message - text or file attachment."""
165
+
166
+ type: Literal["text", "file"]
167
+ text: str | None = None
168
+ url: str | None = None
169
+
170
+ ---
171
+
172
+ ## SQLAlchemy Models
173
+
174
+ Use SQLAlchemy 2.0 style with mapped_column and Mapped type hints:
175
+
176
+ from sqlalchemy import String, Boolean
177
+ from sqlalchemy.dialects.postgresql import UUID
178
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
179
+
180
+ class User(Base, TimestampMixin):
181
+ __tablename__ = "users"
182
+
183
+ id: Mapped[uuid.UUID] = mapped_column(
184
+ UUID(as_uuid=True),
185
+ primary_key=True,
186
+ default=generate_uuid7,
187
+ )
188
+ email: Mapped[str] = mapped_column(
189
+ String(64),
190
+ unique=True,
191
+ nullable=False,
192
+ index=True,
193
+ )
194
+ is_active: Mapped[bool] = mapped_column(
195
+ Boolean,
196
+ default=True,
197
+ nullable=False,
198
+ )
199
+
200
+ # Relationships
201
+ chats: Mapped[list["Chat"]] = relationship(
202
+ back_populates="user",
203
+ cascade="all, delete-orphan",
204
+ )
205
+
206
+ def __repr__(self) -> str:
207
+ return f"<User id={self.id} email={self.email}>"
208
+
209
+ ---
210
+
211
+ ## Repository Pattern
212
+
213
+ Database access through repository classes with static methods:
214
+
215
+ class MessageRepository:
216
+ """Repository for message persistence."""
217
+
218
+ @staticmethod
219
+ async def get_by_chat_id(db: AsyncSession, chat_id: UUID) -> list[Message]:
220
+ """Get all messages for a chat.
221
+
222
+ Args:
223
+ db: Async database session.
224
+ chat_id: Chat UUID to filter by.
225
+
226
+ Returns:
227
+ List of messages ordered by creation time.
228
+ """
229
+ result = await db.execute(
230
+ select(Message)
231
+ .where(Message.chat_id == chat_id)
232
+ .order_by(Message.created_at)
233
+ )
234
+ return list(result.scalars().all())
235
+
236
+ @staticmethod
237
+ async def save(
238
+ db: AsyncSession,
239
+ chat_id: UUID,
240
+ parent_id: UUID,
241
+ role: str,
242
+ parts: list[dict],
243
+ ) -> Message:
244
+ """Save a new message."""
245
+ message = Message(
246
+ chat_id=chat_id,
247
+ parent_msg_id=parent_id,
248
+ role=role,
249
+ parts=parts,
250
+ )
251
+ db.add(message)
252
+ await db.commit()
253
+ await db.refresh(message)
254
+ return message
255
+
256
+ ---
257
+
258
+ ## Service Classes
259
+
260
+ Business logic in service classes initialized with database session:
261
+
262
+ class ChatService:
263
+ """Chat business logic with agentic tool execution."""
264
+
265
+ def __init__(self, db: AsyncSession):
266
+ """Initialize chat service.
267
+
268
+ Args:
269
+ db: Async database session.
270
+ """
271
+ self.db = db
272
+
273
+ async def stream_response(
274
+ self,
275
+ chat_id: UUID,
276
+ user_id: UUID,
277
+ user_message: ChatMessage,
278
+ model_config: ChatModelConfig,
279
+ ) -> AsyncIterator[str]:
280
+ """Process user message with agentic tool execution loop.
281
+
282
+ Args:
283
+ chat_id: Chat UUID.
284
+ user_id: User UUID.
285
+ user_message: User's chat message.
286
+ model_config: Model configuration.
287
+
288
+ Yields:
289
+ SSE formatted event strings.
290
+ """
291
+ ...
292
+
293
+ ---
294
+
295
+ ## Custom Exceptions
296
+
297
+ HTTP exceptions with consistent structure:
298
+
299
+ from fastapi import HTTPException, status
300
+
301
+ class AuthException(HTTPException):
302
+ """Base exception for authentication errors."""
303
+
304
+ def __init__(
305
+ self,
306
+ status_code: int = status.HTTP_401_UNAUTHORIZED,
307
+ detail: str = "Authentication failed",
308
+ headers: dict[str, str] | None = None,
309
+ ) -> None:
310
+ if headers is None:
311
+ headers = {"WWW-Authenticate": "Bearer"}
312
+ super().__init__(status_code=status_code, detail=detail, headers=headers)
313
+
314
+
315
+ class InvalidCredentialsException(AuthException):
316
+ """Raised when credentials are invalid."""
317
+
318
+ def __init__(self) -> None:
319
+ super().__init__(
320
+ status_code=status.HTTP_401_UNAUTHORIZED,
321
+ detail="Invalid email or password",
322
+ )
323
+
324
+ ---
325
+
326
+ ## Configuration
327
+
328
+ Use pydantic-settings for environment configuration:
329
+
330
+ from pydantic import Field
331
+ from pydantic_settings import BaseSettings, SettingsConfigDict
332
+
333
+ class Settings(BaseSettings):
334
+ """Application settings loaded from environment variables."""
335
+
336
+ model_config = SettingsConfigDict(
337
+ env_file=".env",
338
+ env_file_encoding="utf-8",
339
+ case_sensitive=False,
340
+ extra="ignore",
341
+ )
342
+
343
+ environment: Literal["development", "staging", "production"] = Field(
344
+ default="development",
345
+ description="Environment name",
346
+ )
347
+ debug: bool = False
348
+ database_url: str = "postgresql+asyncpg://localhost:5432/app"
349
+
350
+ settings = Settings()
351
+
352
+ ---
353
+
354
+ ## Type Hints
355
+
356
+ - Use modern Python 3.12+ syntax (list instead of List, dict instead of Dict)
357
+ - Use union syntax with pipe: str | None instead of Optional[str]
358
+ - Use Literal for fixed string values
359
+ - Use TYPE_CHECKING for circular import resolution
360
+
361
+ from typing import TYPE_CHECKING, Literal
362
+ from collections.abc import AsyncIterator
363
+
364
+ if TYPE_CHECKING:
365
+ from .chat import Chat
366
+
367
+ def process(data: str | None = None) -> dict[str, Any]:
368
+ ...
369
+
370
+ async def stream() -> AsyncIterator[str]:
371
+ ...
372
+
373
+ ---
374
+
375
+ ## Commands
376
+
377
+ uv sync # Install dependencies
378
+ uv run ruff check # Check linting
379
+ uv run ruff format # Format code
380
+ uv run alembic upgrade head # Run migrations
381
+ uv add - uv remove # to manage dependencies
382
+ ---
383
+
384
+ ## Checklist for New Features
385
+
386
+ - Create feature folder in src/api/{feature}/
387
+ - Add router.py with endpoint docstrings
388
+ - Add schemas.py with Pydantic models (camelCase aliases)
389
+ - Add service.py for business logic
390
+ - Add to src/api/__init__.py router aggregation
391
+ - Create models in src/lib/db/models/ if needed
392
+ - Create repository in src/lib/db/repositories/ if needed
393
+ - Add Alembic migration if schema changes
394
+ - Use Google-style docstrings with Args/Returns
395
+ - Add type hints to all functions