devduck 0.1.1766644714__py3-none-any.whl → 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.

Potentially problematic release.


This version of devduck might be problematic. Click here for more details.

devduck/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.1766644714'
32
- __version_tuple__ = version_tuple = (0, 1, 1766644714)
31
+ __version__ = version = '0.3.0'
32
+ __version_tuple__ = version_tuple = (0, 3, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
devduck/test_redduck.py CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env python3
1
2
  """🦆 DevDuck test suite"""
2
3
 
3
4
 
devduck/tools/__init__.py CHANGED
@@ -1,47 +1,7 @@
1
- """
2
- DevDuck Tools Package
1
+ """DevDuck tools package."""
3
2
 
4
- This module exports all available tools for devduck.
5
- """
6
-
7
- from .agentcore_agents import agentcore_agents
8
- from .agentcore_config import agentcore_config
9
- from .agentcore_invoke import agentcore_invoke
10
- from .agentcore_logs import agentcore_logs
11
- from .ambient import ambient
12
- from .create_subagent import create_subagent
13
- from .fetch_github_tool import fetch_github_tool
14
- from .install_tools import install_tools
15
- from .ipc import ipc
16
- from .mcp_server import mcp_server
17
- from .scraper import scraper
18
- from .speech_to_speech import speech_to_speech
19
- from .state_manager import state_manager
20
- from .store_in_kb import store_in_kb
21
- from .system_prompt import system_prompt
22
3
  from .tcp import tcp
23
- from .tray import tray
24
- from .use_github import use_github
25
- from .websocket import websocket
4
+ from .mcp_server import mcp_server
5
+ from .install_tools import install_tools
26
6
 
27
- __all__ = [
28
- "agentcore_agents",
29
- "agentcore_config",
30
- "agentcore_invoke",
31
- "agentcore_logs",
32
- "ambient",
33
- "create_subagent",
34
- "fetch_github_tool",
35
- "install_tools",
36
- "ipc",
37
- "mcp_server",
38
- "scraper",
39
- "speech_to_speech",
40
- "state_manager",
41
- "store_in_kb",
42
- "system_prompt",
43
- "tcp",
44
- "tray",
45
- "use_github",
46
- "websocket",
47
- ]
7
+ __all__ = ["tcp", "mcp_server", "install_tools"]
@@ -29,7 +29,7 @@ def install_tools(
29
29
  and loading their tools into the agent's registry at runtime.
30
30
 
31
31
  Args:
32
- action: Action to perform - "install", "load", "install_and_load", "list_loaded", "list_available"
32
+ action: Action to perform - "install", "load", "install_and_load", "list_loaded"
33
33
  package: Python package to install (e.g., "strands-agents-tools", "strands-fun-tools")
34
34
  module: Module to import tools from (e.g., "strands_tools", "strands_fun_tools")
35
35
  tool_names: Optional list of specific tools to load. If None, loads all available tools
@@ -39,13 +39,6 @@ def install_tools(
39
39
  Result dictionary with status and content
40
40
 
41
41
  Examples:
42
- # List available tools in a package (without loading)
43
- install_tools(
44
- action="list_available",
45
- package="strands-fun-tools",
46
- module="strands_fun_tools"
47
- )
48
-
49
42
  # Install and load all tools from strands-agents-tools
50
43
  install_tools(
51
44
  action="install_and_load",
@@ -86,15 +79,13 @@ def install_tools(
86
79
  return _load_tools_from_module(module, tool_names, agent)
87
80
  elif action == "list_loaded":
88
81
  return _list_loaded_tools(agent)
89
- elif action == "list_available":
90
- return _list_available_tools(package, module)
91
82
  else:
92
83
  return {
93
84
  "status": "error",
94
85
  "content": [
95
86
  {
96
87
  "text": f"❌ Unknown action: {action}\n\n"
97
- f"Valid actions: install, load, install_and_load, list_loaded, list_available"
88
+ f"Valid actions: install, load, install_and_load, list_loaded"
98
89
  }
99
90
  ],
100
91
  }
@@ -315,95 +306,3 @@ def _list_loaded_tools(agent: Any) -> Dict[str, Any]:
315
306
  "status": "error",
316
307
  "content": [{"text": f"❌ Failed to list tools: {str(e)}"}],
317
308
  }
318
-
319
-
320
- def _list_available_tools(package: Optional[str], module: str) -> Dict[str, Any]:
321
- """List available tools in a package without loading them."""
322
- if not module:
323
- return {
324
- "status": "error",
325
- "content": [
326
- {"text": "❌ module parameter is required for list_available action"}
327
- ],
328
- }
329
-
330
- try:
331
- # Try to import the module
332
- try:
333
- imported_module = importlib.import_module(module)
334
- logger.info(f"Module {module} already installed")
335
- except ImportError:
336
- # Module not installed - try to install package first
337
- if not package:
338
- return {
339
- "status": "error",
340
- "content": [
341
- {
342
- "text": f"❌ Module {module} not found and no package specified to install.\n\n"
343
- f"Please provide the 'package' parameter to install first."
344
- }
345
- ],
346
- }
347
-
348
- logger.info(f"Module {module} not found, installing package {package}")
349
- install_result = _install_package(package)
350
- if install_result["status"] == "error":
351
- return install_result
352
-
353
- # Try importing again after installation
354
- try:
355
- imported_module = importlib.import_module(module)
356
- except ImportError as e:
357
- return {
358
- "status": "error",
359
- "content": [
360
- {
361
- "text": f"❌ Failed to import {module} even after installing {package}: {str(e)}"
362
- }
363
- ],
364
- }
365
-
366
- # Discover tools in the module
367
- available_tools = {}
368
- for attr_name in dir(imported_module):
369
- attr = getattr(imported_module, attr_name)
370
- # Check if it's a tool (has tool_name and tool_spec attributes)
371
- if hasattr(attr, "tool_name") and hasattr(attr, "tool_spec"):
372
- tool_spec = attr.tool_spec
373
- description = tool_spec.get("description", "No description available")
374
- available_tools[attr.tool_name] = description
375
-
376
- if not available_tools:
377
- return {
378
- "status": "success",
379
- "content": [{"text": f"⚠️ No tools found in module: {module}"}],
380
- }
381
-
382
- # Build result message
383
- result_lines = [
384
- f"📦 **Available Tools in {module} ({len(available_tools)})**\n"
385
- ]
386
-
387
- for tool_name, description in sorted(available_tools.items()):
388
- # Truncate long descriptions
389
- if len(description) > 100:
390
- description = description[:97] + "..."
391
-
392
- result_lines.append(f"**{tool_name}**")
393
- result_lines.append(f" {description}\n")
394
-
395
- result_lines.append(f"\n💡 To load these tools, use:")
396
- result_lines.append(f" install_tools(action='load', module='{module}')")
397
- result_lines.append(f" # Or load specific tools:")
398
- result_lines.append(
399
- f" install_tools(action='load', module='{module}', tool_names=['tool1', 'tool2'])"
400
- )
401
-
402
- return {"status": "success", "content": [{"text": "\n".join(result_lines)}]}
403
-
404
- except Exception as e:
405
- logger.exception(f"Error listing available tools from {module}")
406
- return {
407
- "status": "error",
408
- "content": [{"text": f"❌ Failed to list available tools: {str(e)}"}],
409
- }
@@ -190,11 +190,9 @@ def _start_mcp_server(
190
190
  agent_invoke_tool = types.Tool(
191
191
  name="devduck",
192
192
  description=(
193
- "Invoke a FULL DevDuck instance with complete capabilities. "
194
- "Each invocation creates a fresh DevDuck agent with self-healing, "
195
- "hot-reload, all tools, knowledge base integration, and system prompt building. "
196
- "Use this for complex queries requiring reasoning, multi-tool orchestration, "
197
- "or when you need the complete DevDuck experience via MCP."
193
+ "Invoke the full DevDuck agent with a natural language prompt. "
194
+ "Use this for complex queries that require reasoning across multiple tools "
195
+ "or when you need a conversational response from the agent."
198
196
  ),
199
197
  inputSchema={
200
198
  "type": "object",
@@ -230,7 +228,7 @@ def _start_mcp_server(
230
228
  try:
231
229
  logger.debug(f"call_tool: name={name}, arguments={arguments}")
232
230
 
233
- # Handle agent invocation tool - create a full DevDuck instance
231
+ # Handle agent invocation tool - use the devduck instance directly
234
232
  if name == "devduck" and expose_agent:
235
233
  prompt = arguments.get("prompt")
236
234
  if not prompt:
@@ -243,34 +241,8 @@ def _start_mcp_server(
243
241
 
244
242
  logger.debug(f"Invoking devduck with prompt: {prompt[:100]}...")
245
243
 
246
- # Create a NEW DevDuck instance for this MCP invocation
247
- # This gives full DevDuck power: self-healing, hot-reload, all tools, etc.
248
- try:
249
- from devduck import DevDuck
250
-
251
- # Create fresh DevDuck instance (no auto-start to avoid recursion)
252
- mcp_devduck = DevDuck(auto_start_servers=False)
253
- mcp_agent = mcp_devduck.agent
254
-
255
- if not mcp_agent:
256
- return [
257
- types.TextContent(
258
- type="text",
259
- text="❌ Error: Failed to create DevDuck instance",
260
- )
261
- ]
262
-
263
- # Execute with full DevDuck capabilities
264
- result = mcp_agent(prompt)
265
-
266
- except Exception as e:
267
- logger.error(f"DevDuck creation failed: {e}", exc_info=True)
268
- return [
269
- types.TextContent(
270
- type="text",
271
- text=f"❌ Error creating DevDuck instance: {str(e)}",
272
- )
273
- ]
244
+ # Use the devduck agent directly (don't create a new instance)
245
+ result = agent(prompt)
274
246
 
275
247
  # Extract text response from agent result
276
248
  response_text = str(result)
devduck/tools/tcp.py CHANGED
@@ -216,6 +216,10 @@ def handle_client(
216
216
  )
217
217
 
218
218
  try:
219
+ # Send welcome message
220
+ welcome_msg = "🦆 Welcome to DevDuck TCP Server!\n"
221
+ welcome_msg += "Send a message or 'exit' to close the connection.\n\n"
222
+ streaming_handler._send(welcome_msg)
219
223
 
220
224
  while True:
221
225
  # Receive data from the client
@@ -316,12 +320,6 @@ def run_server(
316
320
  if SERVER_THREADS[port]["running"]:
317
321
  logger.error(f"Error accepting connection: {e}")
318
322
 
319
- except OSError as e:
320
- # Port conflict - handled upstream, no need for scary errors
321
- if "Address already in use" in str(e):
322
- logger.debug(f"Port {port} unavailable (handled upstream)")
323
- else:
324
- logger.error(f"Server error on {host}:{port}: {e}")
325
323
  except Exception as e:
326
324
  logger.error(f"Server error on {host}:{port}: {e}")
327
325
  finally:
@@ -347,14 +347,8 @@ def run_websocket_server(
347
347
  asyncio.set_event_loop(loop)
348
348
  WS_SERVER_THREADS[port]["loop"] = loop
349
349
  loop.run_until_complete(start_server())
350
- except OSError as e:
351
- # Port conflict - handled upstream, no need for scary errors
352
- if "Address already in use" in str(e) or "address already in use" in str(e):
353
- logger.debug(f"Port {port} unavailable (handled upstream)")
354
- else:
355
- logger.error(f"WebSocket server error on {host}:{port}: {e}")
356
350
  except Exception as e:
357
- logger.error(f"WebSocket server error on {host}:{port}: {e}")
351
+ logger.error(f"WebSocket server error on {host}:{port}: {e}", exc_info=True)
358
352
  finally:
359
353
  logger.info(f"WebSocket Server on {host}:{port} stopped")
360
354
  WS_SERVER_THREADS[port]["running"] = False
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: devduck
3
+ Version: 0.3.0
4
+ Summary: 🦆 Extreme minimalist self-adapting AI agent - one file, self-healing, runtime dependencies
5
+ Author-email: duck <hey@devduck.dev>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/cagataycali/devduck
8
+ Project-URL: Repository, https://github.com/cagataycali/devduck.git
9
+ Project-URL: Documentation, https://github.com/cagataycali/devduck#readme
10
+ Project-URL: Bug Tracker, https://github.com/cagataycali/devduck/issues
11
+ Keywords: ai,agent,minimalist,self-healing,ollama,strands-agents
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: System :: Systems Administration
25
+ Classifier: Topic :: Utilities
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: strands-agents
30
+ Requires-Dist: prompt_toolkit
31
+ Requires-Dist: strands-agents[ollama]
32
+ Requires-Dist: strands-agents-tools
33
+ Requires-Dist: strands-agentcore-tools
34
+ Requires-Dist: beautifulsoup4
35
+ Requires-Dist: colorama
36
+ Requires-Dist: websockets
37
+ Provides-Extra: all
38
+ Requires-Dist: strands-agents[openai]; extra == "all"
39
+ Requires-Dist: strands-agents[anthropic]; extra == "all"
40
+ Requires-Dist: strands-fun-tools[audio]; extra == "all"
41
+ Requires-Dist: strands-fun-tools[vision]; extra == "all"
42
+ Requires-Dist: strands-fun-tools[all]; extra == "all"
43
+ Requires-Dist: strands-mcp-server; extra == "all"
44
+ Dynamic: license-file
45
+
46
+ # 🦆 DevDuck
47
+
48
+ **One file. Self-healing. Adaptive.**
49
+
50
+ Minimalist AI agent that fixes itself when things break.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ # Minimal install
56
+ pipx install devduck
57
+
58
+ # Full install (all tools)
59
+ pipx install "devduck[all]"
60
+ ```
61
+
62
+ Requires: Python 3.10+, Ollama running (or set MODEL_PROVIDER)
63
+
64
+ ## Use
65
+
66
+ ```bash
67
+ # Start DevDuck (auto-starts TCP, WebSocket, MCP servers)
68
+ devduck
69
+
70
+ # CLI mode
71
+ devduck "what's the time?"
72
+
73
+ # Python
74
+ import devduck
75
+ devduck("calculate 2+2")
76
+ ```
77
+
78
+ ## Auto-Started Servers
79
+
80
+ When you run `devduck`, three servers start automatically:
81
+
82
+ - **🌐 Web UI**: [http://cagataycali.github.io/devduck](http://cagataycali.github.io/devduck) (auto-connects)
83
+ - **🔌 TCP**: `nc localhost 9999` (raw socket)
84
+ - **🌊 WebSocket**: `ws://localhost:8080` (structured JSON)
85
+ - **🔗 MCP**: `http://localhost:8000/mcp` (Model Context Protocol)
86
+
87
+ ### Connect via MCP
88
+
89
+ Add to your MCP client (e.g., Claude Desktop):
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "devduck": {
95
+ "command": "uvx",
96
+ "args": [
97
+ "strands-mcp-server",
98
+ "--upstream-url",
99
+ "http://localhost:8000/mcp/"
100
+ ],
101
+ "disabled": false
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### Connect via Terminal
108
+
109
+ ```bash
110
+ # Direct TCP connection
111
+ nc localhost 9999
112
+ > what's the time?
113
+ ```
114
+
115
+ ## Features
116
+
117
+ - **Self-healing** - Auto-fixes deps, models, errors
118
+ - **Hot-reload** - Create tools in `./tools/*.py`, use instantly
119
+ - **Adaptive** - Picks model based on OS (macOS: 1.7b, Linux: 30b)
120
+ - **14 tools** - shell, editor, files, python, calculator, tcp, etc.
121
+ - **History aware** - Remembers shell/conversation context
122
+ - **Multi-protocol** - TCP, WebSocket, MCP, CLI, Python
123
+
124
+ ## Create Tool
125
+
126
+ ```python
127
+ # ./tools/greet.py
128
+ from strands import tool
129
+
130
+ @tool
131
+ def greet(name: str) -> str:
132
+ return f"Hello {name}!"
133
+ ```
134
+
135
+ Save. Done. Use immediately.
136
+
137
+ ## Multi-Model
138
+
139
+ ```bash
140
+ export MODEL_PROVIDER="bedrock"
141
+ export STRANDS_MODEL_ID="us.anthropic.claude-sonnet-4-5-20250929-v1:0"
142
+ export STRANDS_ADDITIONAL_REQUEST_FIELDS='{"anthropic_beta": ["interleaved-thinking-2025-05-14", "context-1m-2025-08-07"], "thinking": {"type": "enabled", "budget_tokens": 2048}}'
143
+ export STRANDS_MAX_TOKENS="64000"
144
+
145
+ devduck "analyze data"
146
+ ```
147
+
148
+ ---
149
+
150
+ **Quack.** 🦆
151
+
152
+ *Built with [Strands Agents SDK](https://github.com/strands-agents/sdk-python)*
@@ -0,0 +1,18 @@
1
+ devduck/__init__.py,sha256=L_wVHAoQSMcnZaz-8vhGir9itE7RGq7jqSqTN6Uya1A,58613
2
+ devduck/__main__.py,sha256=aeF2RR4k7lzSR2X1QKV9XQPCKhtsH0JYUv2etBBqmL0,145
3
+ devduck/_version.py,sha256=5zTqm8rgXsWYBpB2M3Zw_K1D-aV8wP7NsBLrmMKkrAQ,704
4
+ devduck/test_redduck.py,sha256=nqRchR7d54jWGx7JN5tji2ZV4Ek4L9s-P7hp0mKjA0Y,1773
5
+ devduck/tools/__init__.py,sha256=mu3V4jL2ACN4f-pnUID_A2p6o3Yc_-V_y9071PduCR0,177
6
+ devduck/tools/create_subagent.py,sha256=UzRz9BmU4PbTveZROEpZ311aH-u-i6x89gttu-CniAE,24687
7
+ devduck/tools/install_tools.py,sha256=wm_67b9IfY-2wRuWgxuEKhaSIV5vNfbGmZL3G9dGi2A,10348
8
+ devduck/tools/mcp_server.py,sha256=oyF1gb7K-OlxyJLUO3L-vNo2ajKzIrcnT1crwKMOkhU,20118
9
+ devduck/tools/store_in_kb.py,sha256=-JM-oRQKR3FBubKHFHmXRnZSvi9dVgHxG0lismMgG2k,6861
10
+ devduck/tools/tcp.py,sha256=HkJ_j1t7hsPMxNL51bYHvPkHoTfro9Nov6vClwvwkEk,21943
11
+ devduck/tools/use_github.py,sha256=nr3JSGk48mKUobpgW__2gu6lFyUj93a1XRs3I6vH8W4,13682
12
+ devduck/tools/websocket.py,sha256=lRJZt813iHorVr5UI66Lq-lmaFuLYAfpodeV2gtda7k,16635
13
+ devduck-0.3.0.dist-info/licenses/LICENSE,sha256=CVGEiNh6cW1mgAKW83Q0P4xrQEXvqc6W-rb789W_IHM,1060
14
+ devduck-0.3.0.dist-info/METADATA,sha256=pg0OIugkyVKbHA39NYYBJxr0MRVunCI9-XtwWr9Pcn4,4243
15
+ devduck-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ devduck-0.3.0.dist-info/entry_points.txt,sha256=BAMQaIg_BLZQOTk12bT7hy1dE9oGPLt-_dTbI4cnBnQ,40
17
+ devduck-0.3.0.dist-info/top_level.txt,sha256=ySXWlVronp8xHYfQ_Hdfr463e0EnbWuqyuxs94EU7yk,8
18
+ devduck-0.3.0.dist-info/RECORD,,
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
- dev = devduck:cli
3
2
  devduck = devduck:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 maxs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,76 +0,0 @@
1
- #!/usr/bin/env python3
2
- """DevDuck AgentCore Handler"""
3
- import json
4
- import os
5
- import threading
6
- from bedrock_agentcore.runtime import BedrockAgentCoreApp
7
-
8
- # Configure for AgentCore deployment
9
- os.environ["DEVDUCK_AUTO_START_SERVERS"] = "false"
10
- os.environ["MODEL_PROVIDER"] = "bedrock"
11
-
12
- from devduck import devduck
13
-
14
- app = BedrockAgentCoreApp()
15
-
16
-
17
- @app.entrypoint
18
- async def invoke(payload, context):
19
- """AgentCore entrypoint - streaming by default with async generator"""
20
- mode = payload.get("mode", "streaming") # streaming (default), sync, async
21
-
22
- query = payload.get("prompt", payload.get("text", ""))
23
- if not query:
24
- yield {"error": "No query provided"}
25
- return
26
-
27
- print(f"Mode: {mode}, Query: {query}")
28
-
29
- agent = devduck.agent
30
-
31
- if mode == "sync":
32
- # Sync mode - return result directly (blocking)
33
- try:
34
- result = agent(query)
35
- yield {"statusCode": 200, "response": str(result)}
36
- except Exception as e:
37
- print(f"Error in sync: {str(e)}")
38
- yield {"statusCode": 500, "error": str(e)}
39
-
40
- elif mode == "async":
41
- # Async mode - fire and forget in background thread
42
- task_id = app.add_async_task("devduck_processing", payload)
43
- thread = threading.Thread(
44
- target=lambda: _run_in_thread(agent, query, task_id), daemon=True
45
- )
46
- thread.start()
47
- yield {"statusCode": 200, "task_id": task_id}
48
-
49
- else:
50
- # Streaming mode (default) - stream events as they happen
51
- try:
52
- stream = agent.stream_async(query)
53
- async for event in stream:
54
- print(event)
55
- yield event
56
- except Exception as e:
57
- print(f"Error in streaming: {str(e)}")
58
- yield {"error": str(e)}
59
-
60
-
61
- def _run_in_thread(agent, query, task_id):
62
- """Run agent in background thread for async mode"""
63
- try:
64
- result = agent(query)
65
- print(f"DevDuck result: {result}")
66
- app.complete_async_task(task_id)
67
- except Exception as e:
68
- print(f"Error in async thread: {str(e)}")
69
- try:
70
- app.complete_async_task(task_id)
71
- except:
72
- pass
73
-
74
-
75
- if __name__ == "__main__":
76
- app.run()