10xscale-agentflow-cli 0.1.8__tar.gz → 0.2.0__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 (92) hide show
  1. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/10xscale_agentflow_cli.egg-info/PKG-INFO +2 -2
  2. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/10xscale_agentflow_cli.egg-info/requires.txt +1 -1
  3. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/PKG-INFO +2 -2
  4. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/auth/jwt_auth.py +6 -1
  5. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/exceptions/handle_errors.py +1 -15
  6. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/loader.py +8 -0
  7. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/checkpointer/services/checkpointer_service.py +2 -1
  8. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/graph/services/graph_service.py +62 -56
  9. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/parse_output.py +4 -4
  10. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/graph/react.py +0 -74
  11. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/pyproject.toml +2 -2
  12. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/tests/test_utils_parse_and_callable.py +2 -5
  13. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/10xscale_agentflow_cli.egg-info/SOURCES.txt +0 -0
  14. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/10xscale_agentflow_cli.egg-info/dependency_links.txt +0 -0
  15. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/10xscale_agentflow_cli.egg-info/entry_points.txt +0 -0
  16. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/10xscale_agentflow_cli.egg-info/not-zip-safe +0 -0
  17. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/10xscale_agentflow_cli.egg-info/top_level.txt +0 -0
  18. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/MANIFEST.in +0 -0
  19. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/README.md +0 -0
  20. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/Task.md +0 -0
  21. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow.json +0 -0
  22. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/__init__.py +0 -0
  23. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/__init__.py +0 -0
  24. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/commands/__init__.py +0 -0
  25. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/commands/api.py +0 -0
  26. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/commands/build.py +0 -0
  27. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/commands/init.py +0 -0
  28. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/commands/version.py +0 -0
  29. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/constants.py +0 -0
  30. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/core/__init__.py +0 -0
  31. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/core/config.py +0 -0
  32. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/core/output.py +0 -0
  33. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/core/validation.py +0 -0
  34. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/exceptions.py +0 -0
  35. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/logger.py +0 -0
  36. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/main.py +0 -0
  37. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/templates/__init__.py +0 -0
  38. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/cli/templates/defaults.py +0 -0
  39. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/__init__.py +0 -0
  40. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/__init__.py +0 -0
  41. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/__init__.py +0 -0
  42. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/auth/__init__.py +0 -0
  43. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/auth/auth_backend.py +0 -0
  44. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/auth/base_auth.py +0 -0
  45. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/config/__init__.py +0 -0
  46. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/config/graph_config.py +0 -0
  47. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/config/sentry_config.py +0 -0
  48. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/config/settings.py +0 -0
  49. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/config/setup_logs.py +0 -0
  50. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/config/setup_middleware.py +0 -0
  51. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/config/worker_middleware.py +0 -0
  52. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/exceptions/__init__.py +0 -0
  53. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/exceptions/general_exception.py +0 -0
  54. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/exceptions/resources_exceptions.py +0 -0
  55. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/core/exceptions/user_exception.py +0 -0
  56. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/main.py +0 -0
  57. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/__init__.py +0 -0
  58. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/checkpointer/__init__.py +0 -0
  59. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/checkpointer/router.py +0 -0
  60. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/checkpointer/schemas/__init__.py +0 -0
  61. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/checkpointer/schemas/checkpointer_schemas.py +0 -0
  62. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/checkpointer/services/__init__.py +0 -0
  63. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/graph/__init__.py +0 -0
  64. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/graph/router.py +0 -0
  65. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/graph/schemas/__init__.py +0 -0
  66. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/graph/schemas/graph_schemas.py +0 -0
  67. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/graph/services/__init__.py +0 -0
  68. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/ping/__init__.py +0 -0
  69. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/ping/router.py +0 -0
  70. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/setup_router.py +0 -0
  71. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/store/__init__.py +0 -0
  72. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/store/router.py +0 -0
  73. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/store/schemas/__init__.py +0 -0
  74. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/store/schemas/store_schemas.py +0 -0
  75. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/store/services/__init__.py +0 -0
  76. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/routers/store/services/store_service.py +0 -0
  77. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/tasks/__init__.py +0 -0
  78. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/tasks/user_tasks.py +0 -0
  79. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/__init__.py +0 -0
  80. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/callable_helper.py +0 -0
  81. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/response_helper.py +0 -0
  82. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/schemas/__init__.py +0 -0
  83. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/schemas/output_schemas.py +0 -0
  84. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/schemas/user_schemas.py +0 -0
  85. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/snowflake_id_generator.py +0 -0
  86. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/swagger_helper.py +0 -0
  87. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/utils/thread_name_generator.py +0 -0
  88. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/agentflow_cli/src/app/worker.py +0 -0
  89. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/graph/__init__.py +0 -0
  90. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/graph/thread_name_generator.py +0 -0
  91. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/scripts/generate_docs.py +0 -0
  92. {10xscale_agentflow_cli-0.1.8 → 10xscale_agentflow_cli-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 10xscale-agentflow-cli
3
- Version: 0.1.8
3
+ Version: 0.2.0
4
4
  Summary: CLI and API for 10xscale AgentFlow
5
5
  Author-email: 10xscale <contact@10xscale.ai>
6
6
  Maintainer-email: Shudipto Trafder <shudiptotrafder@gmail.com>
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
22
22
  Requires-Python: >=3.12
23
23
  Description-Content-Type: text/markdown
24
- Requires-Dist: 10xscale-agentflow>=0.4.0
24
+ Requires-Dist: 10xscale-agentflow>=0.5.0
25
25
  Requires-Dist: fastapi
26
26
  Requires-Dist: gunicorn
27
27
  Requires-Dist: orjson
@@ -1,4 +1,4 @@
1
- 10xscale-agentflow>=0.4.0
1
+ 10xscale-agentflow>=0.5.0
2
2
  fastapi
3
3
  gunicorn
4
4
  orjson
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 10xscale-agentflow-cli
3
- Version: 0.1.8
3
+ Version: 0.2.0
4
4
  Summary: CLI and API for 10xscale AgentFlow
5
5
  Author-email: 10xscale <contact@10xscale.ai>
6
6
  Maintainer-email: Shudipto Trafder <shudiptotrafder@gmail.com>
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
22
22
  Requires-Python: >=3.12
23
23
  Description-Content-Type: text/markdown
24
- Requires-Dist: 10xscale-agentflow>=0.4.0
24
+ Requires-Dist: 10xscale-agentflow>=0.5.0
25
25
  Requires-Dist: fastapi
26
26
  Requires-Dist: gunicorn
27
27
  Requires-Dist: orjson
@@ -44,6 +44,11 @@ class JwtAuth(BaseAuth):
44
44
  jwt_secret_key = os.environ.get("JWT_SECRET_KEY", None)
45
45
  jwt_algorithm = os.environ.get("JWT_ALGORITHM", None)
46
46
 
47
+ # check bearer token then remove barer prefix
48
+ token = credential.credentials
49
+ if token.lower().startswith("bearer "):
50
+ token = token[7:]
51
+
47
52
  if jwt_secret_key is None or jwt_algorithm is None:
48
53
  raise UserAccountError(
49
54
  message="JWT settings are not configured",
@@ -52,7 +57,7 @@ class JwtAuth(BaseAuth):
52
57
 
53
58
  try:
54
59
  decoded_token = jwt.decode(
55
- credential.credentials,
60
+ token,
56
61
  jwt_secret_key, # type: ignore
57
62
  algorithms=[jwt_algorithm], # type: ignore
58
63
  )
@@ -4,7 +4,6 @@ from agentflow.exceptions import (
4
4
  GraphRecursionError,
5
5
  MetricsError,
6
6
  NodeError,
7
- ResourceNotFoundError,
8
7
  SchemaVersionError,
9
8
  SerializationError,
10
9
  StorageError,
@@ -40,7 +39,7 @@ def init_errors_handler(app: FastAPI):
40
39
  ValueError: Handles value errors.
41
40
  UserAccountError: Handles custom user account errors.
42
41
  UserPermissionError: Handles custom user permission errors.
43
- ResourceNotFoundError: Handles custom resource not found errors.
42
+ APIResourceNotFoundError: Handles custom API resource not found errors.
44
43
  """
45
44
 
46
45
  @app.exception_handler(HTTPException)
@@ -208,16 +207,3 @@ def init_errors_handler(app: FastAPI):
208
207
  details=getattr(exc, "context", None),
209
208
  status_code=503,
210
209
  )
211
-
212
- @app.exception_handler(ResourceNotFoundError)
213
- async def resource_not_found_storage_exception_handler(
214
- request: Request, exc: ResourceNotFoundError
215
- ):
216
- logger.error(f"ResourceNotFoundError: url: {request.base_url}", exc_info=exc)
217
- return error_response(
218
- request,
219
- error_code=getattr(exc, "error_code", "RESOURCE_NOT_FOUND_000"),
220
- message=getattr(exc, "message", str(exc)),
221
- details=getattr(exc, "context", None),
222
- status_code=404,
223
- )
@@ -202,6 +202,14 @@ async def attach_all_modules(
202
202
  path,
203
203
  )
204
204
  container.bind_instance(BaseAuth, auth_backend)
205
+ elif method == "jwt":
206
+ from agentflow_cli.src.app.core.auth.jwt_auth import JwtAuth
207
+
208
+ jwt_auth = JwtAuth()
209
+ container.bind_instance(BaseAuth, jwt_auth)
210
+
211
+ elif method == "none":
212
+ container.bind_instance(BaseAuth, None, allow_none=True)
205
213
  else:
206
214
  # bind None
207
215
  container.bind_instance(BaseAuth, None, allow_none=True)
@@ -174,7 +174,8 @@ class CheckpointerService:
174
174
  base: dict[str, Any] = {}
175
175
  if old_state is not None:
176
176
  # Keep full dump so we can preserve existing fields
177
- base = old_state.model_dump()
177
+ # Use serialize_as_any=True to include subclass fields
178
+ base = old_state.model_dump(serialize_as_any=True)
178
179
 
179
180
  merged: dict[str, Any] = {**base}
180
181
 
@@ -247,7 +247,7 @@ class GraphService:
247
247
 
248
248
  return GraphInvokeOutputSchema(
249
249
  messages=messages,
250
- state=raw_state.model_dump() if raw_state else None,
250
+ state=raw_state.model_dump(serialize_as_any=True) if raw_state else None,
251
251
  context=context,
252
252
  summary=context_summary,
253
253
  meta=meta,
@@ -295,7 +295,7 @@ class GraphService:
295
295
  mt = chunk.metadata or {}
296
296
  mt.update(meta)
297
297
  chunk.metadata = mt
298
- yield chunk.model_dump_json()
298
+ yield chunk.model_dump_json(serialize_as_any=True)
299
299
  if (
300
300
  self.config.thread_name_generator_path
301
301
  and meta["is_new_thread"]
@@ -308,7 +308,6 @@ class GraphService:
308
308
  logger.info("Graph streaming completed successfully")
309
309
 
310
310
  if meta["is_new_thread"] and self.config.thread_name_generator_path:
311
- messages_str = [msg.text() for msg in messages_str]
312
311
  thread_name = await self._save_thread_name(
313
312
  config, config["thread_id"], messages_str
314
313
  )
@@ -318,7 +317,7 @@ class GraphService:
318
317
  event=StreamEvent.UPDATES,
319
318
  data={"status": "completed"},
320
319
  metadata=meta,
321
- ).model_dump_json()
320
+ ).model_dump_json(serialize_as_any=True)
322
321
 
323
322
  except Exception as e:
324
323
  logger.error(f"Graph streaming failed: {e}")
@@ -344,6 +343,25 @@ class GraphService:
344
343
  logger.error(f"Failed to get state schema: {e}")
345
344
  raise HTTPException(status_code=500, detail=f"Failed to get state schema: {e!s}")
346
345
 
346
+ def _has_empty_tool_call(self, msg: Message) -> bool:
347
+ """Return True if any tool call on the message has empty content.
348
+
349
+ A tool call is considered empty if its ``content`` attribute/key is ``None`` or
350
+ an empty string. Tool calls may be dict-like or objects with a ``content`` attribute.
351
+ """
352
+ tool_calls = getattr(msg, "tools_calls", None)
353
+ if not tool_calls:
354
+ return False
355
+ for tool_call in tool_calls:
356
+ content = (
357
+ tool_call.get("content")
358
+ if isinstance(tool_call, dict)
359
+ else getattr(tool_call, "content", None)
360
+ )
361
+ if content in (None, ""):
362
+ return True
363
+ return False
364
+
347
365
  async def fix_graph(
348
366
  self,
349
367
  thread_id: str,
@@ -372,66 +390,54 @@ class GraphService:
372
390
  Raises:
373
391
  HTTPException: If the operation fails
374
392
  """
393
+ try:
394
+ logger.info(f"Starting fix graph operation for thread: {thread_id}")
395
+ logger.debug(f"User info: {user}")
375
396
 
376
- logger.info(f"Starting fix graph operation for thread: {thread_id}")
377
- logger.debug(f"User info: {user}")
378
-
379
- fix_config = {
380
- "thread_id": thread_id,
381
- "user": user,
382
- }
383
-
384
- # Merge additional config if provided
385
- if config:
386
- fix_config.update(config)
387
-
388
- logger.debug("Fetching current state from checkpointer")
389
- state: AgentState | None = await self.checkpointer.aget_state(fix_config)
397
+ fix_config = {"thread_id": thread_id, "user": user}
398
+ if config:
399
+ fix_config.update(config)
400
+
401
+ logger.debug("Fetching current state from checkpointer")
402
+ state: AgentState | None = await self.checkpointer.aget_state(fix_config)
403
+ if not state:
404
+ logger.warning(f"No state found for thread: {thread_id}")
405
+ return {
406
+ "success": False,
407
+ "message": f"No state found for thread: {thread_id}",
408
+ "removed_count": 0,
409
+ "state": None,
410
+ }
390
411
 
391
- if not state:
392
- logger.warning(f"No state found for thread: {thread_id}")
393
- return {
394
- "success": False,
395
- "message": f"No state found for thread: {thread_id}",
396
- "removed_count": 0,
397
- "state": None,
398
- }
412
+ messages: list[Message] = list(state.context or [])
413
+ logger.debug(f"Found {len(messages)} messages in state")
414
+ if not messages:
415
+ return {
416
+ "success": True,
417
+ "message": "No messages found in state",
418
+ "removed_count": 0,
419
+ "state": state.model_dump_json(serialize_as_any=True),
420
+ }
399
421
 
400
- messages: list[Message] = state.context
401
- logger.debug(f"Found {len(messages)} messages in state")
422
+ filtered = [m for m in messages if not self._has_empty_tool_call(m)]
423
+ removed_count = len(messages) - len(filtered)
402
424
 
403
- if not messages:
404
- logger.info("No messages found in state, nothing to fix")
405
- return {
406
- "success": True,
407
- "message": "No messages found in state",
408
- "removed_count": 0,
409
- "state": state.model_dump_json(),
410
- }
425
+ if removed_count:
426
+ state.context = filtered
427
+ await self.checkpointer.aput_state(fix_config, state)
428
+ message = f"Successfully removed {removed_count} message(s)"
429
+ else:
430
+ message = "No messages with empty tool calls found"
411
431
 
412
- last_message = messages[-1]
413
- updated_context = []
414
- if last_message.role == "assistant" and last_message.tools_calls:
415
- updated_context = messages[:-1]
416
- state.context = updated_context
417
- await self.checkpointer.aput_state(fix_config, state)
418
432
  return {
419
433
  "success": True,
420
- "message": "Removed last assistant message with empty tool calls",
421
- "removed_count": 1,
422
- "state": state.model_dump_json(),
434
+ "message": message,
435
+ "removed_count": removed_count,
436
+ "state": state.model_dump_json(serialize_as_any=True),
423
437
  }
424
- else:
425
- logger.warning(
426
- "Last message is not an assistant message with tool calls, skipping it from checks."
427
- )
428
-
429
- return {
430
- "success": True,
431
- "message": "No messages with empty tool calls found",
432
- "removed_count": 0,
433
- "state": state.model_dump_json(),
434
- }
438
+ except Exception as e:
439
+ logger.error(f"Fix graph operation failed: {e}")
440
+ raise HTTPException(status_code=500, detail=f"Fix graph operation failed: {e!s}")
435
441
 
436
442
  async def setup(self, data: GraphSetupSchema) -> dict:
437
443
  # lets create tools
@@ -7,11 +7,11 @@ from agentflow_cli.src.app.core.config.settings import Settings
7
7
 
8
8
  def parse_state_output(settings: Settings, response: BaseModel) -> dict[str, Any]:
9
9
  # if settings.IS_DEBUG:
10
- # return response.model_dump(exclude={"execution_meta"})
11
- return response.model_dump()
10
+ # return response.model_dump(exclude={"execution_meta"}, serialize_as_any=True)
11
+ return response.model_dump(serialize_as_any=True)
12
12
 
13
13
 
14
14
  def parse_message_output(settings: Settings, response: BaseModel) -> dict[str, Any]:
15
15
  # if settings.IS_DEBUG:
16
- # return response.model_dump(exclude={"raw"})
17
- return response.model_dump()
16
+ # return response.model_dump(exclude={"raw"}, serialize_as_any=True)
17
+ return response.model_dump(serialize_as_any=True)
@@ -1,37 +1,3 @@
1
- """
2
- Graph-based React Agent Implementation
3
-
4
- This module implements a reactive agent system using PyAgenity's StateGraph.
5
- The agent can interact with tools (like weather checking) and maintain conversation
6
- state through a checkpointer. The graph orchestrates the flow between the main
7
- agent logic and tool execution.
8
-
9
- Key Components:
10
- - Weather tool: Demonstrates tool calling with dependency injection
11
- - Main agent: AI-powered assistant that can use tools
12
- - Graph flow: Conditional routing based on tool usage
13
- - Checkpointer: Maintains conversation state across interactions
14
-
15
- Architecture:
16
- The system uses a state graph with two main nodes:
17
- 1. MAIN: Processes user input and generates AI responses
18
- 2. TOOL: Executes tool calls when requested by the AI
19
-
20
- The graph conditionally routes between these nodes based on whether
21
- the AI response contains tool calls. Conversation history is maintained
22
- through the checkpointer, allowing for multi-turn conversations.
23
-
24
- Tools are defined as functions with JSON schema docstrings that describe
25
- their interface for the AI model. The ToolNode automatically extracts
26
- these schemas for tool selection.
27
-
28
- Dependencies:
29
- - PyAgenity: For graph and state management
30
- - LiteLLM: For AI model interactions
31
- - InjectQ: For dependency injection
32
- - Python logging: For debug and info messages
33
- """
34
-
35
1
  import logging
36
2
  from typing import Any
37
3
 
@@ -70,46 +36,6 @@ class MyAgentState(AgentState):
70
36
  checkpointer = InMemoryCheckpointer[MyAgentState]()
71
37
 
72
38
 
73
- """
74
- Note: The docstring below will be used as the tool description and it will be
75
- passed to the AI model for tool selection, so keep it relevant and concise.
76
- This function will be converted to a tool with the following schema:
77
- [
78
- {
79
- 'type': 'function',
80
- 'function': {
81
- 'name': 'get_weather',
82
- 'description': 'Retrieve current weather information for a specified location.',
83
- 'parameters': {
84
- 'type': 'object',
85
- 'properties': {
86
- 'location': {'type': 'string'}
87
- },
88
- 'required': ['location']
89
- }
90
- }
91
- }
92
- ]
93
-
94
- Parameters like tool_call_id, state, and checkpointer are injected automatically
95
- by InjectQ when the tool is called by the agent.
96
- Available injected parameters:
97
- The following parameters are automatically injected by InjectQ when the tool is called,
98
- but need to keep them as same name and type for proper injection:
99
- - tool_call_id: Unique ID for the tool call
100
- - state: Current AgentState containing conversation context
101
- - config: Configuration dictionary passed during graph invocation
102
-
103
- Below fields need to be used with Inject[] to get the instances:
104
- - context_manager: ContextManager instance for managing context, like trimming
105
- - publisher: Publisher instance for publishing events and logs
106
- - checkpointer: InMemoryCheckpointer instance for state management
107
- - store: InMemoryStore instance for temporary data storage
108
- - callback: CallbackManager instance for handling callbacks
109
-
110
- """
111
-
112
-
113
39
  def get_weather(
114
40
  location: str,
115
41
  tool_call_id: str,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "10xscale-agentflow-cli"
7
- version = "0.1.8"
7
+ version = "0.2.0"
8
8
  description = "CLI and API for 10xscale AgentFlow"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -34,7 +34,7 @@ classifiers = [
34
34
  "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
35
35
  ]
36
36
  dependencies = [
37
- "10xscale-agentflow>=0.4.0",
37
+ "10xscale-agentflow>=0.5.0",
38
38
  "fastapi",
39
39
  "gunicorn",
40
40
  "orjson",
@@ -28,9 +28,7 @@ def test_parse_state_output(is_debug: bool):
28
28
  settings = Settings(IS_DEBUG=is_debug)
29
29
  model = _StateModel(a=1, b="x", execution_meta={"duration": 123})
30
30
  out = parse_state_output(settings, model)
31
- # execution_meta excluded only in debug mode per implementation
32
- # Since parse_state_output doesn't filter execution_meta (commented out),
33
- # it should always be present regardless of debug mode
31
+ # Current implementation always includes execution_meta regardless of debug
34
32
  assert out["execution_meta"] == {"duration": 123}
35
33
  assert out["a"] == 1 and out["b"] == "x"
36
34
 
@@ -40,8 +38,7 @@ def test_parse_message_output(is_debug: bool):
40
38
  settings = Settings(IS_DEBUG=is_debug)
41
39
  model = _MessageModel(content="hello", raw={"tokens": 5})
42
40
  out = parse_message_output(settings, model)
43
- # Since parse_message_output doesn't filter raw (commented out),
44
- # it should always be present regardless of debug mode
41
+ # Current implementation always includes raw regardless of debug
45
42
  assert out["raw"] == {"tokens": 5}
46
43
  assert out["content"] == "hello"
47
44