letta-nightly 0.11.7.dev20251007104119__py3-none-any.whl → 0.11.7.dev20251008104128__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letta/adapters/letta_llm_adapter.py +1 -0
- letta/adapters/letta_llm_request_adapter.py +0 -1
- letta/adapters/letta_llm_stream_adapter.py +7 -2
- letta/adapters/simple_llm_request_adapter.py +88 -0
- letta/adapters/simple_llm_stream_adapter.py +192 -0
- letta/agents/agent_loop.py +6 -0
- letta/agents/ephemeral_summary_agent.py +2 -1
- letta/agents/helpers.py +142 -6
- letta/agents/letta_agent.py +13 -33
- letta/agents/letta_agent_batch.py +2 -4
- letta/agents/letta_agent_v2.py +87 -77
- letta/agents/letta_agent_v3.py +899 -0
- letta/agents/voice_agent.py +2 -6
- letta/constants.py +8 -4
- letta/errors.py +40 -0
- letta/functions/function_sets/base.py +84 -4
- letta/functions/function_sets/multi_agent.py +0 -3
- letta/functions/schema_generator.py +113 -71
- letta/groups/dynamic_multi_agent.py +3 -2
- letta/groups/helpers.py +1 -2
- letta/groups/round_robin_multi_agent.py +3 -2
- letta/groups/sleeptime_multi_agent.py +3 -2
- letta/groups/sleeptime_multi_agent_v2.py +1 -1
- letta/groups/sleeptime_multi_agent_v3.py +17 -17
- letta/groups/supervisor_multi_agent.py +84 -80
- letta/helpers/converters.py +3 -0
- letta/helpers/message_helper.py +4 -0
- letta/helpers/tool_rule_solver.py +92 -5
- letta/interfaces/anthropic_streaming_interface.py +409 -0
- letta/interfaces/gemini_streaming_interface.py +296 -0
- letta/interfaces/openai_streaming_interface.py +752 -1
- letta/llm_api/anthropic_client.py +126 -16
- letta/llm_api/bedrock_client.py +4 -2
- letta/llm_api/deepseek_client.py +4 -1
- letta/llm_api/google_vertex_client.py +123 -42
- letta/llm_api/groq_client.py +4 -1
- letta/llm_api/llm_api_tools.py +11 -4
- letta/llm_api/llm_client_base.py +6 -2
- letta/llm_api/openai.py +32 -2
- letta/llm_api/openai_client.py +423 -18
- letta/llm_api/xai_client.py +4 -1
- letta/main.py +9 -5
- letta/memory.py +1 -0
- letta/orm/__init__.py +1 -1
- letta/orm/agent.py +10 -0
- letta/orm/block.py +7 -16
- letta/orm/blocks_agents.py +8 -2
- letta/orm/files_agents.py +2 -0
- letta/orm/job.py +7 -5
- letta/orm/mcp_oauth.py +1 -0
- letta/orm/message.py +21 -6
- letta/orm/organization.py +2 -0
- letta/orm/provider.py +6 -2
- letta/orm/run.py +71 -0
- letta/orm/sandbox_config.py +7 -1
- letta/orm/sqlalchemy_base.py +0 -306
- letta/orm/step.py +6 -5
- letta/orm/step_metrics.py +5 -5
- letta/otel/tracing.py +28 -3
- letta/plugins/defaults.py +4 -4
- letta/prompts/system_prompts/__init__.py +2 -0
- letta/prompts/system_prompts/letta_v1.py +25 -0
- letta/schemas/agent.py +3 -2
- letta/schemas/agent_file.py +9 -3
- letta/schemas/block.py +23 -10
- letta/schemas/enums.py +21 -2
- letta/schemas/job.py +17 -4
- letta/schemas/letta_message_content.py +71 -2
- letta/schemas/letta_stop_reason.py +5 -5
- letta/schemas/llm_config.py +53 -3
- letta/schemas/memory.py +1 -1
- letta/schemas/message.py +504 -117
- letta/schemas/openai/responses_request.py +64 -0
- letta/schemas/providers/__init__.py +2 -0
- letta/schemas/providers/anthropic.py +16 -0
- letta/schemas/providers/ollama.py +115 -33
- letta/schemas/providers/openrouter.py +52 -0
- letta/schemas/providers/vllm.py +2 -1
- letta/schemas/run.py +48 -42
- letta/schemas/step.py +2 -2
- letta/schemas/step_metrics.py +1 -1
- letta/schemas/tool.py +15 -107
- letta/schemas/tool_rule.py +88 -5
- letta/serialize_schemas/marshmallow_agent.py +1 -0
- letta/server/db.py +86 -408
- letta/server/rest_api/app.py +61 -10
- letta/server/rest_api/dependencies.py +14 -0
- letta/server/rest_api/redis_stream_manager.py +19 -8
- letta/server/rest_api/routers/v1/agents.py +364 -292
- letta/server/rest_api/routers/v1/blocks.py +14 -20
- letta/server/rest_api/routers/v1/identities.py +45 -110
- letta/server/rest_api/routers/v1/internal_templates.py +21 -0
- letta/server/rest_api/routers/v1/jobs.py +23 -6
- letta/server/rest_api/routers/v1/messages.py +1 -1
- letta/server/rest_api/routers/v1/runs.py +126 -85
- letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
- letta/server/rest_api/routers/v1/tools.py +281 -594
- letta/server/rest_api/routers/v1/voice.py +1 -1
- letta/server/rest_api/streaming_response.py +29 -29
- letta/server/rest_api/utils.py +122 -64
- letta/server/server.py +160 -887
- letta/services/agent_manager.py +236 -919
- letta/services/agent_serialization_manager.py +16 -0
- letta/services/archive_manager.py +0 -100
- letta/services/block_manager.py +211 -168
- letta/services/file_manager.py +1 -1
- letta/services/files_agents_manager.py +24 -33
- letta/services/group_manager.py +0 -142
- letta/services/helpers/agent_manager_helper.py +7 -2
- letta/services/helpers/run_manager_helper.py +85 -0
- letta/services/job_manager.py +96 -411
- letta/services/lettuce/__init__.py +6 -0
- letta/services/lettuce/lettuce_client_base.py +86 -0
- letta/services/mcp_manager.py +38 -6
- letta/services/message_manager.py +165 -362
- letta/services/organization_manager.py +0 -36
- letta/services/passage_manager.py +0 -345
- letta/services/provider_manager.py +0 -80
- letta/services/run_manager.py +301 -0
- letta/services/sandbox_config_manager.py +0 -234
- letta/services/step_manager.py +62 -39
- letta/services/summarizer/summarizer.py +9 -7
- letta/services/telemetry_manager.py +0 -16
- letta/services/tool_executor/builtin_tool_executor.py +35 -0
- letta/services/tool_executor/core_tool_executor.py +397 -2
- letta/services/tool_executor/files_tool_executor.py +3 -3
- letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
- letta/services/tool_executor/tool_execution_manager.py +6 -8
- letta/services/tool_executor/tool_executor_base.py +3 -3
- letta/services/tool_manager.py +85 -339
- letta/services/tool_sandbox/base.py +24 -13
- letta/services/tool_sandbox/e2b_sandbox.py +16 -1
- letta/services/tool_schema_generator.py +123 -0
- letta/services/user_manager.py +0 -99
- letta/settings.py +20 -4
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/METADATA +3 -5
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/RECORD +140 -132
- letta/agents/temporal/activities/__init__.py +0 -4
- letta/agents/temporal/activities/example_activity.py +0 -7
- letta/agents/temporal/activities/prepare_messages.py +0 -10
- letta/agents/temporal/temporal_agent_workflow.py +0 -56
- letta/agents/temporal/types.py +0 -25
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/licenses/LICENSE +0 -0
@@ -23,6 +23,7 @@ from anthropic.types.beta import (
|
|
23
23
|
BetaThinkingDelta,
|
24
24
|
BetaToolUseBlock,
|
25
25
|
)
|
26
|
+
from letta_client.types import assistant_message
|
26
27
|
|
27
28
|
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
28
29
|
from letta.local_llm.constants import INNER_THOUGHTS_KWARG
|
@@ -65,9 +66,13 @@ class AnthropicStreamingInterface:
|
|
65
66
|
use_assistant_message: bool = False,
|
66
67
|
put_inner_thoughts_in_kwarg: bool = False,
|
67
68
|
requires_approval_tools: list = [],
|
69
|
+
run_id: str | None = None,
|
70
|
+
step_id: str | None = None,
|
68
71
|
):
|
69
72
|
self.json_parser: JSONParser = PydanticJSONParser()
|
70
73
|
self.use_assistant_message = use_assistant_message
|
74
|
+
self.run_id = run_id
|
75
|
+
self.step_id = step_id
|
71
76
|
|
72
77
|
# Premake IDs for database writes
|
73
78
|
self.letta_message_id = Message.generate_id()
|
@@ -271,11 +276,15 @@ class AnthropicStreamingInterface:
|
|
271
276
|
if not self.use_assistant_message:
|
272
277
|
# Only buffer the initial tool call message if it doesn't require approval
|
273
278
|
# For approval-required tools, we'll create the ApprovalRequestMessage later
|
279
|
+
if prev_message_type and prev_message_type != "tool_call_message":
|
280
|
+
message_index += 1
|
274
281
|
if self.tool_call_name not in self.requires_approval_tools:
|
275
282
|
tool_call_msg = ToolCallMessage(
|
276
283
|
id=self.letta_message_id,
|
277
284
|
tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id),
|
278
285
|
date=datetime.now(timezone.utc).isoformat(),
|
286
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
287
|
+
run_id=self.run_id,
|
279
288
|
)
|
280
289
|
self.tool_call_buffer.append(tool_call_msg)
|
281
290
|
elif isinstance(content, BetaThinkingBlock):
|
@@ -291,6 +300,8 @@ class AnthropicStreamingInterface:
|
|
291
300
|
hidden_reasoning=content.data,
|
292
301
|
date=datetime.now(timezone.utc).isoformat(),
|
293
302
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
303
|
+
run_id=self.run_id,
|
304
|
+
step_id=self.step_id,
|
294
305
|
)
|
295
306
|
self.reasoning_messages.append(hidden_reasoning_message)
|
296
307
|
prev_message_type = hidden_reasoning_message.message_type
|
@@ -336,6 +347,8 @@ class AnthropicStreamingInterface:
|
|
336
347
|
reasoning=self.accumulated_inner_thoughts[-1],
|
337
348
|
date=datetime.now(timezone.utc).isoformat(),
|
338
349
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
350
|
+
run_id=self.run_id,
|
351
|
+
step_id=self.step_id,
|
339
352
|
)
|
340
353
|
self.reasoning_messages.append(reasoning_message)
|
341
354
|
prev_message_type = reasoning_message.message_type
|
@@ -363,6 +376,8 @@ class AnthropicStreamingInterface:
|
|
363
376
|
reasoning=inner_thoughts_diff,
|
364
377
|
date=datetime.now(timezone.utc).isoformat(),
|
365
378
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
379
|
+
run_id=self.run_id,
|
380
|
+
step_id=self.step_id,
|
366
381
|
)
|
367
382
|
self.reasoning_messages.append(reasoning_message)
|
368
383
|
prev_message_type = reasoning_message.message_type
|
@@ -393,6 +408,7 @@ class AnthropicStreamingInterface:
|
|
393
408
|
tool_call_id=self.tool_call_id,
|
394
409
|
arguments=tool_call_args,
|
395
410
|
),
|
411
|
+
run_id=self.run_id,
|
396
412
|
)
|
397
413
|
prev_message_type = approval_msg.message_type
|
398
414
|
yield approval_msg
|
@@ -416,6 +432,7 @@ class AnthropicStreamingInterface:
|
|
416
432
|
tool_call_id=self.tool_call_id,
|
417
433
|
arguments=tool_call_args,
|
418
434
|
),
|
435
|
+
run_id=self.run_id,
|
419
436
|
)
|
420
437
|
prev_message_type = tool_call_msg.message_type
|
421
438
|
yield tool_call_msg
|
@@ -436,6 +453,7 @@ class AnthropicStreamingInterface:
|
|
436
453
|
content=[TextContent(text=send_message_diff)],
|
437
454
|
date=datetime.now(timezone.utc).isoformat(),
|
438
455
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
456
|
+
run_id=self.run_id,
|
439
457
|
)
|
440
458
|
prev_message_type = assistant_msg.message_type
|
441
459
|
yield assistant_msg
|
@@ -446,12 +464,14 @@ class AnthropicStreamingInterface:
|
|
446
464
|
id=self.letta_message_id,
|
447
465
|
tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
|
448
466
|
date=datetime.now(timezone.utc).isoformat(),
|
467
|
+
run_id=self.run_id,
|
449
468
|
)
|
450
469
|
else:
|
451
470
|
tool_call_msg = ToolCallMessage(
|
452
471
|
id=self.letta_message_id,
|
453
472
|
tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
|
454
473
|
date=datetime.now(timezone.utc).isoformat(),
|
474
|
+
run_id=self.run_id,
|
455
475
|
)
|
456
476
|
if self.inner_thoughts_complete:
|
457
477
|
if prev_message_type and prev_message_type != "tool_call_message":
|
@@ -479,6 +499,8 @@ class AnthropicStreamingInterface:
|
|
479
499
|
reasoning=delta.thinking,
|
480
500
|
date=datetime.now(timezone.utc).isoformat(),
|
481
501
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
502
|
+
run_id=self.run_id,
|
503
|
+
step_id=self.step_id,
|
482
504
|
)
|
483
505
|
self.reasoning_messages.append(reasoning_message)
|
484
506
|
prev_message_type = reasoning_message.message_type
|
@@ -499,6 +521,8 @@ class AnthropicStreamingInterface:
|
|
499
521
|
date=datetime.now(timezone.utc).isoformat(),
|
500
522
|
signature=delta.signature,
|
501
523
|
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
524
|
+
run_id=self.run_id,
|
525
|
+
step_id=self.step_id,
|
502
526
|
)
|
503
527
|
self.reasoning_messages.append(reasoning_message)
|
504
528
|
prev_message_type = reasoning_message.message_type
|
@@ -522,3 +546,388 @@ class AnthropicStreamingInterface:
|
|
522
546
|
self.tool_call_buffer = []
|
523
547
|
|
524
548
|
self.anthropic_mode = None
|
549
|
+
|
550
|
+
|
551
|
+
class SimpleAnthropicStreamingInterface:
|
552
|
+
"""
|
553
|
+
A simpler version of AnthropicStreamingInterface that doesn't handle send_message parsing on inner_thoughts_in_kwargs
|
554
|
+
"""
|
555
|
+
|
556
|
+
def __init__(
|
557
|
+
self,
|
558
|
+
requires_approval_tools: list = [],
|
559
|
+
run_id: str | None = None,
|
560
|
+
step_id: str | None = None,
|
561
|
+
):
|
562
|
+
self.json_parser: JSONParser = PydanticJSONParser()
|
563
|
+
self.run_id = run_id
|
564
|
+
self.step_id = step_id
|
565
|
+
|
566
|
+
# Premake IDs for database writes
|
567
|
+
self.letta_message_id = Message.generate_id()
|
568
|
+
|
569
|
+
self.anthropic_mode = None
|
570
|
+
self.message_id = None
|
571
|
+
self.accumulated_inner_thoughts = []
|
572
|
+
self.tool_call_id = None
|
573
|
+
self.tool_call_name = None
|
574
|
+
self.accumulated_tool_call_args = ""
|
575
|
+
self.previous_parse = {}
|
576
|
+
|
577
|
+
# usage trackers
|
578
|
+
self.input_tokens = 0
|
579
|
+
self.output_tokens = 0
|
580
|
+
self.model = None
|
581
|
+
|
582
|
+
# reasoning object trackers
|
583
|
+
self.reasoning_messages = []
|
584
|
+
|
585
|
+
# assistant object trackers
|
586
|
+
self.assistant_messages: list[AssistantMessage] = []
|
587
|
+
|
588
|
+
# Buffer to hold tool call messages until inner thoughts are complete
|
589
|
+
self.tool_call_buffer = []
|
590
|
+
self.inner_thoughts_complete = False
|
591
|
+
|
592
|
+
# Buffer to handle partial XML tags across chunks
|
593
|
+
self.partial_tag_buffer = ""
|
594
|
+
|
595
|
+
self.requires_approval_tools = requires_approval_tools
|
596
|
+
|
597
|
+
def get_tool_call_object(self) -> Optional[ToolCall]:
|
598
|
+
"""Useful for agent loop"""
|
599
|
+
if not self.tool_call_name:
|
600
|
+
return None
|
601
|
+
|
602
|
+
# hack for tool rules
|
603
|
+
try:
|
604
|
+
tool_input = json.loads(self.accumulated_tool_call_args)
|
605
|
+
except json.JSONDecodeError as e:
|
606
|
+
# Attempt to use OptimisticJSONParser to handle incomplete/malformed JSON
|
607
|
+
try:
|
608
|
+
tool_input = self.json_parser.parse(self.accumulated_tool_call_args)
|
609
|
+
except:
|
610
|
+
logger.warning(
|
611
|
+
f"Failed to decode tool call arguments for tool_call_id={self.tool_call_id}, "
|
612
|
+
f"name={self.tool_call_name}. Raw input: {self.accumulated_tool_call_args!r}. Error: {e}"
|
613
|
+
)
|
614
|
+
raise e
|
615
|
+
if "id" in tool_input and tool_input["id"].startswith("toolu_") and "function" in tool_input:
|
616
|
+
arguments = str(json.dumps(tool_input["function"]["arguments"], indent=2))
|
617
|
+
else:
|
618
|
+
arguments = str(json.dumps(tool_input, indent=2))
|
619
|
+
return ToolCall(id=self.tool_call_id, function=FunctionCall(arguments=arguments, name=self.tool_call_name))
|
620
|
+
|
621
|
+
def get_reasoning_content(self) -> list[TextContent | ReasoningContent | RedactedReasoningContent]:
|
622
|
+
def _process_group(
|
623
|
+
group: list[ReasoningMessage | HiddenReasoningMessage | AssistantMessage],
|
624
|
+
group_type: str,
|
625
|
+
) -> TextContent | ReasoningContent | RedactedReasoningContent:
|
626
|
+
if group_type == "reasoning":
|
627
|
+
reasoning_text = "".join(chunk.reasoning for chunk in group).strip()
|
628
|
+
is_native = any(chunk.source == "reasoner_model" for chunk in group)
|
629
|
+
signature = next((chunk.signature for chunk in group if chunk.signature is not None), None)
|
630
|
+
if is_native:
|
631
|
+
return ReasoningContent(is_native=is_native, reasoning=reasoning_text, signature=signature)
|
632
|
+
else:
|
633
|
+
return TextContent(text=reasoning_text)
|
634
|
+
elif group_type == "redacted":
|
635
|
+
redacted_text = "".join(chunk.hidden_reasoning for chunk in group if chunk.hidden_reasoning is not None)
|
636
|
+
return RedactedReasoningContent(data=redacted_text)
|
637
|
+
elif group_type == "text":
|
638
|
+
concat = ""
|
639
|
+
for chunk in group:
|
640
|
+
if isinstance(chunk.content, list):
|
641
|
+
concat += "".join([c.text for c in chunk.content])
|
642
|
+
else:
|
643
|
+
concat += chunk.content
|
644
|
+
return TextContent(text=concat)
|
645
|
+
else:
|
646
|
+
raise ValueError("Unexpected group type")
|
647
|
+
|
648
|
+
merged = []
|
649
|
+
current_group = []
|
650
|
+
current_group_type = None # "reasoning" or "redacted"
|
651
|
+
|
652
|
+
for msg in self.reasoning_messages:
|
653
|
+
# Determine the type of the current message
|
654
|
+
if isinstance(msg, HiddenReasoningMessage):
|
655
|
+
msg_type = "redacted"
|
656
|
+
elif isinstance(msg, ReasoningMessage):
|
657
|
+
msg_type = "reasoning"
|
658
|
+
elif isinstance(msg, AssistantMessage):
|
659
|
+
msg_type = "text"
|
660
|
+
else:
|
661
|
+
raise ValueError("Unexpected message type")
|
662
|
+
|
663
|
+
# Initialize group type if not set
|
664
|
+
if current_group_type is None:
|
665
|
+
current_group_type = msg_type
|
666
|
+
|
667
|
+
# If the type changes, process the current group
|
668
|
+
if msg_type != current_group_type:
|
669
|
+
merged.append(_process_group(current_group, current_group_type))
|
670
|
+
current_group = []
|
671
|
+
current_group_type = msg_type
|
672
|
+
|
673
|
+
current_group.append(msg)
|
674
|
+
|
675
|
+
# Process the final group, if any.
|
676
|
+
if current_group:
|
677
|
+
merged.append(_process_group(current_group, current_group_type))
|
678
|
+
|
679
|
+
return merged
|
680
|
+
|
681
|
+
def get_content(self) -> list[TextContent | ReasoningContent | RedactedReasoningContent]:
|
682
|
+
return self.get_reasoning_content()
|
683
|
+
# concat = ""
|
684
|
+
# for msg in self.assistant_messages:
|
685
|
+
# if isinstance(msg.content, list):
|
686
|
+
# concat += "".join([c.text for c in msg.content])
|
687
|
+
# else:
|
688
|
+
# concat += msg.content
|
689
|
+
# return [TextContent(text=concat)]
|
690
|
+
|
691
|
+
async def process(
|
692
|
+
self,
|
693
|
+
stream: AsyncStream[BetaRawMessageStreamEvent],
|
694
|
+
ttft_span: Optional["Span"] = None,
|
695
|
+
) -> AsyncGenerator[LettaMessage | LettaStopReason, None]:
|
696
|
+
prev_message_type = None
|
697
|
+
message_index = 0
|
698
|
+
event = None
|
699
|
+
try:
|
700
|
+
async with stream:
|
701
|
+
async for event in stream:
|
702
|
+
try:
|
703
|
+
async for message in self._process_event(event, ttft_span, prev_message_type, message_index):
|
704
|
+
new_message_type = message.message_type
|
705
|
+
if new_message_type != prev_message_type:
|
706
|
+
if prev_message_type != None:
|
707
|
+
message_index += 1
|
708
|
+
prev_message_type = new_message_type
|
709
|
+
# print(f"Yielding message: {message}")
|
710
|
+
yield message
|
711
|
+
except asyncio.CancelledError as e:
|
712
|
+
import traceback
|
713
|
+
|
714
|
+
logger.info("Cancelled stream attempt but overriding %s: %s", e, traceback.format_exc())
|
715
|
+
async for message in self._process_event(event, ttft_span, prev_message_type, message_index):
|
716
|
+
new_message_type = message.message_type
|
717
|
+
if new_message_type != prev_message_type:
|
718
|
+
if prev_message_type != None:
|
719
|
+
message_index += 1
|
720
|
+
prev_message_type = new_message_type
|
721
|
+
yield message
|
722
|
+
|
723
|
+
# Don't raise the exception here
|
724
|
+
continue
|
725
|
+
|
726
|
+
except Exception as e:
|
727
|
+
import traceback
|
728
|
+
|
729
|
+
logger.error("Error processing stream: %s\n%s", e, traceback.format_exc())
|
730
|
+
if ttft_span:
|
731
|
+
ttft_span.add_event(
|
732
|
+
name="stop_reason",
|
733
|
+
attributes={"stop_reason": StopReasonType.error.value, "error": str(e), "stacktrace": traceback.format_exc()},
|
734
|
+
)
|
735
|
+
yield LettaStopReason(stop_reason=StopReasonType.error)
|
736
|
+
raise e
|
737
|
+
finally:
|
738
|
+
logger.info("AnthropicStreamingInterface: Stream processing complete.")
|
739
|
+
|
740
|
+
async def _process_event(
|
741
|
+
self,
|
742
|
+
event: BetaRawMessageStreamEvent,
|
743
|
+
ttft_span: Optional["Span"] = None,
|
744
|
+
prev_message_type: Optional[str] = None,
|
745
|
+
message_index: int = 0,
|
746
|
+
) -> AsyncGenerator[LettaMessage | LettaStopReason, None]:
|
747
|
+
"""Process a single event from the Anthropic stream and yield any resulting messages.
|
748
|
+
|
749
|
+
Args:
|
750
|
+
event: The event to process
|
751
|
+
|
752
|
+
Yields:
|
753
|
+
Messages generated from processing this event
|
754
|
+
"""
|
755
|
+
if isinstance(event, BetaRawContentBlockStartEvent):
|
756
|
+
content = event.content_block
|
757
|
+
|
758
|
+
if isinstance(content, BetaTextBlock):
|
759
|
+
self.anthropic_mode = EventMode.TEXT
|
760
|
+
# TODO: Can capture citations, etc.
|
761
|
+
|
762
|
+
elif isinstance(content, BetaToolUseBlock):
|
763
|
+
self.anthropic_mode = EventMode.TOOL_USE
|
764
|
+
self.tool_call_id = content.id
|
765
|
+
self.tool_call_name = content.name
|
766
|
+
|
767
|
+
if self.tool_call_name in self.requires_approval_tools:
|
768
|
+
if prev_message_type and prev_message_type != "approval_request_message":
|
769
|
+
message_index += 1
|
770
|
+
tool_call_msg = ApprovalRequestMessage(
|
771
|
+
id=self.letta_message_id,
|
772
|
+
tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id),
|
773
|
+
date=datetime.now(timezone.utc).isoformat(),
|
774
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
775
|
+
run_id=self.run_id,
|
776
|
+
step_id=self.step_id,
|
777
|
+
)
|
778
|
+
else:
|
779
|
+
if prev_message_type and prev_message_type != "tool_call_message":
|
780
|
+
message_index += 1
|
781
|
+
tool_call_msg = ToolCallMessage(
|
782
|
+
id=self.letta_message_id,
|
783
|
+
tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id),
|
784
|
+
date=datetime.now(timezone.utc).isoformat(),
|
785
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
786
|
+
run_id=self.run_id,
|
787
|
+
step_id=self.step_id,
|
788
|
+
)
|
789
|
+
prev_message_type = tool_call_msg.message_type
|
790
|
+
yield tool_call_msg
|
791
|
+
|
792
|
+
elif isinstance(content, BetaThinkingBlock):
|
793
|
+
self.anthropic_mode = EventMode.THINKING
|
794
|
+
# TODO: Can capture signature, etc.
|
795
|
+
|
796
|
+
elif isinstance(content, BetaRedactedThinkingBlock):
|
797
|
+
self.anthropic_mode = EventMode.REDACTED_THINKING
|
798
|
+
|
799
|
+
if prev_message_type and prev_message_type != "hidden_reasoning_message":
|
800
|
+
message_index += 1
|
801
|
+
|
802
|
+
hidden_reasoning_message = HiddenReasoningMessage(
|
803
|
+
id=self.letta_message_id,
|
804
|
+
state="redacted",
|
805
|
+
hidden_reasoning=content.data,
|
806
|
+
date=datetime.now(timezone.utc).isoformat(),
|
807
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
808
|
+
run_id=self.run_id,
|
809
|
+
step_id=self.step_id,
|
810
|
+
)
|
811
|
+
|
812
|
+
self.reasoning_messages.append(hidden_reasoning_message)
|
813
|
+
prev_message_type = hidden_reasoning_message.message_type
|
814
|
+
yield hidden_reasoning_message
|
815
|
+
|
816
|
+
elif isinstance(event, BetaRawContentBlockDeltaEvent):
|
817
|
+
delta = event.delta
|
818
|
+
|
819
|
+
if isinstance(delta, BetaTextDelta):
|
820
|
+
# Safety check
|
821
|
+
if not self.anthropic_mode == EventMode.TEXT:
|
822
|
+
raise RuntimeError(f"Streaming integrity failed - received BetaTextDelta object while not in TEXT EventMode: {delta}")
|
823
|
+
|
824
|
+
if prev_message_type and prev_message_type != "assistant_message":
|
825
|
+
message_index += 1
|
826
|
+
|
827
|
+
assistant_msg = AssistantMessage(
|
828
|
+
id=self.letta_message_id,
|
829
|
+
# content=[TextContent(text=delta.text)],
|
830
|
+
content=delta.text,
|
831
|
+
date=datetime.now(timezone.utc).isoformat(),
|
832
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
833
|
+
run_id=self.run_id,
|
834
|
+
step_id=self.step_id,
|
835
|
+
)
|
836
|
+
# self.assistant_messages.append(assistant_msg)
|
837
|
+
self.reasoning_messages.append(assistant_msg)
|
838
|
+
prev_message_type = assistant_msg.message_type
|
839
|
+
yield assistant_msg
|
840
|
+
|
841
|
+
elif isinstance(delta, BetaInputJSONDelta):
|
842
|
+
if not self.anthropic_mode == EventMode.TOOL_USE:
|
843
|
+
raise RuntimeError(
|
844
|
+
f"Streaming integrity failed - received BetaInputJSONDelta object while not in TOOL_USE EventMode: {delta}"
|
845
|
+
)
|
846
|
+
|
847
|
+
self.accumulated_tool_call_args += delta.partial_json
|
848
|
+
|
849
|
+
if self.tool_call_name in self.requires_approval_tools:
|
850
|
+
if prev_message_type and prev_message_type != "approval_request_message":
|
851
|
+
message_index += 1
|
852
|
+
tool_call_msg = ApprovalRequestMessage(
|
853
|
+
id=self.letta_message_id,
|
854
|
+
tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
|
855
|
+
date=datetime.now(timezone.utc).isoformat(),
|
856
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
857
|
+
run_id=self.run_id,
|
858
|
+
step_id=self.step_id,
|
859
|
+
)
|
860
|
+
else:
|
861
|
+
if prev_message_type and prev_message_type != "tool_call_message":
|
862
|
+
message_index += 1
|
863
|
+
tool_call_msg = ToolCallMessage(
|
864
|
+
id=self.letta_message_id,
|
865
|
+
tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
|
866
|
+
date=datetime.now(timezone.utc).isoformat(),
|
867
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
868
|
+
run_id=self.run_id,
|
869
|
+
step_id=self.step_id,
|
870
|
+
)
|
871
|
+
|
872
|
+
yield tool_call_msg
|
873
|
+
|
874
|
+
elif isinstance(delta, BetaThinkingDelta):
|
875
|
+
# Safety check
|
876
|
+
if not self.anthropic_mode == EventMode.THINKING:
|
877
|
+
raise RuntimeError(
|
878
|
+
f"Streaming integrity failed - received BetaThinkingBlock object while not in THINKING EventMode: {delta}"
|
879
|
+
)
|
880
|
+
|
881
|
+
if prev_message_type and prev_message_type != "reasoning_message":
|
882
|
+
message_index += 1
|
883
|
+
reasoning_message = ReasoningMessage(
|
884
|
+
id=self.letta_message_id,
|
885
|
+
source="reasoner_model",
|
886
|
+
reasoning=delta.thinking,
|
887
|
+
date=datetime.now(timezone.utc).isoformat(),
|
888
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
889
|
+
run_id=self.run_id,
|
890
|
+
step_id=self.step_id,
|
891
|
+
)
|
892
|
+
self.reasoning_messages.append(reasoning_message)
|
893
|
+
prev_message_type = reasoning_message.message_type
|
894
|
+
yield reasoning_message
|
895
|
+
|
896
|
+
elif isinstance(delta, BetaSignatureDelta):
|
897
|
+
# Safety check
|
898
|
+
if not self.anthropic_mode == EventMode.THINKING:
|
899
|
+
raise RuntimeError(
|
900
|
+
f"Streaming integrity failed - received BetaSignatureDelta object while not in THINKING EventMode: {delta}"
|
901
|
+
)
|
902
|
+
|
903
|
+
if prev_message_type and prev_message_type != "reasoning_message":
|
904
|
+
message_index += 1
|
905
|
+
reasoning_message = ReasoningMessage(
|
906
|
+
id=self.letta_message_id,
|
907
|
+
source="reasoner_model",
|
908
|
+
reasoning="",
|
909
|
+
date=datetime.now(timezone.utc).isoformat(),
|
910
|
+
signature=delta.signature,
|
911
|
+
otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
|
912
|
+
run_id=self.run_id,
|
913
|
+
step_id=self.step_id,
|
914
|
+
)
|
915
|
+
self.reasoning_messages.append(reasoning_message)
|
916
|
+
prev_message_type = reasoning_message.message_type
|
917
|
+
yield reasoning_message
|
918
|
+
|
919
|
+
elif isinstance(event, BetaRawMessageStartEvent):
|
920
|
+
self.message_id = event.message.id
|
921
|
+
self.input_tokens += event.message.usage.input_tokens
|
922
|
+
self.output_tokens += event.message.usage.output_tokens
|
923
|
+
self.model = event.message.model
|
924
|
+
|
925
|
+
elif isinstance(event, BetaRawMessageDeltaEvent):
|
926
|
+
self.output_tokens += event.usage.output_tokens
|
927
|
+
|
928
|
+
elif isinstance(event, BetaRawMessageStopEvent):
|
929
|
+
# Don't do anything here! We don't want to stop the stream.
|
930
|
+
pass
|
931
|
+
|
932
|
+
elif isinstance(event, BetaRawContentBlockStopEvent):
|
933
|
+
self.anthropic_mode = None
|