fix(backend): Use raw content from the LLM as conversation history (#9551)

### Changes 🏗️

Instead of parsing an LlmMessage by ourselves, we use raw content from
the LLM as conversation history and accept any format it introduces.

Example output:
```
[
  {
    "role": "system",
    "content": "Thinking carefully step by step decide which function to call. Always choose a function call from the list of function signatures. The test graph is basically an all knowing answer machine you can use it to get an answer"
  },
  {
    "role": "user",
    "content": "Hey how's the weather today"
  },
  {
    "role": "assistant",
    "audio": null,
    "content": null,
    "refusal": null,
    "tool_calls": [
      {
        "id": "call_Z7CKKIkldylmfWJdE6ZnDxjr",
        "type": "function",
        "function": {
          "name": "storevalueblock",
          "arguments": "{\"input\":\"I don't have context for your location. Could you provide one?\"}"
        }
      }
    ],
    "function_call": null
  }
]
```

### Checklist 📋

#### For code changes:
- [ ] I have clearly listed my changes in the PR description
- [ ] I have made a test plan
- [ ] I have tested my changes according to the test plan:
  <!-- Put your test plan here: -->
  - [ ] ...

<details>
  <summary>Example test plan</summary>
  
  - [ ] Create from scratch and execute an agent with at least 3 blocks
- [ ] Import an agent from file upload, and confirm it executes
correctly
  - [ ] Upload agent to marketplace
- [ ] Import an agent from marketplace and confirm it executes correctly
  - [ ] Edit an agent from monitor, and confirm it executes correctly
</details>

#### For configuration changes:
- [ ] `.env.example` is updated or already compatible with my changes
- [ ] `docker-compose.yml` is updated or already compatible with my
changes
- [ ] I have included a list of my configuration changes in the PR
description (under **Changes**)

<details>
  <summary>Examples of configuration changes</summary>

  - Changing ports
  - Adding new services that need to communicate with each other
  - Secrets or environment variable changes
  - New or infrastructure changes such as databases
</details>
This commit is contained in:
Zamil Majdy
2025-03-02 03:41:38 +07:00
committed by GitHub
parent d3b83b79f9
commit 83d879ea65
2 changed files with 20 additions and 34 deletions

View File

@@ -229,17 +229,6 @@ for model in LlmModel:
raise ValueError(f"Missing MODEL_METADATA metadata for model: {model}")
class MessageRole(str, Enum):
SYSTEM = "system"
USER = "user"
ASSISTANT = "assistant"
class Message(BlockSchema):
role: MessageRole
content: str
class ToolCall(BaseModel):
name: str
arguments: str
@@ -252,7 +241,8 @@ class ToolContentBlock(BaseModel):
class LLMResponse(BaseModel):
prompt: str
raw_response: Any
prompt: List[Any]
response: str
tool_calls: Optional[List[ToolContentBlock]] | None
prompt_tokens: int
@@ -362,7 +352,8 @@ def llm_call(
tool_calls = None
return LLMResponse(
prompt=json.dumps(prompt),
raw_response=response.choices[0].message,
prompt=prompt,
response=response.choices[0].message.content or "",
tool_calls=tool_calls,
prompt_tokens=response.usage.prompt_tokens if response.usage else 0,
@@ -423,7 +414,8 @@ def llm_call(
)
return LLMResponse(
prompt=json.dumps(prompt),
raw_response=resp.content[0],
prompt=prompt,
response=(
resp.content[0].name
if isinstance(resp.content[0], anthropic.types.ToolUseBlock)
@@ -450,7 +442,8 @@ def llm_call(
max_tokens=max_tokens,
)
return LLMResponse(
prompt=json.dumps(prompt),
raw_response=response.choices[0].message,
prompt=prompt,
response=response.choices[0].message.content or "",
tool_calls=None,
prompt_tokens=response.usage.prompt_tokens if response.usage else 0,
@@ -469,7 +462,8 @@ def llm_call(
stream=False,
)
return LLMResponse(
prompt=json.dumps(prompt),
raw_response=response.get("response") or "",
prompt=prompt,
response=response.get("response") or "",
tool_calls=None,
prompt_tokens=response.get("prompt_eval_count") or 0,
@@ -515,7 +509,8 @@ def llm_call(
tool_calls = None
return LLMResponse(
prompt=json.dumps(prompt),
raw_response=response.choices[0].message,
prompt=prompt,
response=response.choices[0].message.content or "",
tool_calls=tool_calls,
prompt_tokens=response.usage.prompt_tokens if response.usage else 0,
@@ -557,7 +552,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase):
default="",
description="The system prompt to provide additional context to the model.",
)
conversation_history: list[Message] = SchemaField(
conversation_history: list[Any] = SchemaField(
default=[],
description="The conversation history to provide context for the prompt.",
)
@@ -613,7 +608,8 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase):
],
test_mock={
"llm_call": lambda *args, **kwargs: LLMResponse(
prompt="",
raw_response="",
prompt=[""],
response=json.dumps(
{
"key1": "key1Value",
@@ -1032,7 +1028,7 @@ class AITextSummarizerBlock(AIBlockBase):
class AIConversationBlock(AIBlockBase):
class Input(BlockSchema):
messages: List[Message] = SchemaField(
messages: List[Any] = SchemaField(
description="List of messages in the conversation.", min_length=1
)
model: LlmModel = SchemaField(

View File

@@ -53,7 +53,7 @@ class SmartDecisionMakerBlock(Block):
default="Thinking carefully step by step decide which function to call. Always choose a function call from the list of function signatures.",
description="The system prompt to provide additional context to the model.",
)
conversation_history: list[llm.Message] = SchemaField(
conversation_history: list[Any] = SchemaField(
default=[],
description="The conversation history to provide context for the prompt.",
)
@@ -84,7 +84,7 @@ class SmartDecisionMakerBlock(Block):
finished: str = SchemaField(
description="The finished message to display to the user."
)
conversations: list[llm.Message] = SchemaField(
conversations: list[Any] = SchemaField(
description="The conversation history to provide context for the prompt."
)
@@ -312,7 +312,6 @@ class SmartDecisionMakerBlock(Block):
if not response.tool_calls:
yield "finished", f"No Decision Made finishing task: {response.response}"
assistant_response = response.response
else:
for tool_call in response.tool_calls:
tool_name = tool_call.function.name
@@ -321,15 +320,6 @@ class SmartDecisionMakerBlock(Block):
for arg_name, arg_value in tool_args.items():
yield f"tools_^_{tool_name}_{arg_name}".lower(), arg_value
assistant_response = "\n".join(
f"[{c.function.name}] called with arguments: {c.function.arguments}"
for c in response.tool_calls
)
input_data.conversation_history.append(
llm.Message(role=llm.MessageRole.USER, content=response.prompt)
)
input_data.conversation_history.append(
llm.Message(role=llm.MessageRole.ASSISTANT, content=assistant_response)
)
input_data.conversation_history.extend(response.prompt)
input_data.conversation_history.append(response.raw_response)
yield "conversations", input_data.conversation_history