mistralai-workflows-cli 1.2.1__tar.gz → 1.2.2__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 (98) hide show
  1. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/.gitignore +3 -0
  2. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/PKG-INFO +1 -1
  3. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/pyproject.toml +1 -2
  4. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/SKILL.md +3 -0
  5. mistralai_workflows_cli-1.2.2/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/connectors.mdx +178 -0
  6. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/scripts/test_workflow.py +31 -59
  7. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/start.py +1 -3
  8. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/worker.py +3 -5
  9. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/cargo_release/activities.py +5 -15
  10. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/cargo_release/models.py +3 -9
  11. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/cargo_release/workflow.py +4 -11
  12. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/activities.py +7 -23
  13. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/models.py +4 -12
  14. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/workflow.py +2 -6
  15. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/activities.py +1 -3
  16. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/models.py +0 -1
  17. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/workflow.py +1 -4
  18. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/linear_summarization/__init__.py +1 -1
  19. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/linear_summarization/linear_activities.py +5 -20
  20. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/linear_summarization/summary_activity.py +2 -8
  21. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/linear_summarization/workflow.py +2 -7
  22. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/worker.py +2 -3
  23. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/workflows/hello.py +1 -2
  24. {mistralai_workflows_cli-1.2.1/src/mistral_workflows_cli/_template/template/src → mistralai_workflows_cli-1.2.2/src/mistral_workflows_cli/_template/template}/worker.py +1 -1
  25. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/Makefile +0 -0
  26. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/README.md +0 -0
  27. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/__init__.py +0 -0
  28. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/copier.yml +0 -0
  29. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/execution_ids.md +0 -0
  30. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/getting-started/core-concepts.mdx +0 -0
  31. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/getting-started/installation.mdx +0 -0
  32. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/getting-started/introduction.mdx +0 -0
  33. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/getting-started/python-sdk.mdx +0 -0
  34. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/getting-started/value-proposition.mdx +0 -0
  35. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/getting-started/your-first-workflow.mdx +0 -0
  36. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/_deployment-patterns.mdx +0 -0
  37. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/activities.mdx +0 -0
  38. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/assist-workflows.mdx +0 -0
  39. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/concurrency.mdx +0 -0
  40. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/dependency-injection.mdx +0 -0
  41. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/diagnostics.md +0 -0
  42. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/durable-agents.mdx +0 -0
  43. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/error-codes.mdx +0 -0
  44. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/handling-large-data.mdx +0 -0
  45. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/limitations.mdx +0 -0
  46. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/local-execution.mdx +0 -0
  47. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/migration-v2-to-v3.mdx +0 -0
  48. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/observability.mdx +0 -0
  49. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/payload-encoding.mdx +0 -0
  50. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/rate-limiting.mdx +0 -0
  51. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/scheduling.mdx +0 -0
  52. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/signals-queries-updates.mdx +0 -0
  53. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/streaming-consumption.mdx +0 -0
  54. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/streaming.mdx +0 -0
  55. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/testing.md +0 -0
  56. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/workflows-exception.mdx +0 -0
  57. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/workflows-plugins.mdx +0 -0
  58. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/workflows.mdx +0 -0
  59. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/pipeline_pattern.md +0 -0
  60. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/workflow_testing.md +0 -0
  61. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.env.jinja +0 -0
  62. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.gitignore +0 -0
  63. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/Makefile +0 -0
  64. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/README.md.jinja +0 -0
  65. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/pyproject.toml.jinja +0 -0
  66. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/__init__.py +0 -0
  67. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/dev.py +0 -0
  68. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/__init__.py +0 -0
  69. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/cargo_release/README.md +0 -0
  70. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/cargo_release/__init__.py +0 -0
  71. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/cargo_release/sample_data/shipping_doc_anomaly.png +0 -0
  72. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/cargo_release/sample_data/shipping_doc_normal.png +0 -0
  73. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/README.md +0 -0
  74. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/__init__.py +0 -0
  75. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/sample_data/legacy_repo/__init__.py +0 -0
  76. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/sample_data/legacy_repo/api_client.py +0 -0
  77. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/sample_data/legacy_repo/processor.py +0 -0
  78. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/sample_data/legacy_repo/utils.py +0 -0
  79. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/code_modernization/sub_workflow.py +0 -0
  80. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/README.md +0 -0
  81. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/__init__.py +0 -0
  82. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/claim_high_severity.json +0 -0
  83. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/claim_low_severity.json +0 -0
  84. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/claim_medium_severity.json +0 -0
  85. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/photos/CREDITS.md +0 -0
  86. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/photos/claim_high_totaled_front.jpg +0 -0
  87. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/photos/claim_high_totaled_side.jpg +0 -0
  88. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/photos/claim_low_scratch_door.jpg +0 -0
  89. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/insurance_claims/sample_data/photos/claim_medium_dent_rear.jpg +0 -0
  90. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/linear_summarization/README.md +0 -0
  91. {mistralai_workflows_cli-1.2.1/src/mistral_workflows_cli/_template/template → mistralai_workflows_cli-1.2.2/src/mistral_workflows_cli/_template/template/src}/worker.py +0 -0
  92. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/workflows/__init__.py +0 -0
  93. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_version.py +0 -0
  94. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/main.py +0 -0
  95. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/setup.py +0 -0
  96. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/tests/__init__.py +0 -0
  97. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/tests/test_setup.py +0 -0
  98. {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/uv.lock +0 -0
@@ -219,6 +219,9 @@ jobs/run/checkpoints/test/consolidated/params.json
219
219
 
220
220
  # Cursor plans folder
221
221
  .cursor/plans/
222
+
223
+ # Agent plans (scratch planning docs)
224
+ .agents/plans/
222
225
  tsconfig.tsbuildinfo
223
226
 
224
227
  .vscode-test/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mistralai-workflows-cli
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: CLI to bootstrap Mistral Workflows projects
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: click>=8.0
@@ -32,8 +32,7 @@ packages = ["src/mistral_workflows_cli"]
32
32
 
33
33
  [tool.ruff]
34
34
  line-length = 120
35
- # The copier template is project data, not part of the CLI's own source.
36
- extend-exclude = ["src/mistral_workflows_cli/_template"]
35
+ extend-exclude = ["src/mistral_workflows_cli/_template/template/src/examples/code_modernization/sample_data/legacy_repo"]
37
36
 
38
37
  [tool.ruff.lint]
39
38
  select = ["E", "F", "I", "UP"]
@@ -73,6 +73,7 @@ The documentation is organized into several categories:
73
73
  - **[Payload Encoding](references/guides/payload-encoding.mdx)**: Payload offloading, AES-GCM encryption, key rotation
74
74
  - **[Observability](references/guides/observability.mdx)**: OpenTelemetry traces, trace sampling
75
75
  - **[Durable Agents](references/guides/durable-agents.mdx)**: Agent, Runner, RemoteSession/LocalSession, MCP, multi-agent handoffs
76
+ - **[Connectors](references/guides/connectors.mdx)**: Call third-party tools (GitHub, Notion, Slack, ...) from a workflow or give them to an agent, without holding the service's credentials
76
77
  - **[Conversational Workflows](references/guides/assist-workflows.mdx)**: InteractiveWorkflow, HITL, ChatInput/FormInput, Canvas editing, Rich UI components, Tool UI states
77
78
  - **[Local Execution](references/guides/local-execution.mdx)**: No-infra dev mode with Pydantic model params
78
79
  - **[Limitations](references/guides/limitations.mdx)**: System constraints and limits
@@ -121,6 +122,7 @@ Use this skill when you need to:
121
122
  7. **Build conversational workflows**: Create interactive workflows with HITL, forms, canvas, and rich UI
122
123
  8. **Ensure fault tolerance**: Implement systems that can recover from failures automatically
123
124
  9. **Stream events**: Real-time token streaming and progress updates via NATS
125
+ 10. **Integrate external services**: Call GitHub, Notion, Slack, and other connectors with platform-managed credentials
124
126
 
125
127
  ## Key Features
126
128
 
@@ -135,5 +137,6 @@ Use this skill when you need to:
135
137
  - **Large payload handling**: OffloadableField with S3/Azure/GCS blob storage
136
138
  - **Conversational workflows**: Interactive workflows with Vibe integration, forms, canvas, and rich UI components
137
139
  - **Durable agents**: AI agents with MCP support, multi-agent handoffs, and persistent state
140
+ - **Connectors**: Call external services with platform-managed credentials avoiding secrets in the workflow code
138
141
  - **Scalability**: Designed to handle complex, distributed applications
139
142
 
@@ -0,0 +1,178 @@
1
+ ---
2
+ id: connectors
3
+ title: Connectors
4
+ sidebar_position: 8
5
+ ---
6
+
7
+ # Connectors in Workflows
8
+
9
+ Connectors let workflows call external services (GitHub, Notion, Slack, ...) using credentials managed by the Mistral Connectors platform. The workflow **declares** which connectors it needs; the platform resolves credentials and drives any OAuth flow automatically. The same code works whether the connector uses a bearer/PAT token or OAuth2. **You never put secrets in workflow code**.
10
+
11
+ Connectors ship in the Mistral plugin:
12
+
13
+ ```bash
14
+ uv add 'mistralai-workflows[mistralai]'
15
+ ```
16
+
17
+ ### Discovering configured connectors
18
+
19
+ The `name` you pass to `connector(...)` must match a connector already configured for the workspace, don't guess them ever! To see the configured connectors and their exact names, run:
20
+
21
+ ```bash
22
+ uv run python -c "from dotenv import load_dotenv; load_dotenv(); import os; from mistralai.client import Mistral; print(*[c.name for c in Mistral(api_key=os.environ['MISTRAL_API_KEY']).beta.connectors.list().items], sep='\n')"
23
+ ```
24
+
25
+ For a connector's tools, use `list_tools` (returns a plain list, not `.items`) and don't guess them even if you think you know how they must be called!:
26
+
27
+ ```bash
28
+ uv run python -c "from dotenv import load_dotenv; load_dotenv(); import os; from mistralai.client import Mistral; print(*[t.name for t in Mistral(api_key=os.environ['MISTRAL_API_KEY']).beta.connectors.list_tools(connector_id_or_name='github_app')], sep='\n')"
29
+ ```
30
+
31
+ Each tool also has `.jsonschema` for its `arguments` (add `t.jsonschema` to the print when you need it).
32
+ (You only need these calls to figure out how to write the code. Don't put these snippets in the workflow itself.)
33
+
34
+ ## Declaring connectors in a workflow
35
+
36
+ Three pieces wire a connector in:
37
+
38
+ 1. `connector("name")` — declares a **connector slot**.
39
+ 2. `@uses_connectors(...)` — attaches slots to the workflow class; apply it **after** `@workflow.define` (on the line below).
40
+ 3. `Depends(slot)` — injects a `ToolCallClient` into an activity.
41
+
42
+ ```python
43
+ import pydantic
44
+ import mistralai.workflows as workflows
45
+ from mistralai.workflows import Depends
46
+ from mistralai.workflows.plugins.mistralai.connectors import (
47
+ ToolCallClient,
48
+ connector,
49
+ uses_connectors,
50
+ )
51
+
52
+ github = connector("github_app")
53
+
54
+
55
+ class CreateIssueInput(pydantic.BaseModel):
56
+ owner: str
57
+ repo: str
58
+ title: str
59
+ body: str
60
+
61
+
62
+ @workflows.activity(name="create-github-issue")
63
+ async def create_github_issue(
64
+ owner: str,
65
+ repo: str,
66
+ title: str,
67
+ body: str,
68
+ client: ToolCallClient = Depends(github),
69
+ ) -> None:
70
+ await client.call_tool(
71
+ tool_name="issue_write",
72
+ arguments={
73
+ "method": "create",
74
+ "owner": owner,
75
+ "repo": repo,
76
+ "title": title,
77
+ "body": body,
78
+ },
79
+ )
80
+
81
+
82
+ @workflows.workflow.define(name="github-issue-creator")
83
+ @uses_connectors(github)
84
+ class GitHubIssueCreatorWorkflow:
85
+ @workflows.workflow.entrypoint
86
+ async def run(self, prompt: CreateIssueInput) -> None:
87
+ await create_github_issue(prompt.owner, prompt.repo, prompt.title, prompt.body)
88
+ ```
89
+
90
+ Watch out for:
91
+
92
+ - **Import path** is `mistralai.workflows.plugins.mistralai.connectors` (repeated `mistralai`), not `...plugins.connectors`.
93
+ - **`@uses_connectors` is required** and sits **below** `@workflow.define`. Without it, credentials are never resolved and `call_tool` fails.
94
+ - A `ToolCallClient` only works **inside an activity** (it resolves credentials from the workflow context at call time). A failed call raises `ConnectorToolCallError` (non-retryable).
95
+ - **Tool names and argument schemas are connector-specific** (e.g. GitHub's `issue_write`, Notion's `notion-create-pages`) and aren't inferable from the SDK types. Discover them with `client.beta.connectors.list_tools_async(connector_id_or_name=...)` or take them from the task — don't guess.
96
+ - **No special worker setup** — the Mistral plugin auto-registers the connector auth interceptor; run on a normal worker.
97
+
98
+ ### `connector(...)` parameters
99
+
100
+ | Parameter | Default | Purpose |
101
+ | --- | --- | --- |
102
+ | `name` (positional) | — | Must match a connector configured for the workspace; discover via `client.beta.connectors.list_async()`. |
103
+ | `auto_auth` | `True` | Run the OAuth preflight before the workflow starts. |
104
+ | `credentials_name` | `None` | Pin a specific named credential set; otherwise the user's default is used. |
105
+ | `allow_mcp_ui` | `False` | Surface an MCP tool's `_meta.ui.resourceUri` as a side app in Vibe after a matching tool call (fire-and-forget — the workflow doesn't wait for or consume the result). |
106
+
107
+ ## Acting as the user (on-behalf-of)
108
+
109
+ A connector call runs as one of two identities:
110
+
111
+ - **The triggering user** — set `on_behalf_of=True` on `@workflow.define`, and the connector uses the credentials *that user* authorized (their GitHub, their Notion, ...).
112
+ - **The worker** (default) — the connector uses the worker's own stored credentials: one shared service identity for every run.
113
+
114
+ ```python
115
+ @workflows.workflow.define(name="github-issue-creator", on_behalf_of=True)
116
+ @uses_connectors(github)
117
+ class GitHubIssueCreatorWorkflow:
118
+ ...
119
+ ```
120
+
121
+ Fall back to worker credentials (omit `on_behalf_of`) when there's no triggering user or you deliberately want one shared identity. Caveats (platform-enforced):
122
+
123
+ - **Needs a triggering user.** Starting an `on_behalf_of=True` workflow without a user session is rejected (`403 "On-behalf-of workflows require user identity"`). For the same reason it **can't be combined with `schedules`** — the SDK raises at definition time, since a scheduled run has no triggering user.
124
+ - **Restricted to Mistral-internal orgs.** Registering an `on_behalf_of=True` workflow returns `403 "on_behalf workflows are currently restricted to Mistral-internal organizations"` unless the org is internal or the `ON_BEHALF_WORKFLOWS_ENABLED` feature flag is enabled.
125
+
126
+ ## Connectors in durable agents
127
+
128
+ Pass slots to an `Agent` via `connectors=[...]` so it can call connector tools autonomously. Keep `@uses_connectors` on the workflow. See the [Durable Agents](durable-agents.mdx) guide for agent/runner output handling.
129
+
130
+ ```python
131
+ import mistralai.workflows as workflows
132
+ from mistralai.workflows.plugins.mistralai import Agent, Runner
133
+ from mistralai.workflows.plugins.mistralai.connectors import connector, uses_connectors
134
+
135
+ github = connector("github_app")
136
+
137
+
138
+ @workflows.workflow.define(name="github-agent")
139
+ @uses_connectors(github)
140
+ class GitHubAgentWorkflow:
141
+ @workflows.workflow.entrypoint
142
+ async def run(self, repo: str) -> str:
143
+ agent = Agent(
144
+ name="github-pr-lister",
145
+ model="mistral-medium-latest",
146
+ instructions=f"List recent pull requests on {repo}.",
147
+ connectors=[github], # connector tools available to the agent
148
+ )
149
+ outputs = await Runner.run(agent=agent, inputs=f"Summarize PRs on {repo}.")
150
+ return await extract_text(outputs)
151
+ ```
152
+
153
+ OAuth and credential resolution still happen automatically.
154
+
155
+ ## Running a connector workflow
156
+
157
+ You usually don't run these by hand, they're triggered from Studio/the UI (which shows the OAuth prompt) or by whatever starts the workflow. To trigger one **from Python** when it may need OAuth, use `execute_with_connector_auth_async` (`mistralai.extra.workflows`): it surfaces the auth URL via an `on_auth_required(state)` callback and resumes automatically once the user authorizes. For plain execution without OAuth, see the [Python SDK](../getting-started/python-sdk.mdx) guide.
158
+
159
+ ## Common errors
160
+
161
+ | Symptom | Cause / fix |
162
+ | --- | --- |
163
+ | `RuntimeError: No workflow context available` | `ToolCallClient` used outside an activity. Inject via `Depends` and call tools inside an `@activity`. |
164
+ | Credentials never resolve / `call_tool` fails | Missing `@uses_connectors`, or the connector `name` doesn't match a configured connector (check `client.beta.connectors.list_async()`). |
165
+ | `ConnectorAuthTimeout` | OAuth not completed within 10 minutes. Re-run and authorize promptly. |
166
+ | `ConnectorToolCallError` | The connector tool rejected the call — check `tool_name` and the `arguments` schema. |
167
+ | `ImportError` for connectors | Install the extra: `uv add 'mistralai-workflows[mistralai]'`; import from `mistralai.workflows.plugins.mistralai.connectors`. |
168
+
169
+ ## Quick reference
170
+
171
+ | Symbol | Import from | Use |
172
+ | --- | --- | --- |
173
+ | `connector(name, *, auto_auth, credentials_name, allow_mcp_ui)` | `mistralai.workflows.plugins.mistralai.connectors` | Declare a connector slot. |
174
+ | `uses_connectors(*slots)` | same | Class decorator; attach slots (after `@workflow.define`). |
175
+ | `ToolCallClient` / `.call_tool(tool_name, arguments)` | same | Call connector tools from an activity (inject via `Depends`). |
176
+ | `Agent(..., connectors=[...])` | `mistralai.workflows.plugins.mistralai` | Give a durable agent connector tools. |
177
+ | `execute_with_connector_auth_async(...)` | `mistralai.extra.workflows` | Run a connector workflow from Python, handling OAuth (trigger-side, not needed to build a workflow). |
178
+ | `client.beta.connectors.list_async()` / `list_tools_async(connector_id_or_name=)` | `mistralai` client | Discover configured connectors and their tool names/schemas. |
@@ -19,7 +19,6 @@ import asyncio
19
19
  import importlib.util
20
20
  import inspect
21
21
  import json
22
- import os
23
22
  import sys
24
23
  import traceback
25
24
  from pathlib import Path
@@ -66,11 +65,7 @@ def _import_module(file_path: Path) -> Any:
66
65
 
67
66
  def _find_workflow_classes(module: Any) -> list[type]:
68
67
  """Return all @workflow.define classes in *module*."""
69
- return [
70
- obj
71
- for _, obj in inspect.getmembers(module, inspect.isclass)
72
- if hasattr(obj, "__workflows_workflow_def")
73
- ]
68
+ return [obj for _, obj in inspect.getmembers(module, inspect.isclass) if hasattr(obj, "__workflows_workflow_def")]
74
69
 
75
70
 
76
71
  def _workflow_name(cls: type) -> str:
@@ -81,9 +76,7 @@ def _is_interactive(cls: type) -> bool:
81
76
  return issubclass(cls, workflows.InteractiveWorkflow)
82
77
 
83
78
 
84
- def _discover_workflow(
85
- workflow_file: Path, name_override: str | None
86
- ) -> tuple[type, str, bool]:
79
+ def _discover_workflow(workflow_file: Path, name_override: str | None) -> tuple[type, str, bool]:
87
80
  """Find and select the workflow class. Returns (cls, name, interactive)."""
88
81
  module = _import_module(workflow_file)
89
82
  found = _find_workflow_classes(module)
@@ -98,16 +91,13 @@ def _discover_workflow(
98
91
  matches = [w for w in found if _workflow_name(w) == name_override]
99
92
  if not matches:
100
93
  available = ", ".join(_workflow_name(w) for w in found)
101
- raise SystemExit(
102
- f"Workflow '{name_override}' not found. Available: {available}"
103
- )
94
+ raise SystemExit(f"Workflow '{name_override}' not found. Available: {available}")
104
95
  cls = matches[0]
105
96
  else:
106
97
  if len(found) > 1:
107
98
  names = ", ".join(_workflow_name(w) for w in found)
108
99
  print(
109
- f"Multiple workflows found: {names}. Using the first one. "
110
- "Pass --workflow-name to select.",
100
+ f"Multiple workflows found: {names}. Using the first one. Pass --workflow-name to select.",
111
101
  file=sys.stderr,
112
102
  )
113
103
  cls = found[0]
@@ -146,9 +136,7 @@ async def _poll_and_submit_interactions(
146
136
  input: dict
147
137
 
148
138
  for i, response_data in enumerate(interactions, 1):
149
- task_id = await _wait_for_pending_input(
150
- client, execution_id, i, poll_timeout, poll_interval
151
- )
139
+ task_id = await _wait_for_pending_input(client, execution_id, i, poll_timeout, poll_interval)
152
140
 
153
141
  print(f" Interaction {i}: submitting {json.dumps(response_data)}")
154
142
  try:
@@ -163,8 +151,7 @@ async def _poll_and_submit_interactions(
163
151
  )
164
152
  except asyncio.TimeoutError:
165
153
  print(
166
- f" Interaction {i}: update timed out "
167
- "(workflow may have failed to process the input)",
154
+ f" Interaction {i}: update timed out (workflow may have failed to process the input)",
168
155
  file=sys.stderr,
169
156
  )
170
157
  raise
@@ -193,18 +180,13 @@ async def _wait_for_pending_input(
193
180
  if pending:
194
181
  task_id = pending[0]["task_id"]
195
182
  label = pending[0].get("label", "")
196
- print(
197
- f" Interaction {index}: pending input found "
198
- f"(task={task_id[:8]}..., label={label!r})"
199
- )
183
+ print(f" Interaction {index}: pending input found (task={task_id[:8]}..., label={label!r})")
200
184
  return task_id
201
185
  except Exception:
202
186
  pass
203
187
 
204
188
  if asyncio.get_event_loop().time() - start > timeout:
205
- raise TimeoutError(
206
- f"Timeout waiting for pending input #{index} ({timeout}s)"
207
- )
189
+ raise TimeoutError(f"Timeout waiting for pending input #{index} ({timeout}s)")
208
190
  await asyncio.sleep(interval)
209
191
 
210
192
 
@@ -213,9 +195,7 @@ async def _wait_for_pending_input(
213
195
  # ---------------------------------------------------------------------------
214
196
 
215
197
 
216
- async def _execute_with_retry(
217
- client: Any, wf_name: str, input_dict: dict | None, retries: int = 10
218
- ) -> Any:
198
+ async def _execute_with_retry(client: Any, wf_name: str, input_dict: dict | None, retries: int = 10) -> Any:
219
199
  """Start the workflow, retrying on registration-propagation errors."""
220
200
  for attempt in range(retries):
221
201
  try:
@@ -244,23 +224,17 @@ async def _await_result(
244
224
  """
245
225
  if not (interactive and interactions):
246
226
  final = await asyncio.wait_for(
247
- client.workflows.wait_for_workflow_completion_async(
248
- execution_id, polling_interval=2
249
- ),
227
+ client.workflows.wait_for_workflow_completion_async(execution_id, polling_interval=2),
250
228
  timeout=timeout,
251
229
  )
252
230
  return final.result
253
231
 
254
232
  # Run interactions and completion polling concurrently.
255
233
  interaction_task = asyncio.create_task(
256
- _poll_and_submit_interactions(
257
- client, execution_id, interactions, poll_timeout=timeout
258
- )
234
+ _poll_and_submit_interactions(client, execution_id, interactions, poll_timeout=timeout)
259
235
  )
260
236
  completion_task = asyncio.create_task(
261
- client.workflows.wait_for_workflow_completion_async(
262
- execution_id, polling_interval=2
263
- )
237
+ client.workflows.wait_for_workflow_completion_async(execution_id, polling_interval=2)
264
238
  )
265
239
 
266
240
  done, pending = await asyncio.wait(
@@ -312,9 +286,7 @@ async def run_workflow(
312
286
  _ensure_sdk()
313
287
 
314
288
  # -- discover --
315
- workflow_cls, wf_name, interactive = _discover_workflow(
316
- workflow_file, workflow_name_override
317
- )
289
+ workflow_cls, wf_name, interactive = _discover_workflow(workflow_file, workflow_name_override)
318
290
 
319
291
  print(f"Workflow: {wf_name} ({workflow_cls.__name__})")
320
292
  print(f"Interactive: {interactive}")
@@ -348,25 +320,19 @@ async def run_workflow(
348
320
  execution_id: str | None = None
349
321
  try:
350
322
  # -- execute --
351
- execution = await _execute_with_retry(
352
- client, wf_name, _build_input(input_data)
353
- )
323
+ execution = await _execute_with_retry(client, wf_name, _build_input(input_data))
354
324
  execution_id = execution.execution_id
355
325
  print(f"Execution: {execution_id}")
356
326
  print(f"Status: {execution.status}\n")
357
327
 
358
328
  # -- wait for result --
359
- return await _await_result(
360
- client, execution_id, interactions, interactive, timeout_seconds
361
- )
329
+ return await _await_result(client, execution_id, interactions, interactive, timeout_seconds)
362
330
 
363
331
  except asyncio.TimeoutError:
364
332
  if execution_id:
365
333
  print(f"\nTerminating execution {execution_id}...", file=sys.stderr)
366
334
  try:
367
- await client.workflows.executions.terminate_workflow_execution_async(
368
- execution_id=execution_id
369
- )
335
+ await client.workflows.executions.terminate_workflow_execution_async(execution_id=execution_id)
370
336
  print("Execution terminated.", file=sys.stderr)
371
337
  except Exception as e:
372
338
  print(f"Failed to terminate: {e}", file=sys.stderr)
@@ -393,27 +359,32 @@ def _parse_args() -> argparse.Namespace:
393
359
  epilog=__doc__,
394
360
  )
395
361
  parser.add_argument(
396
- "workflow_file", type=Path,
362
+ "workflow_file",
363
+ type=Path,
397
364
  help="Path to the Python file containing the workflow.",
398
365
  )
399
366
  parser.add_argument(
400
- "--input", required=True, dest="input_json",
367
+ "--input",
368
+ required=True,
369
+ dest="input_json",
401
370
  help="JSON string with the workflow input.",
402
371
  )
403
372
  parser.add_argument(
404
- "--timeout", type=int, default=30,
373
+ "--timeout",
374
+ type=int,
375
+ default=30,
405
376
  help="Max seconds before the workflow is killed (default: 30).",
406
377
  )
407
378
  parser.add_argument(
408
- "--workflow-name", default=None,
379
+ "--workflow-name",
380
+ default=None,
409
381
  help="Workflow name (if the file contains multiple workflows).",
410
382
  )
411
383
  parser.add_argument(
412
- "--interactions", default=None, dest="interactions_json",
413
- help=(
414
- "JSON array of interaction responses for interactive workflows. "
415
- 'Example: \'[{"choice": "WFL"}]\''
416
- ),
384
+ "--interactions",
385
+ default=None,
386
+ dest="interactions_json",
387
+ help=('JSON array of interaction responses for interactive workflows. Example: \'[{"choice": "WFL"}]\''),
417
388
  )
418
389
  return parser.parse_args()
419
390
 
@@ -445,6 +416,7 @@ def main() -> None:
445
416
  # Load .env if present.
446
417
  try:
447
418
  from dotenv import load_dotenv
419
+
448
420
  load_dotenv(override=True)
449
421
  except ImportError:
450
422
  pass
@@ -46,9 +46,7 @@ async def main() -> None:
46
46
 
47
47
  raw_input = raw_input or {}
48
48
  if not isinstance(raw_input, dict):
49
- raise SystemExit(
50
- f"Error: --input must be a JSON object, got {type(raw_input).__name__}"
51
- )
49
+ raise SystemExit(f"Error: --input must be a JSON object, got {type(raw_input).__name__}")
52
50
 
53
51
  api_key = os.environ.get("MISTRAL_API_KEY", "")
54
52
  if not api_key:
@@ -3,12 +3,12 @@
3
3
 
4
4
  import asyncio
5
5
  import importlib
6
- from dotenv import load_dotenv
7
-
8
6
  import inspect
9
7
  import pkgutil
10
8
  import sys
11
9
 
10
+ from dotenv import load_dotenv
11
+
12
12
  load_dotenv(override=True)
13
13
 
14
14
  import mistralai.workflows as mistralai_workflows
@@ -22,9 +22,7 @@ def discover_workflows() -> list[type]:
22
22
  discovered = []
23
23
  package = importlib.import_module("workflows")
24
24
 
25
- for _, modname, ispkg in pkgutil.iter_modules(
26
- package.__path__, prefix="workflows."
27
- ):
25
+ for _, modname, ispkg in pkgutil.iter_modules(package.__path__, prefix="workflows."):
28
26
  if ispkg:
29
27
  continue
30
28
  module = importlib.import_module(modname)
@@ -6,10 +6,9 @@ The workflow body calls these as awaitable activity proxies; the SDK
6
6
  schedules them on a worker and persists the results in event history.
7
7
  """
8
8
 
9
- from datetime import timedelta
10
-
11
9
  import base64
12
10
  import mimetypes
11
+ from datetime import timedelta
13
12
  from pathlib import Path
14
13
  from urllib.parse import unquote
15
14
  from urllib.request import url2pathname
@@ -34,9 +33,7 @@ def _confined_local_path(raw_path: str) -> Path:
34
33
  candidate = Path(raw_path)
35
34
  candidate = (candidate if candidate.is_absolute() else root / candidate).resolve()
36
35
  if not candidate.is_relative_to(root):
37
- raise ValueError(
38
- f"Refusing to read '{candidate}': outside the project directory '{root}'."
39
- )
36
+ raise ValueError(f"Refusing to read '{candidate}': outside the project directory '{root}'.")
40
37
  return candidate
41
38
 
42
39
 
@@ -121,9 +118,7 @@ async def classify_cargo_type(extracted_text: str) -> CargoClassification:
121
118
  retry_policy_max_attempts=2,
122
119
  start_to_close_timeout=timedelta(minutes=1),
123
120
  )
124
- async def validate_dangerous_goods(
125
- extracted_text: str, shipment_id: str
126
- ) -> DangerousGoodsResult:
121
+ async def validate_dangerous_goods(extracted_text: str, shipment_id: str) -> DangerousGoodsResult:
127
122
  """Run a structured-output LLM call to extract UN number, hazard class, and anomalies.
128
123
 
129
124
  This activity is called from DangerousGoodsValidationWorkflow (the sub-workflow),
@@ -142,10 +137,7 @@ async def validate_dangerous_goods(
142
137
  )
143
138
  ),
144
139
  workflows_mistralai.UserMessage(
145
- content=(
146
- f"Shipment ID: {shipment_id}\n\n"
147
- f"Shipping document extract:\n\n{extracted_text}"
148
- )
140
+ content=(f"Shipment ID: {shipment_id}\n\nShipping document extract:\n\n{extracted_text}")
149
141
  ),
150
142
  ],
151
143
  )
@@ -157,9 +149,7 @@ async def validate_dangerous_goods(
157
149
  retry_policy_max_attempts=2,
158
150
  start_to_close_timeout=timedelta(minutes=1),
159
151
  )
160
- async def validate_customs_compliance(
161
- extracted_text: str, cargo_type: str, shipment_id: str
162
- ) -> ComplianceCheck:
152
+ async def validate_customs_compliance(extracted_text: str, cargo_type: str, shipment_id: str) -> ComplianceCheck:
163
153
  """Check the document against standard customs rules.
164
154
 
165
155
  Validates presence of: HS code, country of origin, consignee details,
@@ -13,9 +13,7 @@ from pydantic import BaseModel, Field
13
13
  class CargoReleaseInput(BaseModel):
14
14
  """Single entrypoint model — fields become top-level JSON input keys."""
15
15
 
16
- document_uri: str = Field(
17
- description="Local file path or URL of the shipping document to process"
18
- )
16
+ document_uri: str = Field(description="Local file path or URL of the shipping document to process")
19
17
  shipment_id: str = Field(description="Unique identifier for this shipment")
20
18
 
21
19
 
@@ -36,9 +34,7 @@ class DangerousGoodsParams(BaseModel):
36
34
  class DangerousGoodsResult(BaseModel):
37
35
  """Structured output from the dangerous-goods validation sub-workflow."""
38
36
 
39
- un_number: str | None = Field(
40
- default=None, description="UN number if present, e.g. 'UN 1203'"
41
- )
37
+ un_number: str | None = Field(default=None, description="UN number if present, e.g. 'UN 1203'")
42
38
  hazard_class: str | None = Field(
43
39
  default=None,
44
40
  description="IMO/ADR hazard class, e.g. 'Class 3 - Flammable liquids'",
@@ -47,9 +43,7 @@ class DangerousGoodsResult(BaseModel):
47
43
  default_factory=list,
48
44
  description="List of compliance anomalies found (e.g. missing emergency contact)",
49
45
  )
50
- has_anomaly: bool = Field(
51
- description="True if any anomaly was detected that requires human review"
52
- )
46
+ has_anomaly: bool = Field(description="True if any anomaly was detected that requires human review")
53
47
 
54
48
 
55
49
  class ComplianceCheck(BaseModel):
@@ -41,8 +41,7 @@ _CARGO_NAME = "cargo-release-compliance"
41
41
  name=_DG_NAME,
42
42
  workflow_display_name="Dangerous Goods Validation",
43
43
  workflow_description=(
44
- "Sub-workflow: extract UN number, hazard class, and anomalies "
45
- "from a dangerous goods shipping document."
44
+ "Sub-workflow: extract UN number, hazard class, and anomalies from a dangerous goods shipping document."
46
45
  ),
47
46
  )
48
47
  class DangerousGoodsValidationWorkflow:
@@ -141,21 +140,15 @@ class CargoReleaseWorkflow(workflows.InteractiveWorkflow):
141
140
  # a) the dangerous-goods sub-workflow found something wrong, OR
142
141
  # b) customs compliance failed.
143
142
  # This boolean is computed from recorded activity results → deterministic.
144
- anomaly_detected = (dg_result is not None and dg_result.has_anomaly) or (
145
- not compliance.passed
146
- )
143
+ anomaly_detected = (dg_result is not None and dg_result.has_anomaly) or (not compliance.passed)
147
144
 
148
145
  if anomaly_detected:
149
146
  # Build a human-readable summary for the reviewer.
150
147
  anomaly_summary_parts: list[str] = []
151
148
  if dg_result and dg_result.anomalies:
152
- anomaly_summary_parts.append(
153
- "Dangerous goods anomalies: " + "; ".join(dg_result.anomalies)
154
- )
149
+ anomaly_summary_parts.append("Dangerous goods anomalies: " + "; ".join(dg_result.anomalies))
155
150
  if not compliance.passed:
156
- anomaly_summary_parts.append(
157
- "Customs compliance failures: " + ", ".join(compliance.failed_rules)
158
- )
151
+ anomaly_summary_parts.append("Customs compliance failures: " + ", ".join(compliance.failed_rules))
159
152
  anomaly_summary = " | ".join(anomaly_summary_parts)
160
153
 
161
154
  await workflows_mistralai.send_assistant_message(