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.
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/.gitignore +3 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/PKG-INFO +1 -1
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/pyproject.toml +1 -2
- {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
- mistralai_workflows_cli-1.2.2/src/mistral_workflows_cli/_template/template/.agents/skills/workflows/references/guides/connectors.mdx +178 -0
- {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
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/start.py +1 -3
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/worker.py +3 -5
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/worker.py +2 -3
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/workflows/hello.py +1 -2
- {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
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/Makefile +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/README.md +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/__init__.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/copier.yml +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.env.jinja +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/.gitignore +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/Makefile +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/README.md.jinja +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/pyproject.toml.jinja +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/__init__.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/entrypoints/dev.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/examples/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_template/template/src/workflows/__init__.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/_version.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/main.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/src/mistral_workflows_cli/setup.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/tests/__init__.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/tests/test_setup.py +0 -0
- {mistralai_workflows_cli-1.2.1 → mistralai_workflows_cli-1.2.2}/uv.lock +0 -0
|
@@ -32,8 +32,7 @@ packages = ["src/mistral_workflows_cli"]
|
|
|
32
32
|
|
|
33
33
|
[tool.ruff]
|
|
34
34
|
line-length = 120
|
|
35
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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(
|