mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat(agent): add readMedia tool for model to view the image (#165)
* feat(agent): add readMedia tool for loading local images into model context * feat(channel/inbound): include container attachment refs in inbound query * fix(agent): preserve ImagePart literal typing in buildNativeImageParts * chore: rename tool --------- Co-authored-by: 晨苒 <16112591+chen-ran@users.noreply.github.com>
This commit is contained in:
@@ -142,7 +142,7 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
|
||||
if sender == nil {
|
||||
return fmt.Errorf("reply sender not configured")
|
||||
}
|
||||
text := buildInboundQuery(msg.Message)
|
||||
text := buildInboundQuery(msg.Message, nil)
|
||||
if p.logger != nil {
|
||||
p.logger.Debug("inbound handle start",
|
||||
slog.String("channel", msg.Channel.String()),
|
||||
@@ -153,7 +153,7 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
|
||||
slog.String("conversation_id", strings.TrimSpace(msg.Conversation.ID)),
|
||||
)
|
||||
}
|
||||
if strings.TrimSpace(text) == "" && len(msg.Message.Attachments) == 0 {
|
||||
if strings.TrimSpace(msg.Message.PlainText()) == "" && len(msg.Message.Attachments) == 0 {
|
||||
if p.logger != nil {
|
||||
p.logger.Debug("inbound dropped empty", slog.String("channel", msg.Channel.String()))
|
||||
}
|
||||
@@ -185,6 +185,7 @@ func (p *ChannelInboundProcessor) HandleInbound(ctx context.Context, cfg channel
|
||||
identity := state.Identity
|
||||
resolvedAttachments := p.ingestInboundAttachments(ctx, cfg, msg, strings.TrimSpace(identity.BotID), msg.Message.Attachments)
|
||||
attachments := mapChannelToChatAttachments(resolvedAttachments)
|
||||
text = buildInboundQuery(msg.Message, attachments)
|
||||
|
||||
// Resolve or create the route via channel_routes.
|
||||
if p.routeResolver == nil {
|
||||
@@ -1052,7 +1053,7 @@ func mapStreamChunkToChannelEvents(chunk conversation.StreamChunk) ([]channel.St
|
||||
}
|
||||
}
|
||||
|
||||
func buildInboundQuery(message channel.Message) string {
|
||||
func buildInboundQuery(message channel.Message, attachments []conversation.ChatAttachment) string {
|
||||
text := strings.TrimSpace(message.PlainText())
|
||||
if text != "" {
|
||||
return text
|
||||
@@ -1061,10 +1062,46 @@ func buildInboundQuery(message channel.Message) string {
|
||||
return ""
|
||||
}
|
||||
count := len(message.Attachments)
|
||||
fallback := fmt.Sprintf("[User sent %d attachments]", count)
|
||||
if count == 1 {
|
||||
return "[User sent 1 attachment]"
|
||||
fallback = "[User sent 1 attachment]"
|
||||
}
|
||||
return fmt.Sprintf("[User sent %d attachments]", count)
|
||||
refs := collectContainerAttachmentRefs(attachments)
|
||||
if len(refs) == 0 {
|
||||
return fallback
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fallback)
|
||||
sb.WriteString("\n[Attachment refs: container paths]\n")
|
||||
for _, ref := range refs {
|
||||
sb.WriteString("- ")
|
||||
sb.WriteString(ref)
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
return strings.TrimSpace(sb.String())
|
||||
}
|
||||
|
||||
func collectContainerAttachmentRefs(attachments []conversation.ChatAttachment) []string {
|
||||
if len(attachments) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{}, len(attachments))
|
||||
refs := make([]string, 0, len(attachments))
|
||||
for _, att := range attachments {
|
||||
ref := strings.TrimSpace(att.Path)
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[ref]; exists {
|
||||
continue
|
||||
}
|
||||
seen[ref] = struct{}{}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func normalizeContentPartType(raw string) channel.MessagePartType {
|
||||
|
||||
@@ -387,7 +387,7 @@ func TestBuildInboundQueryAttachmentFallback(t *testing.T) {
|
||||
{Type: channel.AttachmentImage},
|
||||
},
|
||||
}
|
||||
if got := buildInboundQuery(one); got != "[User sent 1 attachment]" {
|
||||
if got := buildInboundQuery(one, nil); got != "[User sent 1 attachment]" {
|
||||
t.Fatalf("unexpected single attachment fallback: %q", got)
|
||||
}
|
||||
|
||||
@@ -397,11 +397,34 @@ func TestBuildInboundQueryAttachmentFallback(t *testing.T) {
|
||||
{Type: channel.AttachmentImage},
|
||||
},
|
||||
}
|
||||
if got := buildInboundQuery(two); got != "[User sent 2 attachments]" {
|
||||
if got := buildInboundQuery(two, nil); got != "[User sent 2 attachments]" {
|
||||
t.Fatalf("unexpected multiple attachment fallback: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInboundQueryAttachmentFallbackWithContainerRefs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
msg := channel.Message{
|
||||
Attachments: []channel.Attachment{
|
||||
{Type: channel.AttachmentImage},
|
||||
{Type: channel.AttachmentImage},
|
||||
},
|
||||
}
|
||||
atts := []conversation.ChatAttachment{
|
||||
{Path: "/data/media/ab/first.png"},
|
||||
{Path: "/data/media/cd/second.png"},
|
||||
{Path: "/data/media/ab/first.png"},
|
||||
}
|
||||
want := "[User sent 2 attachments]\n" +
|
||||
"[Attachment refs: container paths]\n" +
|
||||
"- /data/media/ab/first.png\n" +
|
||||
"- /data/media/cd/second.png"
|
||||
if got := buildInboundQuery(msg, atts); got != want {
|
||||
t.Fatalf("unexpected attachment refs fallback:\nwant:\n%s\n\ngot:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelInboundProcessorAttachmentOnlyUsesFallbackQuery(t *testing.T) {
|
||||
channelIdentitySvc := &fakeChannelIdentityService{channelIdentity: identities.ChannelIdentity{ID: "channelIdentity-fallback"}}
|
||||
memberSvc := &fakeMemberService{isMember: true}
|
||||
|
||||
Reference in New Issue
Block a user