crewplus 0.2.97__tar.gz → 0.2.99__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 (46) hide show
  1. {crewplus-0.2.97 → crewplus-0.2.99}/PKG-INFO +2 -1
  2. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/__init__.py +15 -1
  3. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/azure_chat_model.py +24 -7
  4. crewplus-0.2.99/crewplus/services/capabilities/__init__.py +19 -0
  5. crewplus-0.2.99/crewplus/services/capabilities/base.py +179 -0
  6. crewplus-0.2.99/crewplus/services/capabilities/bash.py +210 -0
  7. crewplus-0.2.99/crewplus/services/capabilities/registry.py +225 -0
  8. crewplus-0.2.99/crewplus/services/capabilities/web_search.py +225 -0
  9. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/gemini_chat_model.py +5 -4
  10. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/model_load_balancer.py +58 -0
  11. crewplus-0.2.99/crewplus/services/oai_claude_chat_model.py +473 -0
  12. {crewplus-0.2.97 → crewplus-0.2.99}/pyproject.toml +2 -1
  13. crewplus-0.2.99/tests/test_bash_tools.py +532 -0
  14. crewplus-0.2.99/tests/test_claude_api.py +210 -0
  15. crewplus-0.2.99/tests/test_claude_langchain_openai.py +611 -0
  16. crewplus-0.2.99/tests/test_oai_claude_chat_model.py +549 -0
  17. {crewplus-0.2.97 → crewplus-0.2.99}/LICENSE +0 -0
  18. {crewplus-0.2.97 → crewplus-0.2.99}/README.md +0 -0
  19. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/__init__.py +0 -0
  20. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/callbacks/__init__.py +0 -0
  21. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/callbacks/async_langfuse_handler.py +0 -0
  22. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/claude_chat_model.py +0 -0
  23. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/feedback.md +0 -0
  24. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/feedback_manager.py +0 -0
  25. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/init_services.py +0 -0
  26. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/schemas/feedback.py +0 -0
  27. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/tracing_manager.py +0 -0
  28. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/__init__.py +0 -0
  29. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/schema_action.py +0 -0
  30. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/schema_document_updater.py +0 -0
  31. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/tracing_util.py +0 -0
  32. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/__init__.py +0 -0
  33. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/milvus_schema_manager.py +0 -0
  34. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/schema_milvus.py +0 -0
  35. {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/vdb_service.py +0 -0
  36. {crewplus-0.2.97 → crewplus-0.2.99}/docs/GeminiChatModel.md +0 -0
  37. {crewplus-0.2.97 → crewplus-0.2.99}/docs/ModelLoadBalancer.md +0 -0
  38. {crewplus-0.2.97 → crewplus-0.2.99}/docs/VDBService.md +0 -0
  39. {crewplus-0.2.97 → crewplus-0.2.99}/docs/index.md +0 -0
  40. {crewplus-0.2.97 → crewplus-0.2.99}/tests/README.md +0 -0
  41. {crewplus-0.2.97 → crewplus-0.2.99}/tests/conftest.py +0 -0
  42. {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_claude_custom_endpoint_integration.py +0 -0
  43. {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_claude_taskflow_schema.py +0 -0
  44. {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_custom_claude_endpoint.py +0 -0
  45. {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_gemini3_pro_preview.py +0 -0
  46. {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_gemini_bind_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crewplus
3
- Version: 0.2.97
3
+ Version: 0.2.99
4
4
  Summary: Base services for CrewPlus AI applications
5
5
  Author-Email: Tim Liu <tim@opsmateai.com>
6
6
  License: MIT
@@ -15,6 +15,7 @@ Requires-Dist: google-genai>=1.21.1
15
15
  Requires-Dist: anthropic[vertex]>=0.76.0
16
16
  Requires-Dist: langchain-milvus<0.3.0,>=0.2.1
17
17
  Requires-Dist: langfuse<4.0.0,>=3.1.3
18
+ Requires-Dist: pymilvus<=2.6.7,>=2.5.7
18
19
  Description-Content-Type: text/markdown
19
20
 
20
21
  # CrewPlus
@@ -1,15 +1,24 @@
1
1
  from .gemini_chat_model import GeminiChatModel
2
2
  from .claude_chat_model import ClaudeChatModel
3
+ from .oai_claude_chat_model import OAIClaudeChatModel
3
4
  from .init_services import init_load_balancer, get_model_balancer
4
5
  from .model_load_balancer import ModelLoadBalancer
5
6
  from .azure_chat_model import TracedAzureChatOpenAI
6
7
  from .feedback_manager import LangfuseFeedbackManager
7
8
  from .schemas.feedback import FeedbackIn, FeedbackUpdate, FeedbackOut
8
9
  from .tracing_manager import TracingManager, TracingContext
10
+ from .capabilities import (
11
+ BaseCapability,
12
+ CapabilityConfig,
13
+ CapabilityRegistry,
14
+ TavilySearchCapability,
15
+ BashCapability,
16
+ )
9
17
 
10
18
  __all__ = [
11
19
  "GeminiChatModel",
12
20
  "ClaudeChatModel",
21
+ "OAIClaudeChatModel",
13
22
  "init_load_balancer",
14
23
  "get_model_balancer",
15
24
  "ModelLoadBalancer",
@@ -19,5 +28,10 @@ __all__ = [
19
28
  "FeedbackUpdate",
20
29
  "FeedbackOut",
21
30
  "TracingManager",
22
- "TracingContext"
31
+ "TracingContext",
32
+ "BaseCapability",
33
+ "CapabilityConfig",
34
+ "CapabilityRegistry",
35
+ "TavilySearchCapability",
36
+ "BashCapability",
23
37
  ]
@@ -26,13 +26,30 @@ class TracedAzureChatOpenAI(AzureChatOpenAI):
26
26
 
27
27
  **Supported Capabilities:**
28
28
 
29
- None. Azure OpenAI Chat Completions API does not have built-in tools like
30
- web search or code execution. To add these capabilities:
31
- - Use Azure OpenAI Assistants API with built-in tools
32
- - Implement external tools via function calling
33
- - Use third-party services for web search/code execution
34
-
35
- The `capabilities` configuration is not applicable to this model class.
29
+ None. Azure OpenAI Chat Completions API does not have built-in web search
30
+ or similar capabilities.
31
+
32
+ Unlike Claude and Gemini which have platform-native web search:
33
+ - Claude: Built-in `web_search_20250305` tool, search executed by Anthropic
34
+ - Gemini: Built-in `GoogleSearch` tool, search executed by Google
35
+ - Azure OpenAI: **No built-in web search in Chat Completions API**
36
+
37
+ Microsoft does offer web search through other APIs, but none are compatible
38
+ with the Chat Completions API used here:
39
+ - Responses API `web_search_preview`: Only available on OpenAI direct, NOT on Azure
40
+ - Agents API `bing_grounding`: Requires `azure-ai-agents` SDK, incompatible with LangChain
41
+ - Chat Completions `data_sources`: Only supports Azure AI Search (your own index), not web search
42
+
43
+ If web search is needed for Azure models in the future, the feasible approach is
44
+ Function Calling + external search service (e.g. Bing Search API):
45
+ 1. Inject a `web_search` function definition into the request
46
+ 2. When the model returns a tool_call, execute the search externally
47
+ 3. Feed results back to the model for final response generation
48
+ This is fundamentally different from Claude/Gemini where the platform handles
49
+ the search internally in a single API call.
50
+
51
+ Revisit this when Microsoft enables `web_search_preview` on Azure OpenAI.
52
+ Ref: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/web-search
36
53
 
37
54
  Attributes:
38
55
  logger (Optional[logging.Logger]): An optional logger instance.
@@ -0,0 +1,19 @@
1
+ """
2
+ Capabilities module for custom tool support.
3
+
4
+ Provides a registry-based system for adding capabilities (web search, bash, etc.)
5
+ to chat models that don't have native support for these features.
6
+ """
7
+
8
+ from .base import BaseCapability, CapabilityConfig
9
+ from .registry import CapabilityRegistry
10
+ from .web_search import TavilySearchCapability
11
+ from .bash import BashCapability
12
+
13
+ __all__ = [
14
+ "BaseCapability",
15
+ "CapabilityConfig",
16
+ "CapabilityRegistry",
17
+ "TavilySearchCapability",
18
+ "BashCapability",
19
+ ]
@@ -0,0 +1,179 @@
1
+ """
2
+ Base classes for capability system.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict, Optional, List
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class CapabilityConfig(BaseModel):
11
+ """Configuration for a capability."""
12
+
13
+ enabled: bool = Field(default=True, description="Whether this capability is enabled")
14
+ type: str = Field(default="custom", description="Capability type: 'native' or 'custom'")
15
+
16
+ class Config:
17
+ extra = "allow" # Allow additional fields for specific capabilities
18
+
19
+
20
+ class BaseCapability(ABC):
21
+ """
22
+ Abstract base class for all capabilities.
23
+
24
+ Capabilities are tools that can be added to chat models via function calling.
25
+ Each capability provides:
26
+ - A tool schema for the LLM to understand how to call it
27
+ - An optional execute method for actual execution (if needed)
28
+
29
+ Example:
30
+ class MyCapability(BaseCapability):
31
+ @property
32
+ def name(self) -> str:
33
+ return "my_tool"
34
+
35
+ @property
36
+ def description(self) -> str:
37
+ return "Does something useful"
38
+
39
+ def get_parameters_schema(self) -> Dict:
40
+ return {
41
+ "type": "object",
42
+ "properties": {
43
+ "input": {"type": "string", "description": "The input"}
44
+ },
45
+ "required": ["input"]
46
+ }
47
+ """
48
+
49
+ def __init__(self, config: Dict[str, Any]):
50
+ """
51
+ Initialize the capability with configuration.
52
+
53
+ Args:
54
+ config: Configuration dictionary for this capability
55
+ """
56
+ self.config = CapabilityConfig(**config) if isinstance(config, dict) else config
57
+ self._raw_config = config
58
+
59
+ @property
60
+ @abstractmethod
61
+ def name(self) -> str:
62
+ """
63
+ Return the tool name for function calling.
64
+
65
+ Must match regex: ^[a-zA-Z0-9_-]{1,64}$
66
+ """
67
+ pass
68
+
69
+ @property
70
+ @abstractmethod
71
+ def description(self) -> str:
72
+ """
73
+ Return a detailed description of what this tool does.
74
+
75
+ This helps the LLM understand when and how to use the tool.
76
+ """
77
+ pass
78
+
79
+ @abstractmethod
80
+ def get_parameters_schema(self) -> Dict[str, Any]:
81
+ """
82
+ Return the JSON Schema for the tool's parameters.
83
+
84
+ Returns:
85
+ Dict with "type": "object", "properties", and "required" keys
86
+ """
87
+ pass
88
+
89
+ def get_tool_schema(self) -> Dict[str, Any]:
90
+ """
91
+ Return the complete OpenAI-compatible tool schema.
92
+
93
+ Returns:
94
+ Tool definition in OpenAI function calling format
95
+ """
96
+ return {
97
+ "type": "function",
98
+ "function": {
99
+ "name": self.name,
100
+ "description": self.description,
101
+ "parameters": self.get_parameters_schema()
102
+ }
103
+ }
104
+
105
+ def get_langchain_tool_schema(self) -> Dict[str, Any]:
106
+ """
107
+ Return tool schema in LangChain/Claude format.
108
+
109
+ Returns:
110
+ Tool definition compatible with Claude's API
111
+ """
112
+ return {
113
+ "name": self.name,
114
+ "description": self.description,
115
+ "input_schema": self.get_parameters_schema()
116
+ }
117
+
118
+ async def execute(self, **kwargs) -> str:
119
+ """
120
+ Execute the capability with given arguments.
121
+
122
+ Override this method to provide actual execution logic.
123
+ Default implementation raises NotImplementedError.
124
+
125
+ Args:
126
+ **kwargs: Arguments matching the parameters schema
127
+
128
+ Returns:
129
+ String result of the execution
130
+ """
131
+ raise NotImplementedError(
132
+ f"Capability '{self.name}' does not support execution. "
133
+ "Override execute() method to add execution support."
134
+ )
135
+
136
+ def execute_sync(self, **kwargs) -> str:
137
+ """
138
+ Synchronous version of execute.
139
+
140
+ Default implementation raises NotImplementedError.
141
+ """
142
+ raise NotImplementedError(
143
+ f"Capability '{self.name}' does not support synchronous execution. "
144
+ "Override execute_sync() method to add execution support."
145
+ )
146
+
147
+ def is_enabled(self) -> bool:
148
+ """Check if this capability is enabled."""
149
+ return self.config.enabled
150
+
151
+ def get_config_value(self, key: str, default: Any = None) -> Any:
152
+ """Get a configuration value by key."""
153
+ return self._raw_config.get(key, default)
154
+
155
+ def update_config(self, updates: Dict[str, Any]) -> None:
156
+ """
157
+ Update configuration with new values.
158
+
159
+ Args:
160
+ updates: Dictionary of config updates to apply
161
+ """
162
+ self._raw_config.update(updates)
163
+ self.config = CapabilityConfig(**self._raw_config)
164
+
165
+ def clone_with_config(self, config_override: Dict[str, Any]) -> "BaseCapability":
166
+ """
167
+ Create a new instance with merged configuration.
168
+
169
+ Args:
170
+ config_override: Configuration values to override
171
+
172
+ Returns:
173
+ New capability instance with merged config
174
+ """
175
+ merged_config = {**self._raw_config, **config_override}
176
+ return self.__class__(merged_config)
177
+
178
+ def __repr__(self) -> str:
179
+ return f"{self.__class__.__name__}(name='{self.name}', enabled={self.is_enabled()})"
@@ -0,0 +1,210 @@
1
+ """
2
+ Bash command capability for function calling simulation.
3
+
4
+ This capability allows the LLM to express bash commands via function calling.
5
+ It does NOT actually execute commands - it's designed for testing and simulation
6
+ of the function calling workflow.
7
+ """
8
+
9
+ import logging
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from .base import BaseCapability
13
+
14
+
15
+ class BashCapability(BaseCapability):
16
+ """
17
+ Bash command capability for function calling simulation.
18
+
19
+ This capability provides the tool schema for bash commands, allowing the LLM
20
+ to generate proper function calls with command and restart parameters.
21
+ Execution is NOT performed - this is for function calling simulation only.
22
+
23
+ Configuration Options:
24
+ enabled (bool): Whether this capability is enabled (default: True)
25
+ safe_mode (bool): If True, hints to LLM to avoid dangerous commands (default: True)
26
+ allowed_commands (list): List of allowed command prefixes (optional)
27
+ timeout (int): Suggested timeout in seconds (default: 30)
28
+ working_directory (str): Suggested working directory (optional)
29
+
30
+ Example:
31
+ config = {
32
+ "enabled": True,
33
+ "safe_mode": True,
34
+ "allowed_commands": ["ls", "cat", "grep", "find", "echo"],
35
+ "timeout": 30
36
+ }
37
+ capability = BashCapability(config)
38
+ schema = capability.get_tool_schema()
39
+
40
+ Note:
41
+ This capability is designed for testing the function calling workflow.
42
+ It does NOT execute actual bash commands. For actual execution, the
43
+ caller must handle the tool_calls returned by the LLM and execute
44
+ them in a controlled environment.
45
+ """
46
+
47
+ def __init__(self, config: Dict[str, Any]):
48
+ super().__init__(config)
49
+ self.logger = logging.getLogger(__name__)
50
+
51
+ # Configuration defaults
52
+ self._safe_mode = config.get("safe_mode", True)
53
+ self._allowed_commands = config.get("allowed_commands", [])
54
+ self._timeout = config.get("timeout", 30)
55
+ self._working_directory = config.get("working_directory")
56
+
57
+ @property
58
+ def name(self) -> str:
59
+ return "bash"
60
+
61
+ @property
62
+ def description(self) -> str:
63
+ base_desc = (
64
+ "Execute a bash command in a persistent shell session. "
65
+ "Use this for running system commands, scripts, and command-line operations. "
66
+ "The shell session maintains state between commands (working directory, "
67
+ "environment variables, created files)."
68
+ )
69
+
70
+ if self._safe_mode:
71
+ base_desc += (
72
+ " IMPORTANT: Avoid destructive commands like 'rm -rf', 'format', "
73
+ "or commands that could harm the system."
74
+ )
75
+
76
+ if self._allowed_commands:
77
+ allowed_str = ", ".join(self._allowed_commands)
78
+ base_desc += f" Preferred commands: {allowed_str}."
79
+
80
+ return base_desc
81
+
82
+ def get_parameters_schema(self) -> Dict[str, Any]:
83
+ return {
84
+ "type": "object",
85
+ "properties": {
86
+ "command": {
87
+ "type": "string",
88
+ "description": (
89
+ "The bash command to execute. Use standard Unix commands. "
90
+ "For multi-step operations, chain commands with && or ;."
91
+ )
92
+ },
93
+ "restart": {
94
+ "type": "boolean",
95
+ "description": (
96
+ "Set to true to restart the bash session, clearing all state "
97
+ "(working directory resets, environment variables cleared)."
98
+ ),
99
+ "default": False
100
+ }
101
+ },
102
+ "required": [] # Neither is strictly required per Anthropic's bash tool spec
103
+ }
104
+
105
+ def validate_command(self, command: str) -> tuple[bool, Optional[str]]:
106
+ """
107
+ Validate a command against safety rules.
108
+
109
+ Args:
110
+ command: The command to validate
111
+
112
+ Returns:
113
+ Tuple of (is_valid, error_message)
114
+ """
115
+ if not command or not command.strip():
116
+ return True, None # Empty command is valid (might just be restart)
117
+
118
+ # Dangerous patterns to block
119
+ dangerous_patterns = [
120
+ "rm -rf /",
121
+ "rm -rf /*",
122
+ ":(){:|:&};:", # Fork bomb
123
+ "mkfs",
124
+ "dd if=",
125
+ "> /dev/sd",
126
+ "chmod -R 777 /",
127
+ "wget | sh",
128
+ "curl | sh",
129
+ ]
130
+
131
+ if self._safe_mode:
132
+ command_lower = command.lower()
133
+ for pattern in dangerous_patterns:
134
+ if pattern in command_lower:
135
+ return False, f"Command blocked: contains dangerous pattern '{pattern}'"
136
+
137
+ # Check allowed commands if specified
138
+ if self._allowed_commands:
139
+ command_parts = command.strip().split()
140
+ if command_parts:
141
+ base_command = command_parts[0]
142
+ if base_command not in self._allowed_commands:
143
+ return False, (
144
+ f"Command '{base_command}' not in allowed list. "
145
+ f"Allowed: {self._allowed_commands}"
146
+ )
147
+
148
+ return True, None
149
+
150
+ def execute_sync(self, command: str = "", restart: bool = False, **kwargs) -> str:
151
+ """
152
+ Simulate bash command execution (no actual execution).
153
+
154
+ This method validates the command and returns a simulation message.
155
+ It does NOT actually execute the command.
156
+
157
+ Args:
158
+ command: The bash command (not executed)
159
+ restart: Whether to restart the session
160
+
161
+ Returns:
162
+ Simulation message describing what would happen
163
+ """
164
+ if restart:
165
+ return "[SIMULATION] Bash session would be restarted. All state cleared."
166
+
167
+ if not command:
168
+ return "[SIMULATION] No command provided."
169
+
170
+ # Validate command
171
+ is_valid, error = self.validate_command(command)
172
+ if not is_valid:
173
+ return f"[SIMULATION] Command blocked: {error}"
174
+
175
+ return (
176
+ f"[SIMULATION] Would execute command: {command}\n"
177
+ f"Note: Actual execution is disabled. This is a function calling simulation.\n"
178
+ f"To enable execution, handle the tool_call in your application code."
179
+ )
180
+
181
+ async def execute(self, command: str = "", restart: bool = False, **kwargs) -> str:
182
+ """
183
+ Async version of execute (same simulation behavior).
184
+
185
+ Args:
186
+ command: The bash command (not executed)
187
+ restart: Whether to restart the session
188
+
189
+ Returns:
190
+ Simulation message describing what would happen
191
+ """
192
+ return self.execute_sync(command=command, restart=restart, **kwargs)
193
+
194
+ def get_execution_hints(self) -> Dict[str, Any]:
195
+ """
196
+ Get hints for actual command execution (for callers who want to execute).
197
+
198
+ Returns:
199
+ Dict with execution configuration hints
200
+ """
201
+ return {
202
+ "safe_mode": self._safe_mode,
203
+ "allowed_commands": self._allowed_commands,
204
+ "timeout": self._timeout,
205
+ "working_directory": self._working_directory,
206
+ "dangerous_patterns": [
207
+ "rm -rf /", "mkfs", "dd if=", ":(){:|:&};:",
208
+ "chmod -R 777 /", "wget | sh", "curl | sh"
209
+ ]
210
+ }