rossum-agent 1.0.0rc0__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.
Files changed (67) hide show
  1. rossum_agent/__init__.py +9 -0
  2. rossum_agent/agent/__init__.py +32 -0
  3. rossum_agent/agent/core.py +932 -0
  4. rossum_agent/agent/memory.py +176 -0
  5. rossum_agent/agent/models.py +160 -0
  6. rossum_agent/agent/request_classifier.py +152 -0
  7. rossum_agent/agent/skills.py +132 -0
  8. rossum_agent/agent/types.py +5 -0
  9. rossum_agent/agent_logging.py +56 -0
  10. rossum_agent/api/__init__.py +1 -0
  11. rossum_agent/api/cli.py +51 -0
  12. rossum_agent/api/dependencies.py +190 -0
  13. rossum_agent/api/main.py +180 -0
  14. rossum_agent/api/models/__init__.py +1 -0
  15. rossum_agent/api/models/schemas.py +301 -0
  16. rossum_agent/api/routes/__init__.py +1 -0
  17. rossum_agent/api/routes/chats.py +95 -0
  18. rossum_agent/api/routes/files.py +113 -0
  19. rossum_agent/api/routes/health.py +44 -0
  20. rossum_agent/api/routes/messages.py +218 -0
  21. rossum_agent/api/services/__init__.py +1 -0
  22. rossum_agent/api/services/agent_service.py +451 -0
  23. rossum_agent/api/services/chat_service.py +197 -0
  24. rossum_agent/api/services/file_service.py +65 -0
  25. rossum_agent/assets/Primary_light_logo.png +0 -0
  26. rossum_agent/bedrock_client.py +64 -0
  27. rossum_agent/prompts/__init__.py +27 -0
  28. rossum_agent/prompts/base_prompt.py +80 -0
  29. rossum_agent/prompts/system_prompt.py +24 -0
  30. rossum_agent/py.typed +0 -0
  31. rossum_agent/redis_storage.py +482 -0
  32. rossum_agent/rossum_mcp_integration.py +123 -0
  33. rossum_agent/skills/hook-debugging.md +31 -0
  34. rossum_agent/skills/organization-setup.md +60 -0
  35. rossum_agent/skills/rossum-deployment.md +102 -0
  36. rossum_agent/skills/schema-patching.md +61 -0
  37. rossum_agent/skills/schema-pruning.md +23 -0
  38. rossum_agent/skills/ui-settings.md +45 -0
  39. rossum_agent/streamlit_app/__init__.py +1 -0
  40. rossum_agent/streamlit_app/app.py +646 -0
  41. rossum_agent/streamlit_app/beep_sound.py +36 -0
  42. rossum_agent/streamlit_app/cli.py +17 -0
  43. rossum_agent/streamlit_app/render_modules.py +123 -0
  44. rossum_agent/streamlit_app/response_formatting.py +305 -0
  45. rossum_agent/tools/__init__.py +214 -0
  46. rossum_agent/tools/core.py +173 -0
  47. rossum_agent/tools/deploy.py +404 -0
  48. rossum_agent/tools/dynamic_tools.py +365 -0
  49. rossum_agent/tools/file_tools.py +62 -0
  50. rossum_agent/tools/formula.py +187 -0
  51. rossum_agent/tools/skills.py +31 -0
  52. rossum_agent/tools/spawn_mcp.py +227 -0
  53. rossum_agent/tools/subagents/__init__.py +31 -0
  54. rossum_agent/tools/subagents/base.py +303 -0
  55. rossum_agent/tools/subagents/hook_debug.py +591 -0
  56. rossum_agent/tools/subagents/knowledge_base.py +305 -0
  57. rossum_agent/tools/subagents/mcp_helpers.py +47 -0
  58. rossum_agent/tools/subagents/schema_patching.py +471 -0
  59. rossum_agent/url_context.py +167 -0
  60. rossum_agent/user_detection.py +100 -0
  61. rossum_agent/utils.py +128 -0
  62. rossum_agent-1.0.0rc0.dist-info/METADATA +311 -0
  63. rossum_agent-1.0.0rc0.dist-info/RECORD +67 -0
  64. rossum_agent-1.0.0rc0.dist-info/WHEEL +5 -0
  65. rossum_agent-1.0.0rc0.dist-info/entry_points.txt +3 -0
  66. rossum_agent-1.0.0rc0.dist-info/licenses/LICENSE +21 -0
  67. rossum_agent-1.0.0rc0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,197 @@
