powermem 0.1.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.
Files changed (123) hide show
  1. powermem/__init__.py +103 -0
  2. powermem/agent/__init__.py +35 -0
  3. powermem/agent/abstract/__init__.py +22 -0
  4. powermem/agent/abstract/collaboration.py +259 -0
  5. powermem/agent/abstract/context.py +187 -0
  6. powermem/agent/abstract/manager.py +232 -0
  7. powermem/agent/abstract/permission.py +217 -0
  8. powermem/agent/abstract/privacy.py +267 -0
  9. powermem/agent/abstract/scope.py +199 -0
  10. powermem/agent/agent.py +791 -0
  11. powermem/agent/components/__init__.py +18 -0
  12. powermem/agent/components/collaboration_coordinator.py +645 -0
  13. powermem/agent/components/permission_controller.py +586 -0
  14. powermem/agent/components/privacy_protector.py +767 -0
  15. powermem/agent/components/scope_controller.py +685 -0
  16. powermem/agent/factories/__init__.py +16 -0
  17. powermem/agent/factories/agent_factory.py +266 -0
  18. powermem/agent/factories/config_factory.py +308 -0
  19. powermem/agent/factories/memory_factory.py +229 -0
  20. powermem/agent/implementations/__init__.py +16 -0
  21. powermem/agent/implementations/hybrid.py +728 -0
  22. powermem/agent/implementations/multi_agent.py +1040 -0
  23. powermem/agent/implementations/multi_user.py +1020 -0
  24. powermem/agent/types.py +53 -0
  25. powermem/agent/wrappers/__init__.py +14 -0
  26. powermem/agent/wrappers/agent_memory_wrapper.py +427 -0
  27. powermem/agent/wrappers/compatibility_wrapper.py +520 -0
  28. powermem/config_loader.py +318 -0
  29. powermem/configs.py +249 -0
  30. powermem/core/__init__.py +19 -0
  31. powermem/core/async_memory.py +1493 -0
  32. powermem/core/audit.py +258 -0
  33. powermem/core/base.py +165 -0
  34. powermem/core/memory.py +1567 -0
  35. powermem/core/setup.py +162 -0
  36. powermem/core/telemetry.py +215 -0
  37. powermem/integrations/__init__.py +17 -0
  38. powermem/integrations/embeddings/__init__.py +13 -0
  39. powermem/integrations/embeddings/aws_bedrock.py +100 -0
  40. powermem/integrations/embeddings/azure_openai.py +55 -0
  41. powermem/integrations/embeddings/base.py +31 -0
  42. powermem/integrations/embeddings/config/base.py +132 -0
  43. powermem/integrations/embeddings/configs.py +31 -0
  44. powermem/integrations/embeddings/factory.py +48 -0
  45. powermem/integrations/embeddings/gemini.py +39 -0
  46. powermem/integrations/embeddings/huggingface.py +41 -0
  47. powermem/integrations/embeddings/langchain.py +35 -0
  48. powermem/integrations/embeddings/lmstudio.py +29 -0
  49. powermem/integrations/embeddings/mock.py +11 -0
  50. powermem/integrations/embeddings/ollama.py +53 -0
  51. powermem/integrations/embeddings/openai.py +49 -0
  52. powermem/integrations/embeddings/qwen.py +102 -0
  53. powermem/integrations/embeddings/together.py +31 -0
  54. powermem/integrations/embeddings/vertexai.py +54 -0
  55. powermem/integrations/llm/__init__.py +18 -0
  56. powermem/integrations/llm/anthropic.py +87 -0
  57. powermem/integrations/llm/base.py +132 -0
  58. powermem/integrations/llm/config/anthropic.py +56 -0
  59. powermem/integrations/llm/config/azure.py +56 -0
  60. powermem/integrations/llm/config/base.py +62 -0
  61. powermem/integrations/llm/config/deepseek.py +56 -0
  62. powermem/integrations/llm/config/ollama.py +56 -0
  63. powermem/integrations/llm/config/openai.py +79 -0
  64. powermem/integrations/llm/config/qwen.py +68 -0
  65. powermem/integrations/llm/config/qwen_asr.py +46 -0
  66. powermem/integrations/llm/config/vllm.py +56 -0
  67. powermem/integrations/llm/configs.py +26 -0
  68. powermem/integrations/llm/deepseek.py +106 -0
  69. powermem/integrations/llm/factory.py +118 -0
  70. powermem/integrations/llm/gemini.py +201 -0
  71. powermem/integrations/llm/langchain.py +65 -0
  72. powermem/integrations/llm/ollama.py +106 -0
  73. powermem/integrations/llm/openai.py +166 -0
  74. powermem/integrations/llm/openai_structured.py +80 -0
  75. powermem/integrations/llm/qwen.py +207 -0
  76. powermem/integrations/llm/qwen_asr.py +171 -0
  77. powermem/integrations/llm/vllm.py +106 -0
  78. powermem/integrations/rerank/__init__.py +20 -0
  79. powermem/integrations/rerank/base.py +43 -0
  80. powermem/integrations/rerank/config/__init__.py +7 -0
  81. powermem/integrations/rerank/config/base.py +27 -0
  82. powermem/integrations/rerank/configs.py +23 -0
  83. powermem/integrations/rerank/factory.py +68 -0
  84. powermem/integrations/rerank/qwen.py +159 -0
  85. powermem/intelligence/__init__.py +17 -0
  86. powermem/intelligence/ebbinghaus_algorithm.py +354 -0
  87. powermem/intelligence/importance_evaluator.py +361 -0
  88. powermem/intelligence/intelligent_memory_manager.py +284 -0
  89. powermem/intelligence/manager.py +148 -0
  90. powermem/intelligence/plugin.py +229 -0
  91. powermem/prompts/__init__.py +29 -0
  92. powermem/prompts/graph/graph_prompts.py +217 -0
  93. powermem/prompts/graph/graph_tools_prompts.py +469 -0
  94. powermem/prompts/importance_evaluation.py +246 -0
  95. powermem/prompts/intelligent_memory_prompts.py +163 -0
  96. powermem/prompts/templates.py +193 -0
  97. powermem/storage/__init__.py +14 -0
  98. powermem/storage/adapter.py +896 -0
  99. powermem/storage/base.py +109 -0
  100. powermem/storage/config/base.py +13 -0
  101. powermem/storage/config/oceanbase.py +58 -0
  102. powermem/storage/config/pgvector.py +52 -0
  103. powermem/storage/config/sqlite.py +27 -0
  104. powermem/storage/configs.py +159 -0
  105. powermem/storage/factory.py +59 -0
  106. powermem/storage/migration_manager.py +438 -0
  107. powermem/storage/oceanbase/__init__.py +8 -0
  108. powermem/storage/oceanbase/constants.py +162 -0
  109. powermem/storage/oceanbase/oceanbase.py +1384 -0
  110. powermem/storage/oceanbase/oceanbase_graph.py +1441 -0
  111. powermem/storage/pgvector/__init__.py +7 -0
  112. powermem/storage/pgvector/pgvector.py +420 -0
  113. powermem/storage/sqlite/__init__.py +0 -0
  114. powermem/storage/sqlite/sqlite.py +218 -0
  115. powermem/storage/sqlite/sqlite_vector_store.py +311 -0
  116. powermem/utils/__init__.py +35 -0
  117. powermem/utils/utils.py +605 -0
  118. powermem/version.py +23 -0
  119. powermem-0.1.0.dist-info/METADATA +187 -0
  120. powermem-0.1.0.dist-info/RECORD +123 -0
  121. powermem-0.1.0.dist-info/WHEEL +5 -0
  122. powermem-0.1.0.dist-info/licenses/LICENSE +206 -0
  123. powermem-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,106 @@
