applied-cli 0.5.4__tar.gz → 0.5.6__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.
- {applied_cli-0.5.4 → applied_cli-0.5.6}/PKG-INFO +1 -1
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli/__init__.py +1 -1
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli/client.py +108 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli/tools.py +105 -3
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli.egg-info/PKG-INFO +1 -1
- {applied_cli-0.5.4 → applied_cli-0.5.6}/pyproject.toml +1 -1
- {applied_cli-0.5.4 → applied_cli-0.5.6}/README.md +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli/cli.py +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli/credentials.py +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli/formatters.py +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli.egg-info/SOURCES.txt +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli.egg-info/dependency_links.txt +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli.egg-info/entry_points.txt +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli.egg-info/requires.txt +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/applied_cli.egg-info/top_level.txt +0 -0
- {applied_cli-0.5.4 → applied_cli-0.5.6}/setup.cfg +0 -0
|
@@ -390,3 +390,111 @@ class AppliedClient:
|
|
|
390
390
|
params={"type": object_type, "id": object_id},
|
|
391
391
|
)
|
|
392
392
|
return data.get("spans", [])
|
|
393
|
+
|
|
394
|
+
# -------------------------------------------------------------------------
|
|
395
|
+
# Conversational Testing (via streaming /complete endpoint)
|
|
396
|
+
# -------------------------------------------------------------------------
|
|
397
|
+
|
|
398
|
+
async def send_message(
|
|
399
|
+
self,
|
|
400
|
+
agent_id: str,
|
|
401
|
+
message: str,
|
|
402
|
+
conversation_id: str | None = None,
|
|
403
|
+
contact_email: str | None = None,
|
|
404
|
+
contact_name: str | None = None,
|
|
405
|
+
contact_phone: str | None = None,
|
|
406
|
+
metadata: dict[str, Any] | None = None,
|
|
407
|
+
) -> dict[str, Any]:
|
|
408
|
+
"""Send a message to an agent and wait for the response.
|
|
409
|
+
|
|
410
|
+
This calls the /complete endpoint (streaming) and consumes the full
|
|
411
|
+
response. Use this for testing conversational flows.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
agent_id: The agent UUID
|
|
415
|
+
message: The user message to send
|
|
416
|
+
conversation_id: Optional existing conversation to continue
|
|
417
|
+
contact_email: Optional contact email for context
|
|
418
|
+
contact_name: Optional contact name for context
|
|
419
|
+
contact_phone: Optional contact phone for context
|
|
420
|
+
metadata: Optional metadata dict for the conversation
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Dict with conversation_id, response_text, and status
|
|
424
|
+
"""
|
|
425
|
+
headers = {
|
|
426
|
+
"Authorization": f"Bearer {self.token}",
|
|
427
|
+
"Content-Type": "application/json",
|
|
428
|
+
"Accept": "text/event-stream",
|
|
429
|
+
}
|
|
430
|
+
if self.shop_id:
|
|
431
|
+
headers["X-Shop-Id"] = self.shop_id
|
|
432
|
+
|
|
433
|
+
body: dict[str, Any] = {
|
|
434
|
+
"transcript": [
|
|
435
|
+
{
|
|
436
|
+
"role": "user",
|
|
437
|
+
"content": message,
|
|
438
|
+
"text": message,
|
|
439
|
+
"format": "markdown",
|
|
440
|
+
}
|
|
441
|
+
],
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if conversation_id:
|
|
445
|
+
body["conversation_id"] = conversation_id
|
|
446
|
+
|
|
447
|
+
# Build metadata with contact info
|
|
448
|
+
meta = metadata or {}
|
|
449
|
+
if contact_email:
|
|
450
|
+
meta["email"] = contact_email
|
|
451
|
+
if contact_name:
|
|
452
|
+
meta["name"] = contact_name
|
|
453
|
+
if contact_phone:
|
|
454
|
+
meta["phone"] = contact_phone
|
|
455
|
+
if meta:
|
|
456
|
+
body["metadata"] = meta
|
|
457
|
+
|
|
458
|
+
url = f"{self.base_url}/v1/agents/{agent_id}/complete/"
|
|
459
|
+
|
|
460
|
+
response_text = ""
|
|
461
|
+
result_conversation_id = conversation_id
|
|
462
|
+
|
|
463
|
+
async with httpx.AsyncClient(timeout=120.0) as client:
|
|
464
|
+
async with client.stream(
|
|
465
|
+
"POST", url, headers=headers, json=body
|
|
466
|
+
) as response:
|
|
467
|
+
response.raise_for_status()
|
|
468
|
+
|
|
469
|
+
async for line in response.aiter_lines():
|
|
470
|
+
if not line:
|
|
471
|
+
continue
|
|
472
|
+
|
|
473
|
+
# Parse SSE format: "data: {...}"
|
|
474
|
+
if line.startswith("data: "):
|
|
475
|
+
data_str = line[6:] # Remove "data: " prefix
|
|
476
|
+
if data_str == "[DONE]":
|
|
477
|
+
break
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
import json
|
|
481
|
+
|
|
482
|
+
data = json.loads(data_str)
|
|
483
|
+
|
|
484
|
+
# Extract conversation_id from first chunk
|
|
485
|
+
if not result_conversation_id:
|
|
486
|
+
result_conversation_id = data.get("conversation_id")
|
|
487
|
+
|
|
488
|
+
# Accumulate response content
|
|
489
|
+
if "content" in data:
|
|
490
|
+
response_text += data["content"]
|
|
491
|
+
|
|
492
|
+
except (json.JSONDecodeError, KeyError):
|
|
493
|
+
# Skip malformed chunks
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
"conversation_id": result_conversation_id,
|
|
498
|
+
"response": response_text,
|
|
499
|
+
"status": "success" if response_text else "empty",
|
|
500
|
+
}
|
|
@@ -4,6 +4,8 @@ These functions wrap the client methods with formatting logic,
|
|
|
4
4
|
suitable for both MCP tools and CLI commands.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import json
|
|
8
|
+
|
|
7
9
|
from applied_cli.client import AppliedClient
|
|
8
10
|
from applied_cli.formatters import select_fields, to_csv, to_json
|
|
9
11
|
|
|
@@ -742,10 +744,50 @@ async def flow_run_get(
|
|
|
742
744
|
result += f" - {status_msg}"
|
|
743
745
|
result += "\n"
|
|
744
746
|
|
|
745
|
-
#
|
|
747
|
+
# executor_run: parse output JSON and show fields individually
|
|
746
748
|
if name == "executor_run" and attrs.get("output"):
|
|
747
|
-
|
|
748
|
-
|
|
749
|
+
try:
|
|
750
|
+
output_data = json.loads(attrs["output"])
|
|
751
|
+
for k, v in output_data.items():
|
|
752
|
+
if k == "success":
|
|
753
|
+
continue
|
|
754
|
+
result += f" output.{k}: {str(v)[:300]}\n"
|
|
755
|
+
except (json.JSONDecodeError, TypeError):
|
|
756
|
+
result += f" output: {str(attrs['output'])[:500]}\n"
|
|
757
|
+
|
|
758
|
+
# completion: show the LLM text output
|
|
759
|
+
elif name == "completion" and attrs.get("content"):
|
|
760
|
+
result += f" completion: {str(attrs['content'])[:500]}\n"
|
|
761
|
+
|
|
762
|
+
# structured_completion: show parsed output
|
|
763
|
+
elif name == "structured_completion" and attrs.get("output"):
|
|
764
|
+
try:
|
|
765
|
+
out = json.loads(attrs["output"])
|
|
766
|
+
result += f" output: {json.dumps(out, indent=None)[:400]}\n"
|
|
767
|
+
except (json.JSONDecodeError, TypeError):
|
|
768
|
+
result += f" output: {str(attrs['output'])[:400]}\n"
|
|
769
|
+
|
|
770
|
+
# branch: show routing decision and comparison breakdown
|
|
771
|
+
elif name == "branch":
|
|
772
|
+
selected = attrs.get("selected.path", "")
|
|
773
|
+
if selected:
|
|
774
|
+
path_label = (
|
|
775
|
+
"default (no match)"
|
|
776
|
+
if selected == "default"
|
|
777
|
+
else f"branch {selected}"
|
|
778
|
+
)
|
|
779
|
+
result += f" selected path: {path_label}\n"
|
|
780
|
+
# Show comparison evaluations
|
|
781
|
+
idx = 1
|
|
782
|
+
while True:
|
|
783
|
+
op = attrs.get(f"comparison.{idx}.operator")
|
|
784
|
+
if not op:
|
|
785
|
+
break
|
|
786
|
+
lhs = attrs.get(f"comparison.{idx}.lhs", "?")
|
|
787
|
+
rhs = attrs.get(f"comparison.{idx}.rhs", "?")
|
|
788
|
+
res = attrs.get(f"comparison.{idx}.result", "?")
|
|
789
|
+
result += f" comparison {idx}: {lhs} {op} {rhs} => {res}\n"
|
|
790
|
+
idx += 1
|
|
749
791
|
|
|
750
792
|
# Actions
|
|
751
793
|
actions = run.get("actions", [])
|
|
@@ -845,6 +887,66 @@ async def conversation_spans(
|
|
|
845
887
|
return result
|
|
846
888
|
|
|
847
889
|
|
|
890
|
+
async def send_message(
|
|
891
|
+
client: AppliedClient,
|
|
892
|
+
agent_id: str,
|
|
893
|
+
message: str,
|
|
894
|
+
conversation_id: str | None = None,
|
|
895
|
+
contact_email: str | None = None,
|
|
896
|
+
contact_name: str | None = None,
|
|
897
|
+
contact_phone: str | None = None,
|
|
898
|
+
) -> str:
|
|
899
|
+
"""
|
|
900
|
+
Send a message to an agent and get the response.
|
|
901
|
+
|
|
902
|
+
Use this to test conversational flows. After sending, you can query
|
|
903
|
+
the conversation messages with conversation_get for full details.
|
|
904
|
+
|
|
905
|
+
Args:
|
|
906
|
+
client: Authenticated AppliedClient
|
|
907
|
+
agent_id: The agent UUID to send the message to
|
|
908
|
+
message: The user message to send
|
|
909
|
+
conversation_id: Optional - continue an existing conversation
|
|
910
|
+
contact_email: Optional contact email (for flows that need it)
|
|
911
|
+
contact_name: Optional contact name
|
|
912
|
+
contact_phone: Optional contact phone
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
Conversation ID, response preview, and status
|
|
916
|
+
"""
|
|
917
|
+
try:
|
|
918
|
+
result = await client.send_message(
|
|
919
|
+
agent_id=agent_id,
|
|
920
|
+
message=message,
|
|
921
|
+
conversation_id=conversation_id,
|
|
922
|
+
contact_email=contact_email,
|
|
923
|
+
contact_name=contact_name,
|
|
924
|
+
contact_phone=contact_phone,
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
output = "# Message Sent\n"
|
|
928
|
+
output += f"conversation_id: {result.get('conversation_id')}\n"
|
|
929
|
+
output += f"status: {result.get('status')}\n"
|
|
930
|
+
|
|
931
|
+
response = result.get("response", "")
|
|
932
|
+
if response:
|
|
933
|
+
# Truncate long responses for preview
|
|
934
|
+
preview = response[:500] + "..." if len(response) > 500 else response
|
|
935
|
+
output += f"\n## Response Preview\n{preview}\n"
|
|
936
|
+
|
|
937
|
+
output += "\n## Next Steps\n"
|
|
938
|
+
output += (
|
|
939
|
+
"- Use `conversation_get` with the conversation_id to see full messages\n"
|
|
940
|
+
)
|
|
941
|
+
output += "- Use `flow_run_list` with conversation_id to see flow runs\n"
|
|
942
|
+
output += "- Use `conversation_spans` for detailed execution trace\n"
|
|
943
|
+
|
|
944
|
+
return output
|
|
945
|
+
|
|
946
|
+
except Exception as e:
|
|
947
|
+
return f"Error sending message: {e}"
|
|
948
|
+
|
|
949
|
+
|
|
848
950
|
async def executor_list(
|
|
849
951
|
client: AppliedClient,
|
|
850
952
|
output_format: str = "csv",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|