1
+ """Chat service for managing chat sessions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime as dt
6
+ import logging
7
+ import secrets
8
+ from typing import TYPE_CHECKING
9
+
10
+ from rossum_agent.api.models.schemas import (
11
+ ChatDetail,
12
+ ChatListResponse,
13
+ ChatResponse,
14
+ ChatSummary,
15
+ FileInfo,
16
+ Message,
17
+ )
18
+ from rossum_agent.redis_storage import ChatData, ChatMetadata, RedisStorage
19
+
20
+ if TYPE_CHECKING:
21
+ from pathlib import Path
22
+ from typing import Any, Literal
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class ChatService:
28
+ """Service for managing chat sessions.
29
+
30
+ Wraps RedisStorage to provide chat CRUD operations with proper
31
+ data transformation to/from API schemas.
32
+ """
33
+
34
+ def __init__(self, redis_storage: RedisStorage | None = None) -> None:
35
+ self._storage = redis_storage or RedisStorage()
36
+
37
+ @property
38
+ def storage(self) -> RedisStorage:
39
+ """Get the underlying RedisStorage instance."""
40
+ return self._storage
41
+
42
+ def is_connected(self) -> bool:
43
+ """Check if Redis is connected."""
44
+ return self._storage.is_connected()
45
+
46
+ def create_chat(
47
+ self, user_id: str | None, mcp_mode: Literal["read-only", "read-write"] = "read-only"
48
+ ) -> ChatResponse:
49
+ """Create a new chat session.
50
+
51
+ Args:
52
+ user_id: User identifier for isolation.
53
+ mcp_mode: MCP mode for this chat session.
54
+
55
+ Returns:
56
+ ChatResponse with the new chat_id and created_at timestamp.
57
+ """
58
+ timestamp = dt.datetime.now(dt.UTC)
59
+ timestamp_str = timestamp.strftime("%Y%m%d%H%M%S")
60
+ unique_suffix = secrets.token_hex(4)
61
+ chat_id = f"chat_{timestamp_str}_{unique_suffix}"
62
+
63
+ initial_messages: list[dict[str, Any]] = []
64
+ metadata = ChatMetadata(mcp_mode=mcp_mode)
65
+ self._storage.save_chat(user_id, chat_id, initial_messages, metadata=metadata)
66
+
67
+ logger.info(f"Created chat {chat_id} for user {user_id or 'shared'} with mcp_mode={mcp_mode}")
68
+ return ChatResponse(chat_id=chat_id, created_at=timestamp)
69
+
70
+ def list_chats(self, user_id: str | None, limit: int = 50, offset: int = 0) -> ChatListResponse:
71
+ """List chat sessions for a user.
72
+
73
+ Args:
74
+ user_id: User identifier for isolation.
75
+ limit: Maximum number of chats to return.
76
+ offset: Pagination offset.
77
+
78
+ Returns:
79
+ ChatListResponse with paginated chat list.
80
+ """
81
+ all_chats = self._storage.list_all_chats(user_id)
82
+
83
+ paginated = all_chats[offset : offset + limit]
84
+ chats = [
85
+ ChatSummary(
86
+ chat_id=chat["chat_id"],
87
+ timestamp=chat["timestamp"],
88
+ message_count=chat["message_count"],
89
+ first_message=chat["first_message"],
90
+ preview=chat.get("preview"),
91
+ )
92
+ for chat in paginated
93
+ ]
94
+
95
+ return ChatListResponse(chats=chats, total=len(all_chats), limit=limit, offset=offset)
96
+
97
+ def get_chat(self, user_id: str | None, chat_id: str) -> ChatDetail | None:
98
+ """Get detailed chat information.
99
+
100
+ Args:
101
+ user_id: User identifier for isolation.
102
+ chat_id: Chat session identifier.
103
+
104
+ Returns:
105
+ ChatDetail with messages and files, or None if not found.
106
+ """
107
+ if (chat_data := self._storage.load_chat(user_id, chat_id)) is None:
108
+ return None
109
+
110
+ messages = []
111
+ for msg in chat_data.messages:
112
+ msg_type = msg.get("type")
113
+ role = msg.get("role")
114
+
115
+ if msg_type == "task_step":
116
+ task_content = msg.get("task", "")
117
+ messages.append(Message(role="user", content=task_content))
118
+ elif msg_type == "memory_step":
119
+ text = msg.get("text")
120
+ if text:
121
+ messages.append(Message(role="assistant", content=text))
122
+ elif role in ("user", "assistant"):
123
+ messages.append(Message(role=role, content=msg.get("content", "")))
124
+
125
+ files_data = self._storage.list_files(chat_id)
126
+ files = [FileInfo(filename=f["filename"], size=f["size"], timestamp=f["timestamp"]) for f in files_data]
127
+
128
+ timestamp_str = chat_id.split("_")[1]
129
+ created_at = dt.datetime.strptime(timestamp_str, "%Y%m%d%H%M%S").replace(tzinfo=dt.UTC)
130
+
131
+ return ChatDetail(chat_id=chat_id, messages=messages, created_at=created_at, files=files)
132
+
133
+ def delete_chat(self, user_id: str | None, chat_id: str) -> bool:
134
+ """Delete a chat session.
135
+
136
+ Args:
137
+ user_id: User identifier for isolation.
138
+ chat_id: Chat session identifier.
139
+
140
+ Returns:
141
+ True if deleted, False otherwise.
142
+ """
143
+ self._storage.delete_all_files(chat_id)
144
+ deleted = self._storage.delete_chat(user_id, chat_id)
145
+ logger.info(f"Deleted chat {chat_id} for user {user_id or 'shared'}: {deleted}")
146
+ return deleted
147
+
148
+ def chat_exists(self, user_id: str | None, chat_id: str) -> bool:
149
+ """Check if a chat exists.
150
+
151
+ Args:
152
+ user_id: User identifier for isolation.
153
+ chat_id: Chat session identifier.
154
+ """
155
+ return self._storage.chat_exists(user_id, chat_id)
156
+
157
+ def get_messages(self, user_id: str | None, chat_id: str) -> list[dict[str, Any]] | None:
158
+ """Get raw messages for a chat session.
159
+
160
+ Args:
161
+ user_id: User identifier for isolation.
162
+ chat_id: Chat session identifier.
163
+ """
164
+ if (chat_data := self._storage.load_chat(user_id, chat_id)) is None:
165
+ return None
166
+ return chat_data.messages
167
+
168
+ def get_chat_data(self, user_id: str | None, chat_id: str) -> ChatData | None:
169
+ """Get full chat data including metadata.
170
+
171
+ Args:
172
+ user_id: User identifier for isolation.
173
+ chat_id: Chat session identifier.
174
+ """
175
+ return self._storage.load_chat(user_id, chat_id)
176
+
177
+ def save_messages(
178
+ self,
179
+ user_id: str | None,
180
+ chat_id: str,
181
+ messages: list[dict[str, Any]],
182
+ output_dir: Path | None = None,
183
+ metadata: ChatMetadata | None = None,
184
+ ) -> bool:
185
+ """Save messages to a chat session.
186
+
187
+ Args:
188
+ user_id: User identifier for isolation.
189
+ chat_id: Chat session identifier.
190
+ messages: List of message dicts to save.
191
+ output_dir: Optional output directory path.
192
+ metadata: Optional chat metadata with token counts and step info.
193
+
194
+ Returns:
195
+ True if saved successfully, False otherwise.
196
+ """
197
+ return self._storage.save_chat(user_id, chat_id, messages, output_dir, metadata)
@@ -0,0 +1,65 @@
1
+ """File service for managing chat session files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import mimetypes
6
+
7
+ from rossum_agent.api.models.schemas import FileInfo
8
+ from rossum_agent.redis_storage import RedisStorage
9
+
10
+
11
+ class FileService:
12
+ """Service for managing files associated with chat sessions.
13
+
14
+ Wraps RedisStorage file operations with proper validation and
15
+ data transformation to/from API schemas.
16
+ """
17
+
18
+ def __init__(self, redis_storage: RedisStorage | None = None) -> None:
19
+ self._storage = redis_storage or RedisStorage()
20
+
21
+ @property
22
+ def storage(self) -> RedisStorage:
23
+ """Get the underlying RedisStorage instance."""
24
+ return self._storage
25
+
26
+ def list_files(self, chat_id: str) -> list[FileInfo]:
27
+ """List all files for a chat session.
28
+
29
+ Args:
30
+ chat_id: Chat session identifier.
31
+
32
+ Returns:
33
+ List of FileInfo objects with file metadata.
34
+ """
35
+ files_data = self._storage.list_files(chat_id)
36
+ return [
37
+ FileInfo(
38
+ filename=f["filename"],
39
+ size=f["size"],
40
+ timestamp=f["timestamp"],
41
+ mime_type=self._guess_mime_type(f["filename"]),
42
+ )
43
+ for f in files_data
44
+ ]
45
+
46
+ def get_file(self, chat_id: str, filename: str) -> tuple[bytes, str] | None:
47
+ """Get file content and MIME type.
48
+
49
+ Args:
50
+ chat_id: Chat session identifier.
51
+ filename: Name of the file.
52
+
53
+ Returns:
54
+ Tuple of (content bytes, mime_type) or None if not found.
55
+ """
56
+ if (content := self._storage.load_file(chat_id, filename)) is None:
57
+ return None
58
+
59
+ mime_type = self._guess_mime_type(filename)
60
+ return content, mime_type
61
+
62
+ def _guess_mime_type(self, filename: str) -> str:
63
+ """Guess MIME type from filename."""
64
+ mime_type, _ = mimetypes.guess_type(filename)
65
+ return mime_type or "application/octet-stream"
@@ -0,0 +1,64 @@
1
+ """AWS Bedrock client module for direct communication with Anthropic models via boto3.Session."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ import boto3
8
+ from anthropic import AnthropicBedrock
9
+
10
+ OPUS_MODEL_ID = "eu.anthropic.claude-opus-4-5-20251101-v1:0"
11
+ HAIKU_MODEL_ID = "eu.anthropic.claude-haiku-4-5-20251001-v1:0"
12
+
13
+
14
+ def create_bedrock_client(
15
+ aws_region: str | None = None, aws_profile: str | None = None, session: boto3.Session | None = None
16
+ ) -> AnthropicBedrock:
17
+ """Create AnthropicBedrock client using boto3.Session credentials.
18
+
19
+ This function supports multiple credential sources:
20
+ 1. Explicit boto3.Session passed as argument
21
+ 2. AWS profile name (uses named profile from ~/.aws/credentials)
22
+ 3. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN)
23
+ 4. IAM role credentials (when running on AWS infrastructure)
24
+
25
+ Args:
26
+ aws_region: AWS region for Bedrock service. Defaults to AWS_REGION env var
27
+ or 'eu-central-1'.
28
+ aws_profile: AWS profile name from ~/.aws/credentials. Overridden if session
29
+ is provided.
30
+ session: Pre-configured boto3.Session. If provided, aws_profile is ignored.
31
+
32
+ Returns:
33
+ Configured AnthropicBedrock client ready for API calls.
34
+ """
35
+ region = aws_region or os.environ.get("AWS_REGION")
36
+
37
+ if session is None:
38
+ session = boto3.Session(profile_name=aws_profile, region_name=region)
39
+
40
+ if (credentials := session.get_credentials()) is None:
41
+ raise RuntimeError(
42
+ "No AWS credentials found. Please configure AWS credentials via environment "
43
+ "variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), AWS profile, or IAM role."
44
+ )
45
+
46
+ frozen_credentials = credentials.get_frozen_credentials()
47
+
48
+ return AnthropicBedrock(
49
+ aws_access_key=frozen_credentials.access_key,
50
+ aws_secret_key=frozen_credentials.secret_key,
51
+ aws_session_token=frozen_credentials.token,
52
+ aws_region=session.region_name or region,
53
+ max_retries=5,
54
+ )
55
+
56
+
57
+ def get_model_id() -> str:
58
+ """Return AWS_BEDROCK_MODEL_ARN if set, otherwise default Opus model."""
59
+ return os.environ.get("AWS_BEDROCK_MODEL_ARN", OPUS_MODEL_ID)
60
+
61
+
62
+ def get_small_model_id() -> str:
63
+ """Return AWS_BEDROCK_MODEL_ARN_SMALL if set, otherwise default Haiku model."""
64
+ return os.environ.get("AWS_BEDROCK_MODEL_ARN_SMALL", HAIKU_MODEL_ID)
@@ -0,0 +1,27 @@
1
+ """Prompt templates for the Rossum Agent.
2
+
3
+ This package contains shared prompt content and specialized prompt builders
4
+ for different agent paradigms (tool-use, code-execution).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from rossum_agent.prompts.base_prompt import (
10
+ CONFIGURATION_WORKFLOWS,
11
+ CRITICAL_REQUIREMENTS,
12
+ DOCUMENTATION_WORKFLOWS,
13
+ OUTPUT_FORMATTING,
14
+ ROSSUM_EXPERT_INTRO,
15
+ get_shared_prompt_sections,
16
+ )
17
+ from rossum_agent.prompts.system_prompt import get_system_prompt
18
+
19
+ __all__ = [
20
+ "CONFIGURATION_WORKFLOWS",
21
+ "CRITICAL_REQUIREMENTS",
22
+ "DOCUMENTATION_WORKFLOWS",
23
+ "OUTPUT_FORMATTING",
24
+ "ROSSUM_EXPERT_INTRO",
25
+ "get_shared_prompt_sections",
26
+ "get_system_prompt",
27
+ ]
@@ -0,0 +1,80 @@
1
+ """Shared prompt content for the Rossum Agent.
2
+
3
+ Optimized for Opus 4.5: Goals + constraints, not procedures.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ ROSSUM_EXPERT_INTRO = """You are an expert Rossum platform specialist. Help users understand, document, debug, and configure document processing workflows.
9
+
10
+ **CRITICAL - Use `search_knowledge_base` before**:
11
+ - Explaining ANY extension/hook behavior (except simple function hooks you can read directly)
12
+ - Debugging issues - knowledge base contains known issues and solutions
13
+ - Configuring extensions - knowledge base has required settings and examples
14
+
15
+ **Skills** (load FIRST when relevant):
16
+ - `load_skill("rossum-deployment")` → sandbox, deploy, cross-org, migrate
17
+ - `load_skill("hook-debugging")` → debug/fix function hooks
18
+ - `load_skill("organization-setup")` → new customer onboarding, queue templates
19
+ - `load_skill("schema-patching")` → modify schemas, add/remove fields, formulas
20
+ - `load_skill("schema-pruning")` → bulk remove unwanted fields from schema
21
+ - `load_skill("ui-settings")` → update queue UI settings, annotation list columns
22
+
23
+ **MCP Tools** (pre-loaded based on request keywords, or load manually):
24
+ - `load_tool_category(["queues", "schemas"])` to load multiple categories at once
25
+ - Categories: annotations, queues, schemas, engines, hooks, email_templates, document_relations, relations, rules, users, workspaces"""
26
+
27
+ CRITICAL_REQUIREMENTS = """
28
+ # Domain Knowledge
29
+
30
+ **Schema**: sections → datapoints | multivalues → tuples (tables). Datapoint fields: `id`, `label`, `type`, `is_formula`, `formula`, `is_reasoning`, `prompt`, `score_threshold`.
31
+
32
+ **API constraints**:
33
+ - IDs are integers: `queue_id=12345` not `"12345"`
34
+ - `score_threshold` cannot be null (default `0.8`) - API rejects null values
35
+ - Annotation updates use numeric `id`, not `schema_id` string
36
+
37
+ **Engine training**: Inbox queues cannot train classification engines - they contain unsplit documents without `document_type`. Only typed documents in training_queues contribute."""
38
+
39
+ DOCUMENTATION_WORKFLOWS = """
40
+ # Visual Documentation
41
+
42
+ Use Mermaid diagrams for workflows. Apply this styling:
43
+
44
+ ```mermaid
45
+ graph TD
46
+ Start[Document Upload]
47
+ Start --> Event1["annotation_status<br/>2 hooks"]
48
+ style Event1 fill:#E8F4F8,stroke:#4A90E2,stroke-width:2px
49
+ Event1 --> Hook1["Validation Hook<br/>[function]"]
50
+ style Hook1 fill:#4A90E2,stroke:#2E5C8A,color:#fff
51
+ Event1 --> End[Complete]
52
+
53
+ click Event1 "#annotation_status"
54
+ click Hook1 "#validation_hook"
55
+ ```
56
+
57
+ Event nodes: light blue (`#E8F4F8`). Hook nodes: darker blue (`#4A90E2`, white text). Add clickable anchors."""
58
+
59
+ CONFIGURATION_WORKFLOWS = """
60
+ # Configuration
61
+
62
+ **Sandbox deployments**: Load `rossum-deployment` skill first. Execute autonomously through diff, then wait for user approval before deploying.
63
+
64
+ **Direct operations**: For single-org changes without sandbox, use MCP tools directly.
65
+
66
+ **Hooks**: Prefer `list_hook_templates` + `create_hook_from_template` over custom code."""
67
+
68
+ OUTPUT_FORMATTING = """
69
+ # Output
70
+
71
+ Match response length to question complexity. Be concise for simple questions.
72
+
73
+ For documentation: use Mermaid diagrams, cross-reference with anchors, explain business logic in prose (not JSON dumps), flag issues with `⚠️ SUSPICIOUS:`."""
74
+
75
+
76
+ def get_shared_prompt_sections() -> str:
77
+ """Get all shared prompt sections combined."""
78
+ return "\n\n---\n".join(
79
+ [CRITICAL_REQUIREMENTS, DOCUMENTATION_WORKFLOWS, CONFIGURATION_WORKFLOWS, OUTPUT_FORMATTING]
80
+ )
@@ -0,0 +1,24 @@
1
+ """System prompt for the RossumAgent using Anthropic's tool use API.
2
+
3
+ This module provides the system prompt that defines the agent's behavior,
4
+ capabilities, and guidelines for interacting with the Rossum platform.
5
+ The prompt is adapted for use with Anthropic's native tool use API.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from rossum_agent.prompts.base_prompt import ROSSUM_EXPERT_INTRO, get_shared_prompt_sections
11
+
12
+ SYSTEM_PROMPT = f"""{ROSSUM_EXPERT_INTRO}
13
+
14
+ ---
15
+ {get_shared_prompt_sections()}"""
16
+
17
+
18
+ def get_system_prompt() -> str:
19
+ """Get the system prompt for the RossumAgent.
20
+
21
+ Returns:
22
+ The system prompt string defining agent behavior.
23
+ """
24
+ return SYSTEM_PROMPT
rossum_agent/py.typed ADDED
File without changes