1
+ from typing import Dict, List, Optional, Union
2
+
3
+ from powermem.integrations.llm import LLMBase
4
+ from powermem.integrations.llm.config.base import BaseLLMConfig
5
+ from powermem.integrations.llm.config.ollama import OllamaConfig
6
+
7
+ try:
8
+ from ollama import Client
9
+ except ImportError:
10
+ raise ImportError("The 'ollama' library is required. Please install it using 'pip install ollama'.")
11
+
12
+
13
+ class OllamaLLM(LLMBase):
14
+ def __init__(self, config: Optional[Union[BaseLLMConfig, OllamaConfig, Dict]] = None):
15
+ # Convert to OllamaConfig if needed
16
+ if config is None:
17
+ config = OllamaConfig()
18
+ elif isinstance(config, dict):
19
+ config = OllamaConfig(**config)
20
+ elif isinstance(config, BaseLLMConfig) and not isinstance(config, OllamaConfig):
21
+ # Convert BaseLLMConfig to OllamaConfig
22
+ config = OllamaConfig(
23
+ model=config.model,
24
+ temperature=config.temperature,
25
+ api_key=config.api_key,
26
+ max_tokens=config.max_tokens,
27
+ top_p=config.top_p,
28
+ top_k=config.top_k,
29
+ enable_vision=config.enable_vision,
30
+ vision_details=config.vision_details,
31
+ http_client_proxies=config.http_client,
32
+ )
33
+
34
+ super().__init__(config)
35
+
36
+ if not self.config.model:
37
+ self.config.model = "llama3.1:70b"
38
+
39
+ self.client = Client(host=self.config.ollama_base_url)
40
+
41
+ def _parse_response(self, response, tools):
42
+ """
43
+ Process the response based on whether tools are used or not.
44
+
45
+ Args:
46
+ response: The raw response from API.
47
+ tools: The list of tools provided in the request.
48
+
49
+ Returns:
50
+ str or dict: The processed response.
51
+ """
52
+ if tools:
53
+ processed_response = {
54
+ "content": response["message"]["content"] if isinstance(response, dict) else response.message.content,
55
+ "tool_calls": [],
56
+ }
57
+
58
+ # Ollama doesn't support tool calls in the same way, so we return the content
59
+ return processed_response
60
+ else:
61
+ # Handle both dict and object responses
62
+ if isinstance(response, dict):
63
+ return response["message"]["content"]
64
+ else:
65
+ return response.message.content
66
+
67
+ def generate_response(
68
+ self,
69
+ messages: List[Dict[str, str]],
70
+ response_format=None,
71
+ tools: Optional[List[Dict]] = None,
72
+ tool_choice: str = "auto",
73
+ **kwargs,
74
+ ):
75
+ """
76
+ Generate a response based on the given messages using Ollama.
77
+
78
+ Args:
79
+ messages (list): List of message dicts containing 'role' and 'content'.
80
+ response_format (str or object, optional): Format of the response. Defaults to "text".
81
+ tools (list, optional): List of tools that the model can call. Defaults to None.
82
+ tool_choice (str, optional): Tool choice method. Defaults to "auto".
83
+ **kwargs: Additional Ollama-specific parameters.
84
+
85
+ Returns:
86
+ str: The generated response.
87
+ """
88
+ # Build parameters for Ollama
89
+ params = {
90
+ "model": self.config.model,
91
+ "messages": messages,
92
+ }
93
+
94
+ # Add options for Ollama (temperature, num_predict, top_p)
95
+ options = {
96
+ "temperature": self.config.temperature,
97
+ "num_predict": self.config.max_tokens,
98
+ "top_p": self.config.top_p,
99
+ }
100
+ params["options"] = options
101
+
102
+ # Remove OpenAI-specific parameters that Ollama doesn't support
103
+ params.pop("max_tokens", None) # Ollama uses different parameter names
104
+
105
+ response = self.client.chat(**params)
106
+ return self._parse_response(response, tools)
@@ -0,0 +1,166 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from typing import Dict, List, Optional, Union
5
+
6
+ from openai import OpenAI
7
+ from powermem.integrations.llm import LLMBase
8
+ from powermem.integrations.llm.config.base import BaseLLMConfig
9
+ from powermem.integrations.llm.config.openai import OpenAIConfig
10
+ from powermem.utils.utils import extract_json
11
+
12
+
13
+ class OpenAILLM(LLMBase):
14
+ def __init__(self, config: Optional[Union[BaseLLMConfig, OpenAIConfig, Dict]] = None):
15
+ # Convert to OpenAIConfig if needed
16
+ if config is None:
17
+ config = OpenAIConfig()
18
+ elif isinstance(config, dict):
19
+ config = OpenAIConfig(**config)
20
+ elif isinstance(config, BaseLLMConfig) and not isinstance(config, OpenAIConfig):
21
+ # Convert BaseLLMConfig to OpenAIConfig
22
+ config = OpenAIConfig(
23
+ model=config.model,
24
+ temperature=config.temperature,
25
+ api_key=config.api_key,
26
+ max_tokens=config.max_tokens,
27
+ top_p=config.top_p,
28
+ top_k=config.top_k,
29
+ enable_vision=config.enable_vision,
30
+ vision_details=config.vision_details,
31
+ http_client_proxies=config.http_client,
32
+ )
33
+
34
+ super().__init__(config)
35
+
36
+ if not self.config.model:
37
+ self.config.model = "gpt-4o-mini"
38
+
39
+ if os.environ.get("OPENROUTER_API_KEY"): # Use OpenRouter
40
+ self.client = OpenAI(
41
+ api_key=os.environ.get("OPENROUTER_API_KEY"),
42
+ base_url=self.config.openrouter_base_url
43
+ or os.getenv("OPENROUTER_API_BASE")
44
+ or "https://openrouter.ai/api/v1",
45
+ )
46
+ else:
47
+ api_key = self.config.api_key or os.getenv("OPENAI_API_KEY")
48
+ base_url = self.config.openai_base_url or os.getenv("OPENAI_BASE_URL") or "https://api.openai.com/v1"
49
+
50
+ self.client = OpenAI(api_key=api_key, base_url=base_url)
51
+
52
+ def _parse_response(self, response, tools):
53
+ """
54
+ Process the response based on whether tools are used or not.
55
+
56
+ Args:
57
+ response: The raw response from API.
58
+ tools: The list of tools provided in the request.
59
+
60
+ Returns:
61
+ str or dict: The processed response.
62
+ """
63
+ if tools:
64
+ processed_response = {
65
+ "content": response.choices[0].message.content,
66
+ "tool_calls": [],
67
+ }
68
+
69
+ if response.choices[0].message.tool_calls:
70
+ for tool_call in response.choices[0].message.tool_calls:
71
+ # Extract and validate arguments
72
+ arguments_str = extract_json(tool_call.function.arguments)
73
+
74
+ # Check if arguments are empty or whitespace only
75
+ if not arguments_str or arguments_str.strip() == "":
76
+ logging.warning(
77
+ f"Tool call '{tool_call.function.name}' has empty arguments. Skipping this tool call."
78
+ )
79
+ continue
80
+
81
+ # Try to parse JSON with error handling
82
+ try:
83
+ arguments = json.loads(arguments_str)
84
+ except json.JSONDecodeError as e:
85
+ logging.error(
86
+ f"Failed to parse tool call arguments for '{tool_call.function.name}': "
87
+ f"{arguments_str[:100]}... Error: {e}"
88
+ )
89
+ continue
90
+
91
+ processed_response["tool_calls"].append(
92
+ {
93
+ "name": tool_call.function.name,
94
+ "arguments": arguments,
95
+ }
96
+ )
97
+
98
+ return processed_response
99
+ else:
100
+ return response.choices[0].message.content
101
+
102
+ def generate_response(
103
+ self,
104
+ messages: List[Dict[str, str]],
105
+ response_format=None,
106
+ tools: Optional[List[Dict]] = None,
107
+ tool_choice: str = "auto",
108
+ **kwargs,
109
+ ):
110
+ """
111
+ Generate a JSON response based on the given messages using OpenAI.
112
+
113
+ Args:
114
+ messages (list): List of message dicts containing 'role' and 'content'.
115
+ response_format (str or object, optional): Format of the response. Defaults to "text".
116
+ tools (list, optional): List of tools that the model can call. Defaults to None.
117
+ tool_choice (str, optional): Tool choice method. Defaults to "auto".
118
+ **kwargs: Additional OpenAI-specific parameters.
119
+
120
+ Returns:
121
+ json: The generated response.
122
+ """
123
+ params = self._get_supported_params(messages=messages, **kwargs)
124
+
125
+ params.update({
126
+ "model": self.config.model,
127
+ "messages": messages,
128
+ })
129
+
130
+ if os.getenv("OPENROUTER_API_KEY"):
131
+ openrouter_params = {}
132
+ if self.config.models:
133
+ openrouter_params["models"] = self.config.models
134
+ openrouter_params["route"] = self.config.route
135
+ params.pop("model")
136
+
137
+ if self.config.site_url and self.config.app_name:
138
+ extra_headers = {
139
+ "HTTP-Referer": self.config.site_url,
140
+ "X-Title": self.config.app_name,
141
+ }
142
+ openrouter_params["extra_headers"] = extra_headers
143
+
144
+ params.update(**openrouter_params)
145
+
146
+ else:
147
+ openai_specific_generation_params = ["store"]
148
+ for param in openai_specific_generation_params:
149
+ if hasattr(self.config, param):
150
+ params[param] = getattr(self.config, param)
151
+
152
+ if response_format:
153
+ params["response_format"] = response_format
154
+ if tools: # TODO: Remove tools if no issues found with new memory addition logic
155
+ params["tools"] = tools
156
+ params["tool_choice"] = tool_choice
157
+ response = self.client.chat.completions.create(**params)
158
+ parsed_response = self._parse_response(response, tools)
159
+ if self.config.response_callback:
160
+ try:
161
+ self.config.response_callback(self, response, params)
162
+ except Exception as e:
163
+ # Log error but don't propagate
164
+ logging.error(f"Error due to callback: {e}")
165
+ pass
166
+ return parsed_response
@@ -0,0 +1,80 @@
1
+ import json
2
+ import os
3
+ from typing import Dict, List, Optional, Any
4
+
5
+ from openai import OpenAI
6
+
7
+ from powermem.integrations.llm import LLMBase
8
+ from powermem.integrations.llm.config.base import BaseLLMConfig
9
+
10
+
11
+ class OpenAIStructuredLLM(LLMBase):
12
+ def __init__(self, config: Optional[BaseLLMConfig] = None):
13
+ super().__init__(config)
14
+
15
+ if not self.config.model:
16
+ self.config.model = "gpt-5"
17
+
18
+ api_key = self.config.api_key or os.getenv("OPENAI_API_KEY")
19
+ base_url = self.config.openai_base_url or os.getenv("OPENAI_API_BASE") or "https://api.openai.com/v1"
20
+ self.client = OpenAI(api_key=api_key, base_url=base_url)
21
+
22
+ def generate_response(
23
+ self,
24
+ messages: List[Dict[str, str]],
25
+ response_format: Optional[str] = None,
26
+ tools: Optional[List[Dict]] = None,
27
+ tool_choice: str = "auto",
28
+ ) -> str | None | dict[str, str | None | list[Any]]:
29
+ """
30
+ Generate a response based on the given messages using OpenAI.
31
+
32
+ Args:
33
+ messages (List[Dict[str, str]]): A list of dictionaries, each containing a 'role' and 'content' key.
34
+ response_format (Optional[str]): The desired format of the response. Defaults to None.
35
+
36
+
37
+ Returns:
38
+ str: The generated response.
39
+ """
40
+ params = {
41
+ "model": self.config.model,
42
+ "messages": messages,
43
+ "temperature": self.config.temperature,
44
+ }
45
+
46
+ if response_format:
47
+ params["response_format"] = response_format
48
+ if tools:
49
+ params["tools"] = tools
50
+ params["tool_choice"] = tool_choice
51
+
52
+ response = self.client.beta.chat.completions.parse(**params)
53
+
54
+ message = response.choices[0].message
55
+
56
+ # If tools were used and tool_calls exist, return structured response
57
+ if tools and message.tool_calls:
58
+ tool_calls_list = []
59
+ for tool_call in message.tool_calls:
60
+ arguments = tool_call.function.arguments
61
+ # Parse arguments if it's a string
62
+ if isinstance(arguments, str):
63
+ try:
64
+ arguments = json.loads(arguments)
65
+ except json.JSONDecodeError:
66
+ arguments = {}
67
+
68
+ tool_calls_list.append({
69
+ "id": tool_call.id,
70
+ "name": tool_call.function.name,
71
+ "arguments": arguments
72
+ })
73
+
74
+ return {
75
+ "content": message.content,
76
+ "tool_calls": tool_calls_list
77
+ }
78
+
79
+ # Otherwise return just the content
80
+ return message.content or ""
@@ -0,0 +1,207 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from typing import Dict, List, Optional, Union
5
+
6
+ try:
7
+ from dashscope import Generation
8
+ from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
9
+ except ImportError:
10
+ Generation = None
11
+ DashScopeAPIResponse = None
12
+
13
+ from powermem.integrations.llm import LLMBase
14
+ from powermem.integrations.llm.config.base import BaseLLMConfig
15
+ from powermem.integrations.llm.config.qwen import QwenConfig
16
+ from powermem.utils.utils import extract_json
17
+ import dashscope
18
+
19
+
20
+ class QwenLLM(LLMBase):
21
+ def __init__(self, config: Optional[Union[BaseLLMConfig, QwenConfig, Dict]] = None):
22
+ # Check if dashscope is available first
23
+ try:
24
+ from dashscope import Generation
25
+ from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
26
+ except ImportError:
27
+ raise ImportError(
28
+ "DashScope SDK is not installed. Please install it with: pip install dashscope"
29
+ )
30
+
31
+ # Convert to QwenConfig if needed
32
+ if config is None:
33
+ config = QwenConfig()
34
+ elif isinstance(config, dict):
35
+ config = QwenConfig(**config)
36
+ elif isinstance(config, BaseLLMConfig) and not isinstance(config, QwenConfig):
37
+ # Convert BaseLLMConfig to QwenConfig
38
+ config = QwenConfig(
39
+ model=config.model,
40
+ temperature=config.temperature,
41
+ api_key=config.api_key,
42
+ max_tokens=config.max_tokens,
43
+ top_p=config.top_p,
44
+ top_k=config.top_k,
45
+ enable_vision=config.enable_vision,
46
+ vision_details=config.vision_details,
47
+ http_client_proxies=config.http_client,
48
+ )
49
+
50
+ super().__init__(config)
51
+
52
+ if not self.config.model:
53
+ self.config.model = "qwen-turbo"
54
+
55
+ # Set API key
56
+ api_key = self.config.api_key or os.getenv("DASHSCOPE_API_KEY")
57
+ if not api_key:
58
+ raise ValueError(
59
+ "API key is required. Set DASHSCOPE_API_KEY environment variable or pass api_key in config.")
60
+
61
+ # Set API key for DashScope SDK
62
+ dashscope.api_key = api_key
63
+
64
+ # Set base URL
65
+ base_url = self.config.dashscope_base_url or os.getenv(
66
+ "DASHSCOPE_BASE_URL") or "https://dashscope.aliyuncs.com/api/v1"
67
+
68
+ if base_url:
69
+ os.environ["DASHSCOPE_BASE_URL"] = base_url
70
+
71
+ def _get_attr(self, obj, key, default=None):
72
+ """Unified handling of attribute access for both dicts and objects"""
73
+ if isinstance(obj, dict):
74
+ return obj.get(key, default)
75
+ return getattr(obj, key, default)
76
+
77
+ def _extract_message(self, output):
78
+ """Extract message object from response output"""
79
+ # Get default text content
80
+ text = self._get_attr(output, 'text', '')
81
+
82
+ # Extract message from choices
83
+ choices = self._get_attr(output, 'choices', [])
84
+ if choices:
85
+ choice = choices[0]
86
+ message = self._get_attr(choice, 'message')
87
+ return message, text
88
+
89
+ return None, text
90
+
91
+ def _extract_content(self, output):
92
+ """Extract response content"""
93
+ message, default_text = self._extract_message(output)
94
+ if message:
95
+ return self._get_attr(message, 'content', default_text)
96
+ return default_text
97
+
98
+ def _extract_tool_calls(self, output):
99
+ """Extract tool calls from response"""
100
+ message, _ = self._extract_message(output)
101
+ if not message:
102
+ return []
103
+
104
+ tool_calls = self._get_attr(message, 'tool_calls')
105
+ if not tool_calls:
106
+ return []
107
+
108
+ processed_calls = []
109
+ for tool_call in tool_calls:
110
+ function = self._get_attr(tool_call, 'function', {})
111
+ name = self._get_attr(function, 'name')
112
+ arguments = self._get_attr(function, 'arguments', '{}')
113
+
114
+ processed_calls.append({
115
+ "name": name,
116
+ "arguments": json.loads(extract_json(arguments)),
117
+ })
118
+
119
+ return processed_calls
120
+
121
+ def _parse_response(self, response: DashScopeAPIResponse, tools: Optional[List[Dict]] = None):
122
+ """
123
+ Process the response based on whether tools are used or not.
124
+
125
+ Args:
126
+ response: The raw response from DashScope API.
127
+ tools: The list of tools provided in the request.
128
+
129
+ Returns:
130
+ str or dict: The processed response.
131
+ """
132
+ if response.status_code != 200:
133
+ raise Exception(f"API request failed with status {response.status_code}: {response.message}")
134
+
135
+ content = self._extract_content(response.output)
136
+
137
+ if tools:
138
+ return {
139
+ "content": content,
140
+ "tool_calls": self._extract_tool_calls(response.output),
141
+ }
142
+
143
+ return content
144
+
145
+ def generate_response(
146
+ self,
147
+ messages: List[Dict[str, str]],
148
+ response_format=None,
149
+ tools: Optional[List[Dict]] = None,
150
+ tool_choice: str = "auto",
151
+ **kwargs,
152
+ ):
153
+ """
154
+ Generate a response based on the given messages using Qwen.
155
+
156
+ Args:
157
+ messages (list): List of message dicts containing 'role' and 'content'.
158
+ response_format (str or object, optional): Format of the response. Defaults to None.
159
+ tools (list, optional): List of tools that the model can call. Defaults to None.
160
+ tool_choice (str, optional): Tool choice method. Defaults to "auto".
161
+ **kwargs: Additional Qwen-specific parameters.
162
+
163
+ Returns:
164
+ str or dict: The generated response.
165
+ """
166
+ params = self._get_supported_params(**kwargs)
167
+
168
+ # Prepare generation parameters
169
+ generation_params = {
170
+ "model": self.config.model,
171
+ "messages": messages,
172
+ "temperature": params.get("temperature", self.config.temperature),
173
+ "max_tokens": params.get("max_tokens", self.config.max_tokens),
174
+ "top_p": params.get("top_p", self.config.top_p),
175
+ }
176
+
177
+ # Add Qwen-specific parameters
178
+ if self.config.enable_search:
179
+ generation_params["enable_search"] = True
180
+ if self.config.search_params:
181
+ generation_params.update(self.config.search_params)
182
+
183
+ # Add tools if provided
184
+ if tools:
185
+ generation_params["tools"] = tools
186
+ generation_params["tool_choice"] = tool_choice
187
+
188
+ # Add response format if provided
189
+ if response_format:
190
+ generation_params["response_format"] = response_format
191
+
192
+ try:
193
+ response = Generation.call(**generation_params)
194
+ parsed_response = self._parse_response(response, tools)
195
+
196
+ if self.config.response_callback:
197
+ try:
198
+ self.config.response_callback(self, response, generation_params)
199
+ except Exception as e:
200
+ # Log error but don't propagate
201
+ logging.error(f"Error due to callback: {e}")
202
+ pass
203
+
204
+ return parsed_response
205
+ except Exception as e:
206
+ logging.error(f"Qwen API call failed: {e}")
207
+ raise